Published
Edited
Jan 14, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
facet: {
data: updown,
y: "prb_id",
marginLeft: 70,
grid: true,
},
fy: {
domain: ordered_probe_id_list,
},
color: { // needed otherwise Plot.plot will do a color conversion
domain: color_arr,
range: color_arr
},
x: {
grid: true
},
marks: [
Plot.tickX(updown, {x: "start", stroke: "black"}),
Plot.tickX(updown, {x: "stop", stroke: "black"}),
Plot.ruleY(updown, {x1: "start", x2: "stop", y: null, strokeWidth: 7, stroke: d => p2c[ d.prb_id ].color })
]
})
Insert cell
ordered_probe_id_list = {
return probes.sort( (a,b) => p2c[ a.id ].dists[0] - p2c[ b.id ].dists[0] ).map( x => x.id )
}
Insert cell
Insert cell
fade_dist = { // the maximum distance in the triangle between the cities. this is the 0 in the color channel
return Math.max(
haversine( loc2coords[0], loc2coords[1] ),
haversine( loc2coords[0], loc2coords[2] ),
haversine( loc2coords[1], loc2coords[2] )
)
}
Insert cell
async function nom_coordinates( loc_name ) {
let coords = fetch(
`https://nominatim.openstreetmap.org/search.php?q=${ loc_name }&format=json`
)
.then(response => response.json())
.then(json => json[0])
.then(j => [+(j.lon), +(j.lat)])
.catch(error => {
// Nominatim might be down
// flalback to pin-on-the-map selection
// mutable nominatim_down = true;
// return _coordinates;
})
return coords;
}
Insert cell
function paginated_fetch(url = "", previousResponse = []) {
return fetch(url)
.then(response => response.json())
.then(newResponse => {
const response = [...previousResponse, ...newResponse['results']]; // Combine the two arrays
if (newResponse['next']) {
url = newResponse['next'];
return paginated_fetch(url, response);
}
return response;
});
}
Insert cell
async function fetch_probes_for_county( ccode ) {
var url = `https://atlas.ripe.net/api/v2/probes?country_code=${ ccode }`;
/* var probes = []
// while( url !== undefined ) {
let p_chunk = fetch( url )
.then( r => r.json() )
.then( r => r['results'] )
probes.push( p_chunk )
} */
var prbs = paginated_fetch( url )
return prbs;
}
Insert cell
color_arr = {
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
return Object.keys(p2c).map( d => p2c[ d ].color ).sort().filter( onlyUnique ) // jump through hoops to do an identity color
}
Insert cell
p2c = { // matrix of [loc][prb]. (by index)
var p2c = {}
var range_f = d3.scaleLinear().domain([0, fade_dist]).range([0,252]).clamp(true);
//console.log( range_f( 5000 ) );
probes.forEach( p => {
const h0 = haversine( p.geometry.coordinates, loc2coords[ 0 ] )
const h1 = haversine( p.geometry.coordinates, loc2coords[ 1 ] )
const h2 = haversine( p.geometry.coordinates, loc2coords[ 2 ] )
const r = parseInt( range_f( h0 ) )
const g = parseInt( range_f( h1 ) )
const b = parseInt( range_f( h2 ) )
//p2c[ p.id ] = `rgb( ${r}, ${g}, ${b} )`
p2c[ p.id ] = {
color: '#' + rgbToHex(r) + rgbToHex(g) + rgbToHex(b),
dists: [h0,h1,h2]
}
});
return p2c;
}
Insert cell
rgbToHex = function (rgb) {
var hex = Number(rgb).toString(16);
if (hex.length < 2) {
hex = "0" + hex;
}
return hex;
};
Insert cell
probes = fetch_probes_for_county( cc ).then( p => p.filter( d => (d.status.id == 1) || (d.status.id == 2) ) )
Insert cell
start = _start.toISOString().split("T")[0]
Insert cell
Insert cell
stop = _stop.toISOString().split("T")[0]
Insert cell
viewof _stop = new Input(
new Date(
(() => {
let a = new Date();
a.setDate(a.getDate() + 1);
return a.toISOString().split("T")[0];
})()
)
)
Insert cell
updown = {
var updown = []; // list of updown
var state = {};
const ts_start = Math.floor( _start.getTime() / 1000)
const ts_stop = Math.floor( _stop.getTime() / 1000)

connections.forEach( d => {
if ( d.event == "connect" ) {
state[ d.prb_id ] = d.timestamp
} else if ( d.event == "disconnect" ) {
var ev_start = ts_start
if ( d.prb_id in state ) { // we have a beginning
ev_start = state[ d.prb_id ]
}
updown.push({
start: new Date( ev_start * 1000 ),
stop: new Date( d.timestamp * 1000 ),
prb_id: d.prb_id
})
}
})
for ( var prb_id in state ) {
updown.push({
start: new Date( state[ prb_id ] * 1000 ),
stop: new Date( ts_stop * 1000 ),
prb_id: +prb_id
})
}
return updown
}
Insert cell
connections = { // adapted/stolen from Agustin :)
let connections = [];
const page_size = 10;
let ended = false;
let i = 0;
let chunk;
let chunks = [];
while (!ended) {
chunk = probes.slice(i, i + page_size).map( d => d.id );
chunks.push(chunk);
console.log( chunk )

i += page_size;
ended = i >= probes.length;
}

let promises = chunks.map(chunk =>
fetch(
`https://atlas.ripe.net:443/api/v2/measurements/7000/results/?start=${start}T00:00&stop=${stop}T00:00&probe_ids=${chunk.join(
','
)}`
).then(r => r.json())
);

let conns = await Promise.all(promises);

return conns.flat();
}
Insert cell
L = {
const L = await require("leaflet@1/dist/leaflet.js");
if (!L._style) {
const href = await require.resolve("leaflet@1/dist/leaflet.css");
document.head.appendChild(L._style = html`<link href=${href} rel=stylesheet>`);
}
return L;
}
Insert cell
function haversine(coord1, coord2 ) {
// adapted from https://stackoverflow.com/questions/14560999/using-the-haversine-formula-in-javascript#14561433
function rad( x ) {
return x * Math.PI / 180
}
var lon1 = coord1[0]
var lat1 = coord1[1]
var lon2 = coord2[0]
var lat2 = coord2[1]
console.log( lon1, lat1, lon2, lat2 )
var R = 6371; // km
//has a problem with the .toRad() method below.
var x1 = lat2-lat1;
var dLat = rad( x1 );
var x2 = lon2-lon1;
var dLon = rad( x2 )
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos( rad( lat1 )) * Math.cos( rad( lat2 )) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
Insert cell
# Below this are tests
Insert cell
haversine( await nom_coordinates("Nur Sultan,KZ"), await nom_coordinates("Almaty,KZ") )
Insert cell
nom_coordinates("Almaty,KZ")
Insert cell
import {Text, Input, bind} from "@observablehq/inputs"
Insert cell
d3 = require('d3@6')
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