Published
Edited
Jul 18, 2019
16 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
featureFilters = [
['roads', ['kind', ['aeroway']], { stroke: 'black', strokeWidth: '1px' }],
['roads', ['landuse_kind', ['aerodrome']], { stroke: 'grey', strokeWidth: '0.75px' }],
['buildings', ['landuse_kind', ['aerodrome']], { fill: 'lightgrey', /*stroke: 'black', strokeWidth: '0.5px'*/ }],
];
Insert cell
Insert cell
function matchAndStyleFeatures(filters) {
// makes a matching function for a given set of filters as defined above
return function(layers) {
return filters.reduce((features, filter) => {
const [layer, [tag, values], style] = filter;
if (layers[layer]) {
features.push(...layers[layer].features
.filter(f => values.indexOf(f.properties[tag]) > -1) // filter by tag values
.map(f => { f.properties.style = style; return f; }) // add style info to feature
);
}
return features;
}, []);
}
}
Insert cell
matchAirports = matchAndStyleFeatures(featureFilters)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
geojson = {
// use Observable mutable values to show a status message up top while loading
mutable loadStatus = md`Loading... it can take a few seconds to find all the tiles ⏳`;
const geojson = {
type: 'FeatureCollection',
features: selectedAirport ? await getFeaturesNearTiles(startTile, startTile, matchAirports) : []
}
mutable loadStatus = md`Tada! ✨`;
return geojson;
}
Insert cell
async function getFeaturesNearTiles(minTile, maxTile, matchFunc, features = [], level = 0, maxLevel = 6) {
async function getTile([x, y, z]) {
console.log('tile', x, y, z);
const url = `https://tile.nextzen.org/tilezen/vector/v1/512/all/${z}/${x}/${y}.json?api_key=${nextzenAPIKey}`;
const layers = await fetch(url).then(r => r.json());

const matchedFeatures = matchFunc(layers);
if (matchedFeatures.length > 0) {
features.push(...matchedFeatures);
return true;
}
return false;
}
let tiles = [];
let featureCount = features.length;
const z = minTile[2];
if (minTile[0] === maxTile[0] && minTile[1] === maxTile[1]) {
tiles.push(getTile(minTile));
}
else {
for (let x = minTile[0]; x <= maxTile[0]; x++) {
tiles.push(getTile([x, minTile[1], z])); // top row
tiles.push(getTile([x, maxTile[1], z])); // bottom row
}
for (let y = minTile[1] + 1; y <= maxTile[1] - 1; y++) {
tiles.push(getTile([minTile[0], y, z])); // left column
tiles.push(getTile([maxTile[0], y, z])); // right column
}
}
await Promise.all(tiles); // wait for all tile fetches to finish
// if any new features were found, expand search one level
if (features.length > featureCount) {
minTile = [minTile[0] - 1, minTile[1] - 1, minTile[2]];
maxTile = [maxTile[0] + 1, maxTile[1] + 1, maxTile[2]];
if (level < maxLevel) {
console.log('expand', minTile, maxTile, features);
await getFeaturesNearTiles(minTile, maxTile, matchFunc, features, level + 1);
}
else {
console.log('hit max expand level');
}
}
return features;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
path = d3.geoPath(d3.geoMercator().fitSize(imageSize, geojson));
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
defaultAirport = {
const params = new URLSearchParams(location.search);
return params.get('airport') || 'JFK';
}
Insert cell
Insert cell
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