Published
Edited
Oct 14, 2019
1 fork
Insert cell
md`# Region rings ${regionName}`
Insert cell
regionName = region.charAt(0).toUpperCase() + region.slice(1);
Insert cell
region = "frankfurt" // berlin, frankfurt, ruhrgebiet, hamburg, münchen, leipzig
Insert cell
excludeProbes=[2779, 542];
Insert cell
center = turf.envelope(prbGeoJson)
Insert cell
filteredPaths = lineLocs.geo.features.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.farthestCount);
Insert cell
minFarthestCount = d3.min(ffLocs, d => d.properties.farthestCount);
Insert cell
ooc = lineLocs.locations.filter(l => l.countryCodeAlpha2 !== 'DE');
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
},
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)
}
});
}
}
return ffLocs; },[]).sort((a,b) => a.properties.passThroughCount > b.properties.passThroughCount && -1 || 1);
Insert cell
map = html`<h1>${regionName}</h1><svg viewBox="0 0 ${width} ${height}" style="display: block;">
<style>
.world { stroke: #eeeeee; stroke-width: 0.5; fill-opacity: 0.5 }
.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; stroke: black; stroke-width: 1; }
.pt-locs { fill: white; stroke: black; }
</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.map(l => `<path class="kreis" d="${path(l.geometry)}" style="fill: ${popKreisColor(l.properties.pop_density)}"></path>`)}
</g>
<g class="bunch-of-lines">${filteredPaths.map(s => `<path d="${path(s)}" style="stroke: ${stringColor[s.properties.idx % stringColor.length]};"/>`)}</g>
<g>${ffLocs.map(l => { let coords = projection(l.geometry.coordinates);
return `<circle class="pt-locs" r="${farthestScale(l.properties.passThroughCount)}" cx="${coords[0]}" cy="${coords[1]}"/><circle class="f-locs" r="${farthestScale(l.properties.farthestCount)}" cx="${coords[0]}" cy="${coords[1]}"/><text x="${coords[0]}" y="${coords[1]}">${l.properties.cityName}</text>`})}
</g>
<use xlink:href="${new URL("#outline", location)}" fill="none" stroke="#000" />
</svg>`
Insert cell
Insert cell
farthestScale = d3.scaleSqrt()
.domain([minFarthestCount, maxFarthestCount])
.range([0, 30]);
Insert cell
height = 1200
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
path = d3.geoPath(projection.scale(3150).rotate([-10,-57]))
Insert cell
projection = d3.geoMercator()
Insert cell
lineLocs = fetch(`https://sg-pub.ripe.net/jasper/germany-cr/${region}.linestrings.geo.json`).then(d => d.json(), e => console.log(e));
Insert cell
probes = fetch(`https://sg-pub.ripe.net/jasper/germany-cr/${region}_probes.json`).then(d => d.json())
Insert cell
Insert cell
world = fetch("https://www-static.ripe.net/static/rnd-ui/openipmap/production/maps/country_deu_adm2.topo.json").then(r => r.json())
Insert cell
land = topojson.feature(world, world.objects["country_deu_adm2"])
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