Mapbox-GL Statistics Context
Quantify the reachability - see how many people are within 45 minutes on foot. Drag the marker to see how the reachable population changes
<!DOCTYPE html>
<html>
<head>
<!-- Include mapboxgl javascript and css -->
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/mapbox-gl.js"></script>
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/mapbox-gl.css" rel="stylesheet">
<!-- Include targomo core -->
<script src="https://releases.targomo.com/core/latest.min.js"></script>
<!-- Include micro progress bar -->
<script src="https://www.targomo.com/developers/scripts/mipb.min.js"></script>
<style>
body, html {
margin: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
position: relative;
}
.population {
position: absolute; padding: 8px;
top: 10px; left: 10px;
font-family: Helvetica, Arial, Sans-Serif;
background: white; border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<!-- where the map will live -->
<div id="map"></div>
<div class="population"><span>Reachable Population:</span> <span id="total-pop"></span></div>
<script>
// create targomo client
const client = new tgm.TargomoClient('westcentraleurope', '__targomo_key_here__');
// Coordinates to center the map
const lnglat = [2.34657, 48.8493];
const attributionText = `<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>`;
// set the progress bar
const pBar = new mipb({ fg: "#FF8319" });
// add the map and set the initial center to paris
const map = new mapboxgl.Map({
container: 'map',
style: client.basemaps.getGLStyleURL("Light"),
minZoom: 9, zoom: 12, center: lnglat, attributionControl: false
}).addControl(new mapboxgl.NavigationControl()).addControl(new mapboxgl.AttributionControl(
{ compact: true, customAttribution: attributionText }
));
// disable scroll zoom
map.scrollZoom.disable();
const marker = new mapboxgl.Marker({
draggable: true
}).setLngLat(lnglat).addTo(map);
marker.on('dragend', getStats);
const statsConfig = {
maxEdgeWeight: 2700, travelType: "Walk", // 45 minutes on foot
statisticsGroup: 123, statistics: [{ id: 0 }] // France INSEE on hexagons, total population
}
async function getStats() {
// show progress bar
pBar.show();
const sources = [{ ...marker.getLngLat(), id: "Marker1" }];
// get the results as well as an id:travelTime map
const [statisticsResults, statisticsTimes] = await Promise.all([
client.statistics.dependent(sources, statsConfig),
client.statistics.travelTimes(sources, statsConfig)
]);
const colors = [
'rgb(26,152,80)', 'rgb(145,207,96)', 'rgb(217,239,139)',
'rgb(254,224,139)', 'rgb(252,141,89)', 'rgb(215,48,39)'
];
const colorExpression = ['match', ['get', 'id']];
const opacityExpression = ['match', ['get', 'id']];
// Calculate color values for each country based on 'id' value
for (const [id, time] of Object.entries(statisticsTimes)) {
// Determine which color bucket the cell's value lies within
const colorIndex = Math.ceil((time/(statsConfig.maxEdgeWeight/colors.length)))-1;
const color = colors[colorIndex];
// update the expressions
colorExpression.push(+id, color);
opacityExpression.push(+id, .5);
}
// Last value is the default, used where there is no data
colorExpression.push('#666');
opacityExpression.push(0.2);
// update the layer's style
map.setPaintProperty('statistics', 'fill-color', colorExpression);
map.setPaintProperty('statistics', 'fill-opacity', opacityExpression);
// tally full reachable population
const reachablePop = Object.values(
statisticsResults.raw.statistics[statsConfig.statistics[0].id]
).reduce((acc, cur) => { return acc+cur }, 0);
// update the
var popElem = document.querySelector('#total-pop');
popElem.innerHTML = Math.round(reachablePop).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
// hide progress bar
pBar.hide()
map.on("click", "statistics", (e) => {
const travelTime = Math.round(statisticsTimes[e.features[0].properties.id] / 60);
const description = `Population: ${Math.round(e.features[0].properties[statsConfig.statistics[0].id])}
<br>Bike time: ${travelTime ? travelTime : 'not reachable'}`;
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(description).addTo(map);
});
}
map.on('load', async () => {
// get metadata for statistics group 123 (France INSEE on hexagons)
// to see the available statistics, as well as details of the dataset
const meta = await client.statistics.metadata(statsConfig.statisticsGroup);
// get the vectortiles url
const tileRoute = await client.statistics.tileRoute(statsConfig.statisticsGroup, [statsConfig.statistics[0]]);
// add the tiles to the map
map.addLayer({
"id": "statistics",
"type": "fill",
"source": {
"type": "vector",
"tiles": [tileRoute],
"minzoom": meta.min_zoom
},
"source-layer": `${statsConfig.statisticsGroup}`, // source-layer is the statistic group id
"layout": {},
"paint": {
"fill-opacity": 0.2,
"fill-color": "#666",
"fill-outline-color": "rgba(50, 50, 50, 0.1)"
}
}, "place_other");
// Change the cursor to a pointer when the mouse is over the stats layer
map.on("mouseenter", "statistics", () => {
map.getCanvas().style.cursor = "pointer";
});
// Change it back to a pointer when it leaves.
map.on("mouseleave", "statistics", () => {
map.getCanvas().style.cursor = "";
});
getStats();
})
</script>
</body>
</html>
Copied to clipboard