Published
Edited
Aug 21, 2020
1 fork
Insert cell
md`# K-root instances used from RIPE Atlas probes ${regionName}`
Insert cell
regionName = region.charAt(0).toUpperCase() + region.slice(1);
Insert cell
excludeProbes=[2779, 542];
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);
Insert cell
minFarthestCount = d3.min(ffLocs, d => d.properties.aggFarthestCount);
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 cc!=='HR' && cc !=='RS' && cc !=='XK' && cc !== 'AL' && cc!=='ME' && cc!=='MK' && cc!=='SI' && cc!=='BA' && c.properties.id !== 'KOS'}
Insert cell
inFilter = c => { const cc = c.properties.countryCodeAlpha2; return cc=='HR' || cc==='RS' || cc==='XK' || cc==='AL' || cc==='ME' || cc==='MK' || cc==='SI' || cc=='BA' || c.properties.id === 'KOS'}
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(inFilter);
Insert cell
region = "see" // berlin, frankfurt, ruhrgebiet, hamburg, münchen, leipzig
Insert cell
mapScale=3550
Insert cell
latMove = -19.6
Insert cell
lonMove = -46;
Insert cell
probeCenter = turf.center(prbGeoJson);
Insert cell
html`<svg><g>${krootLocs.map((l, i) => `<rect x=0 y=${i*18 + 4} width=40 height=10 style="fill: ${stringColor[krootLocs.indexOf(l)]}"/><text x=60 y=${i*18 + 15}>${l}</text>`)}</g></svg>`;
Insert cell
circleLegend = [
{cityName: "London", minRtt: 6, realCoords: [20,10]},
{cityName: "London", minRtt: 20, realCoords: [20,40]},
{cityName: "London", minRtt: 30, realCoords: [20,70]},
{cityName: "London", minRtt: 40, realCoords: [20,100]},
{cityName: "London", minRtt: 60, realCoords: [20,130]},
];
Insert cell
html`<svg><g>${circleLegend.map(krootProbesMap)}</g>
<g>
${circleLegend.map((ci,i) => `<text x=${ci.realCoords[0]+20} y=${ci.realCoords[1] + 5}>${ci.minRtt} ms ${i===0 && "or lower" || i === circleLegend.length - 1 && "or higher" || ""}</text>`)}
</g>
</svg>`
Insert cell
map = html`<h1>${regionName}</h1><svg viewBox="0 0 ${width} ${height}" style="display: block;">
<style>
.see-region { stroke: #eeeee; stroke-width: 0.5; fill-opacity: 0.3; fill: white; }
.world-bg { stroke: white; fill: #eeeeee; fill-opacity: 1.0; }
.water {fill: lightBlue; }
.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; fill: white; stroke: black; stroke-width: 1; stroke-opacity: 1; }
.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: #64d46a; stroke: none; }
.country-labels { font: 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-bg">
<rect class="water" x=0 x=0 width=954 height=800></rect>
${land.features.map(c => `<path d="${path(c.geometry)}"></path>`)}
</g>
<g class="see-region" clip-path="url(${new URL("#clip", location)})">
<use xlink:href="${new URL("#outline", location)}" fill="#fff" />
${countriesGeom.map(c => `<path class="kreis" d="${path(c.geometry)}" style="fill: ${popKreisColor(c.properties.pop_density)}"></path>`)}
</g>
<g class="country-labels">
${countriesGeom.map(cc => {
const center = projection(turf.centroid(cc).geometry.coordinates);
return `<text x=${center[0]} y=${center[1]}>${cc.properties.countryName}</text>`
})}
</g>
<g>
${krootProbes.map(krootProbesMap)}
</g>

</svg>`
Insert cell
krootProbesMap = p => {
return `<circle class="probe ${p.cityName}" style="stroke: ${stringColor[krootLocs.indexOf(p.cityName)]}; fill: none;" cx=${p.coords && projection(p.coords)[0] || p.realCoords[0]} cy=${p.coords && projection(p.coords)[1] || p.realCoords[1]} r="${(p.minRtt <= 6 && 1) || (p.minRtt < 60 && p.minRtt / 6) || 10}"/>`
}
Insert cell
countriesGeom = land.features.filter(
c => {
const cc = c.properties.countryCode;
return cc==='HR' || cc==='RS' || cc==='XK' || cc==='AL' || cc==='ME' || cc==='MK' || cc==='SI' || cc=='BA' || c.properties.id === 'KOS'}
);
Insert cell
// <g>${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}</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" />
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
krootLocs = Array.from(new Set(krootProbes.map(p => p.cityName)));
Insert cell
path = d3.geoPath(projection.scale(mapScale).rotate([latMove,lonMove]))
Insert cell
projection = d3.geoMercator()
Insert cell
lineLocs = fetch(`https://sg-pub.ripe.net/jasper/see-cr/${region}.linestrings.geo.json`).then(d => d.json(), e => console.log(e));
Insert cell
colsv = d3.dsvFormat(";");
Insert cell
krootProbes = colsv.parse(await FileAttachment("kroot-locations-rtt-map_ed.csv").text(), d =>
({
coords: [Number(d.Longitude), Number(d.Latitude)],
minRtt: Number(d['Min. Rtt']),
cityName: String(d.City)
})
);
Insert cell
d3.dsvFormat("|")
Insert cell
probes = fetch(`https://sg-pub.ripe.net/jasper/see-cr/${region}_probes.json`).then(d => d.json())
Insert cell
apnic_file = fetch("https://sg-pub.ripe.net/jasper/germany-cr/pop_coverage_v2.json").then(d => d.json())
Insert cell
enduserAsns = apnic_file.find(d => d.country==="MK" || d.country==="SI" || d.country==="RS" || d.countr==="HR" || d.country==="BA" || d.country==="ME" || d.country==="AL" || d.country==='XK').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

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more