Draw an Isochrone Polygon in MapLibre GL JS
This example fetches a 10-minute walking isochrone area for a point in Vienna and draws it as a filled polygon on a MapLibre GL map.
let map = new maplibregl.Map({
container: "map",
style: "https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=YOUR_API_KEY",
center: [16.372182, 48.208266],
zoom: 14,
});
map.on("load", () => {
let point = [16.372493, 48.20883];
let url = new URL("https://routing.maptoolkit.net/isochrone");
url.searchParams.append("time", 10);
url.searchParams.append("point", `${point[1]},${point[0]}`);
url.searchParams.append("routeType", "foot");
url.searchParams.append("api_key", "YOUR_API_KEY");
fetch(url)
.then((r) => r.json())
.then((polygon) => {
map.addSource("isochrone", { type: "geojson", data: { type: "Feature", geometry: { type: "Polygon", coordinates: [polygon.map((latLng) => latLng.reverse())] } } });
map.addLayer({ id: "isochrone-fill", type: "fill", source: "isochrone", paint: { "fill-color": "#2a3561", "fill-opacity": 0.2 } });
map.addLayer({ id: "isochrone-line", type: "line", source: "isochrone", layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#2a3561", "line-width": 2 } });
let $img = document.createElement("img");
$img.src = "https://static.maptoolkit.net/sprites/toursprung/marker.svg";
$img.width = 29; $img.height = 30;
new maplibregl.Marker({ element: $img, anchor: "bottom" })
.setLngLat(point).addTo(map)
.setPopup(new maplibregl.Popup({ offset: 30 }).setHTML("<p>Within a 10 minute walking distance</p>"))
.togglePopup();
});
});<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5.6.1/dist/maplibre-gl.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@5.6.1/dist/maplibre-gl.css" />
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
let map = new maplibregl.Map({
container: "map",
style: "https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=YOUR_API_KEY",
center: [16.372182, 48.208266],
zoom: 14,
});
map.on("load", () => {
let point = [16.372493, 48.20883];
let url = new URL("https://routing.maptoolkit.net/isochrone");
url.searchParams.append("time", 10);
url.searchParams.append("point", `${point[1]},${point[0]}`);
url.searchParams.append("routeType", "foot");
url.searchParams.append("api_key", "YOUR_API_KEY");
fetch(url)
.then((r) => r.json())
.then((polygon) => {
map.addSource("isochrone", { type: "geojson", data: { type: "Feature", geometry: { type: "Polygon", coordinates: [polygon.map((latLng) => latLng.reverse())] } } });
map.addLayer({ id: "isochrone-fill", type: "fill", source: "isochrone", paint: { "fill-color": "#2a3561", "fill-opacity": 0.2 } });
map.addLayer({ id: "isochrone-line", type: "line", source: "isochrone", layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#2a3561", "line-width": 2 } });
let $img = document.createElement("img");
$img.src = "https://static.maptoolkit.net/sprites/toursprung/marker.svg";
$img.width = 29; $img.height = 30;
new maplibregl.Marker({ element: $img, anchor: "bottom" })
.setLngLat(point).addTo(map)
.setPopup(new maplibregl.Popup({ offset: 30 }).setHTML("<p>Within a 10 minute walking distance</p>"))
.togglePopup();
});
});
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
time is in minutes, and 60 is the maximum: above that the service returns 400 with
Parameter 'time' must not exceed 60 minutes.
routeType decides the shape as much as the time does. A 10 minute foot isochrone is a
small blob; the same 10 minutes as car follows the road network outward and is far less
round.
The response is a bare array of [lat, lng] pairs, not GeoJSON. It has to be reversed
into [lng, lat] and wrapped in a Feature before MapLibre will take it, which is what the
polygon.map(latLng => latLng.reverse()) and the surrounding Polygon geometry are doing.
Note that reverse() mutates the array in place, so the response cannot be reused
afterwards without re-fetching.
The fill and the outline are two layers over one source. A fill layer cannot draw its own
border, so an accompanying line layer gives the edge.
Next steps
time and routeType are the two dials worth pulling. Ten minutes on foot and ten by car
are different questions, and
stacking bands at several times, shaded dark to
light, reads better than a single outline.
The step that makes an isochrone useful is usually testing your own data against it: which branches, stops or listings fall inside the shape. That is a point-in-polygon check on the response. For “how do I get there” rather than “how far can I get”, the Routing API uses the same key.