Published
Edited
Aug 21, 2020
Insert cell
md`# Region rings ${filterString} ${regionName}`
Insert cell
// Set to either "inside" or "outside" to see paths
// staying comopletely inside the region or paths going outside the
// region, respectively
filterString = "inside"
Insert cell
filterType = ({ "inside": inFilter, "outside": outFilter })[filterString]
Insert cell
regionName = region.charAt(0).toUpperCase() + region.slice(1).replace("-"," ");
Insert cell
regionCountries = ["KZ", //Kazakhstan
"KG", // Kyrgyzstan
"TJ", // Tajikistan
"TM", // Turkmenistan
"UZ"
]
Insert cell
excludeProbes=[];
Insert cell
center = turf.envelope(prbGeoJson)
Insert cell
filteredPaths = lineLocs.geo.features.filter(p => enduserAsns.some(asn => {
let mp = probes.find(pp => pp.probe_id === p.properties.src_prb_id || pp.probe_id===p.properties.src_prb_id);
return mp ? (asn===mp.asn_v4 || asn===mp.asn_v6) : false;
})).filter(f => !excludeProbes.some(prbId => prbId===f.properties.dst_prb_id || prbId===f.properties.src_prb_id))
Insert cell
md`### stats`
Insert cell
[d3.min(filteredPaths, d => d.properties.pathLength), d3.max(filteredPaths, d => d.properties.pathLength), d3.median(filteredPaths, d => d.properties.pathLength)]
Insert cell
maxFarthestCount = d3.max(ffLocs, d => d.properties.aggFarthestCount) || d3.max(ffLocs, d => d.properties.farthestCount);
Insert cell
minFarthestCount = d3.min(ffLocs, d => d.properties.aggFarthestCount) || d3.min(ffLocs, d => d.properties.farthestCount);
Insert cell
// Specify either inFilter or outFilter here, so you'll see only the farthest Locations and transit Locations inside the SEE region or outside of it.
// ooc = lineLocs.locations.filter(inFilter);
Insert cell
outFilter = c => { const cc = c.properties.countryCodeAlpha2; return !regionCountries.some(ccc => ccc === cc)}
Insert cell
inFilter = c => { const cc = c.properties.countryCodeAlpha2; return regionCountries.some(ccc => ccc === cc)}
Insert cell
oocGeoIps = lineLocs.geo.features
.flatMap(s => s.properties.locationString)
//.filter(ls => ooc.some(ll => ll.id===ls.locationId))
.reduce((locIps, locIp) => {
let eLocIp = locIps.indexOf(locIps.find(li => li.locationId === locIp.locationId));
if (eLocIp===-1) {
locIps = [...locIps, {locationId: locIp.locationId, ips: [locIp.ip], passThroughCount: 1}]
}
else {
locIps[eLocIp] = {
locationId: locIps[eLocIp].locationId,
passThroughCount: ++locIps[eLocIp].passThroughCount,
ips: Array.from(new Set([...locIps[eLocIp].ips, locIp.ip]))};
}
return locIps;
},[]);
Insert cell
asns = probes.reduce((asnArr, p) => {
let asn_v4 = asnArr.asn_v4.find(ac => ac.asn===p.asn_v4);
if (asn_v4) {
asn_v4.count++
} else {
asnArr.asn_v4.push({ asn: p.asn_v4, count: 1});
}
let asn_v6 = asnArr.asn_v6.find(ac => ac.asn===p.asn_v6);
if (asn_v6) {
asn_v6.count++
} else {
asnArr.asn_v6.push({ asn: p.asn_v6, count: 1});
}
return asnArr;
}, {asn_v4:[], asn_v6:[]});
Insert cell
// All farthest locations with passtroughCount and farthesLocationCount.

fLocs = lineLocs.geo.features.map(s => s.properties.farthestLocation[0]).reduce((ffLocs, loc) => {
let eLoc = ffLocs.indexOf(ffLocs.find(li => li.locationId === loc));
if (eLoc===-1) {
ffLocs = [...ffLocs, {locationId: loc, farthestCount: 1}];
}
else {
ffLocs[eLoc] = { locationId: ffLocs[eLoc].locationId, farthestCount: ++ffLocs[eLoc].farthestCount }
}
return ffLocs;
},[])
.map(lId => {
let loc = lineLocs.locations.find(ll => ll.id===lId.locationId);
return loc && {
type: 'Feature',
properties: {
...loc,
passThroughCount: oocGeoIps.find(ll => ll.locationId===lId.locationId).passThroughCount,
farthestCount: lId.farthestCount,
medianPathLength: d3.median(filteredPaths.filter(p => p.properties.farthestLocation[0]===lId.locationId).map(p => p.properties.pathLength))
},
geometry: {
type: 'Point',
coordinates: [loc.longitude, loc.latitude]
}
}
}).filter(l => l);
Insert cell
scan = turf.clustersDbscan({ type: "FeatureCollection", features: fLocs }, 30, {minPoints: 2});
Insert cell
// clustered locations, set 2nd arg in scan for clustering radius.

ffLocs = scan.features.reduce(
(ffLocs, loc) => {
let cluster = scan.features.filter(ll => ll.properties.cluster === loc.properties.cluster);
if (loc.properties.dbscan === "noise") {
ffLocs.push(loc);
};
if (loc.properties.cluster>=0) {
if (
cluster.every(ll => ll.properties.cityPopulation <= loc.properties.cityPopulation)
) {
ffLocs.push({
...loc,
properties: {
...loc.properties,
aggPassThroughCount: cluster.reduce((count, cm) => count + cm.properties.passThroughCount, 0),
aggFarthestCount: cluster.reduce((count, cm) => count + cm.properties.farthestCount, 0),
avgMedianPathLength: d3.mean(cluster.map(cm => cm.properties.medianPathLength))
}
});
}
}
return ffLocs; },[]).sort((a,b) => a.properties.farthestCount > b.properties.farthestCount && -1 || 1)
// specify inFilter (locations in SEE only) or outFilter (locations outside SEE only)
.filter(filterType);
Insert cell
probeCenter = turf.center(prbGeoJson);
Insert cell
region = "central-asia"
Insert cell
mapScale= 850
Insert cell
Insert cell
map = html`<h1>Paths ${filterString} ${regionName}</h1><svg viewBox="0 0 ${width} ${height}" style="display: block;">
<style>
.world { stroke: #eeeeee; stroke-width: 0.5; fill-opacity: 0.3 }
.hop-loc { fill: none; stroke-width: 1.5; stroke: black; }
.probe-loc { stroke-width: 2; stroke: none; fill: white; fill-opacity: 0.8 }
.l-path { fill: none; stroke-width: 2; stroke: lightBlue; }
.kreis { fill-opacity: 0.8; }
.bunch-of-lines { stroke: black; stroke-width: 0.2; fill: none; stroke-opacity: 1; }
.f-locs { fill: white; fill-opacity: 0.6; stroke: black; stroke-width: 1; }
.pt-locs { fill: none; stroke: #a2cade; }
.e-line { fill: none; stroke: #ff8e19; stroke-opacity: 0.7; }
.probe-center { fill: none; stroke: none; }
.cc-name { fill: white; stroke: none; font: normal 14px arial; }
</style>
<defs>
<path id="outline" d="${path(outline)}" />
<clipPath id="clip"><use xlink:href="${new URL("#outline", location)}" /></clipPath>
</defs>
<g class="world" clip-path="url(${new URL("#clip", location)})">
<use xlink:href="${new URL("#outline", location)}" fill="#fff" />
${land.features.filter(c => { const cc = c.properties.countryCode; return regionCountries.find(ccc => ccc === cc) }).map(l => { const center = projection(turf.centerOfMass(l.geometry).geometry.coordinates); return `<path class="kreis" d="${path(l.geometry)}" style="fill: ${popKreisColor(l.properties.pop_density)}"></path><text class="cc-name" x=${center[0]} y=${center[1]}>${l.properties.countryCode}</text>`; })}
</g>
<g>${filterString === "outside" && ffLocs.map(l => {
let eCoords = projection(l.geometry.coordinates);
let bCoords = projection(probeCenter.geometry.coordinates);
//d="M 10 10 H 90 V 90 H 10 L 10 10"
return `<line class="e-line" x1="${bCoords[0]}" y1="${bCoords[1]}" x2="${eCoords[0]}" y2="${eCoords[1]}" style="stroke-width: ${farthestLine(l.properties.farthestCount)};"/>`;
})}</g>
<g>${ffLocs.map(l => { let coords = projection(l.geometry.coordinates);
return `<circle class="pt-locs" r="${farthestScale(l.properties.aggPassThroughCount || l.properties.passThroughCount)}" cx="${coords[0]}" cy="${coords[1]}"/><circle class="f-locs" style="fill: ${pathLengthScale(l.properties.avgMedianPathLength || l.properties.medianPathLength)}" r="${farthestScale(l.properties.aggFarthestCount || l.properties.farthestCount)}" cx="${coords[0]}" cy="${coords[1]}"/><text x="${coords[0]}" y="${coords[1]}">${l.properties.cityName !== "Astana" && l.properties.cityName || "Nur-Sultan"}</text>`})}
</g>
<g><circle class="probe-center" cx="${projection(probeCenter.geometry.coordinates)[0]}" cy="${projection(probeCenter.geometry.coordinates)[1]}" r="5"/></g>
<use xlink:href="${new URL("#outline", location)}" fill="none" stroke="#000" />
</svg>`
Insert cell
// radical researcher
// jediIxp="#ff8e19"

// jediTransit="#a2cade";
// jediEyeballs="#64d46a";
// jediEyeballsNoProbes="#dcede6";
// jediAnthracite="#363738";
Insert cell
// html`<svg><g class="bunch-of-lines">${filteredPaths.map(s => `<path d="${path(s)}" style="stroke: ${stringColor[s.properties.idx % stringColor.length]};"/>`)}</g></svg>`
Insert cell
Insert cell
farthestScale = d3.scaleLinear()
.domain([1, maxFarthestCount])
.range([2, 30]);
Insert cell
farthestLine = d3.scaleLinear()
.domain([1, maxFarthestCount])
.range([0.2, 60])
Insert cell
pathLengthScale = d3.scaleLinear()
.domain([30, 500, 1800])
.range(["#64d46a","yellow","#ff0000"])
Insert cell
height = 800
Insert cell
popKreisColor = d3.scaleLinear()
.domain([30, 580])
.range(["#ffffff", "#cccccc"])
.interpolate(d3.interpolateLab);
Insert cell
stringColor =["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"]
Insert cell
projection = d3.geoMercator()
Insert cell
lineLocs = fetch(`https://sg-pub.ripe.net/jasper/central-asia-cr/region-jedi.linestrings.geo.json`).then(d => d.json(), e => console.log(e));
Insert cell
probes = fetch(`https://sg-pub.ripe.net/jasper/central-asia-cr/${region}_probes.json` ).then(d => d.json())
Insert cell
apnic_file = fetch("https://sg-pub.ripe.net/jasper/central-asia-cr/pop_coverage_v2.json").then(d => d.json())
Insert cell
enduserAsns = apnic_file.filter(d => regionCountries.find(c => c===d.country)).flatMap(d => d.eyeball_networks).map(en => en.as)
Insert cell
Insert cell
world = fetch("https://sg-pub.ripe.net/jasper/see-cr/openipmapCountries_50m.topo.json").then(r => r.json())
Insert cell
land = topojson.feature(world, world.objects["openipmapCountries_50m"])
Insert cell
outline = ({type: "Sphere"})
Insert cell
turf = require("https://npmcdn.com/@turf/turf@5.1.6/turf.js")
Insert cell
d3 = require("d3@5")
Insert cell
topojson = import("topojson-client@3")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more