Show an Elevation Profile in MapLibre GL JS
This example shows a route on a MapLibre GL map with an interactive elevation profile. Hovering over the profile highlights the corresponding segment on the map and shows a position marker.
MTK.apiKey = "YOUR_API_KEY";
const polyline = [
[11.458077, 47.259163], [11.458337, 47.258754], [11.457979, 47.258611],
[11.457556, 47.258567], [11.456872, 47.258058], [11.456416, 47.257937],
[11.456319, 47.257837], [11.456384, 47.257638], [11.455895, 47.257362],
[11.455407, 47.257307], [11.454625, 47.257318], [11.454235, 47.257196],
[11.454121, 47.256544], [11.454235, 47.255826], [11.454511, 47.255473],
[11.454544, 47.255285], [11.454544, 47.255152], [11.454886, 47.254953],
[11.455097, 47.254876], [11.455097, 47.254843], [11.454495, 47.254953],
[11.454153, 47.254169], [11.452851, 47.253240], [11.450978, 47.252898],
[11.450571, 47.252666], [11.450083, 47.252279], [11.449057, 47.252012],
[11.448096, 47.251890], [11.447429, 47.251503],
];
const map = new maplibregl.Map({
container: "map",
style: "https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=YOUR_API_KEY",
center: [11.452883, 47.255333],
zoom: 14,
});
map.once("load", () => {
map.addLayer({ id: "route", type: "line", source: { type: "geojson", data: { type: "Feature", geometry: { type: "LineString", coordinates: polyline } } }, layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#2a3561", "line-width": 5 } });
map.addLayer({ id: "highlight", type: "line", source: { type: "geojson", data: { type: "Feature", geometry: { type: "LineString", coordinates: [] } } }, layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#fa3f38", "line-width": 5 } });
const img = document.createElement("img");
img.src = "https://static.maptoolkit.net/sprites/toursprung/marker.svg";
img.width = 29; img.height = 30;
const marker = new maplibregl.Marker({ element: img, anchor: "bottom" }).setLngLat([0, 0]);
const elevation = new MTK.ElevationProfile().addTo("profile").setLngLats(polyline);
elevation.on("mousemove", (ev) => { !marker._map && marker.addTo(map); marker.setLngLat(ev.lngLat); });
elevation.on("mouseout", () => { marker._map && marker.remove(); });
elevation.on("highlight", (ev) => { map.getSource("highlight").setData({ type: "Feature", geometry: { type: "LineString", coordinates: ev.lngLats } }); });
});<!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" />
<link rel="stylesheet" href="https://static.maptoolkit.net/mtk/apis/v1.1/elevation.css" />
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
#profile { position: absolute; bottom: 0; right: 0; width: 500px; margin: 0 10px 30px 0; z-index: 999; }
#profile .mtk-elevation-profile { background: #fff; box-shadow: 0 0 15px #68686880; border-radius: 6px; }
#profile .mtk-elevation-curve-stroke { stroke: #303f7e; stroke-width: 1rem; stroke-opacity: 1; }
#profile .mtk-elevation-curve-fill { fill: #303f7e; }
#profile .mtk-elevation-curve-bar { stroke: #fa3f38; }
#profile .mtk-elevation-section { fill: rgba(250, 63, 56, 0.3); }
</style>
</head>
<body>
<div id="map"></div>
<div id="profile"></div>
<script src="https://static.maptoolkit.net/mtk/apis/v1.1/elevation.js?api_key=YOUR_API_KEY"></script>
<script>
MTK.apiKey = "YOUR_API_KEY";
const polyline = [
[11.458077, 47.259163], [11.458337, 47.258754], [11.457979, 47.258611],
[11.457556, 47.258567], [11.456872, 47.258058], [11.456416, 47.257937],
[11.456319, 47.257837], [11.456384, 47.257638], [11.455895, 47.257362],
[11.455407, 47.257307], [11.454625, 47.257318], [11.454235, 47.257196],
[11.454121, 47.256544], [11.454235, 47.255826], [11.454511, 47.255473],
[11.454544, 47.255285], [11.454544, 47.255152], [11.454886, 47.254953],
[11.455097, 47.254876], [11.455097, 47.254843], [11.454495, 47.254953],
[11.454153, 47.254169], [11.452851, 47.253240], [11.450978, 47.252898],
[11.450571, 47.252666], [11.450083, 47.252279], [11.449057, 47.252012],
[11.448096, 47.251890], [11.447429, 47.251503],
];
const map = new maplibregl.Map({
container: "map",
style: "https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=YOUR_API_KEY",
center: [11.452883, 47.255333],
zoom: 14,
});
map.once("load", () => {
map.addLayer({ id: "route", type: "line", source: { type: "geojson", data: { type: "Feature", geometry: { type: "LineString", coordinates: polyline } } }, layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#2a3561", "line-width": 5 } });
map.addLayer({ id: "highlight", type: "line", source: { type: "geojson", data: { type: "Feature", geometry: { type: "LineString", coordinates: [] } } }, layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#fa3f38", "line-width": 5 } });
const img = document.createElement("img");
img.src = "https://static.maptoolkit.net/sprites/toursprung/marker.svg";
img.width = 29; img.height = 30;
const marker = new maplibregl.Marker({ element: img, anchor: "bottom" }).setLngLat([0, 0]);
const elevation = new MTK.ElevationProfile().addTo("profile").setLngLats(polyline);
elevation.on("mousemove", (ev) => { !marker._map && marker.addTo(map); marker.setLngLat(ev.lngLat); });
elevation.on("mouseout", () => { marker._map && marker.remove(); });
elevation.on("highlight", (ev) => { map.getSource("highlight").setData({ type: "Feature", geometry: { type: "LineString", coordinates: ev.lngLats } }); });
});
</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
This example loads two Maptoolkit libraries, and they authorize separately. The map style
carries api_key in its URL, while elevation.js reads the global MTK.apiKey. Set one and
not the other and either the map or the profile comes back empty.
MTK.ElevationProfile fetches elevations for the coordinates you give it and draws the
chart, then emits events you wire to the map: mousemove for the cursor position,
highlight for a selected stretch, mouseout when the cursor leaves.
Coordinates here are [lng, lat], matching MapLibre. The Leaflet version of this example
uses [lat, lng] for the same route, which is worth noting if you copy between them.
The route is a fixed coordinate array. In a real application it would come from the Routing API, whose encoded polyline you decode first.
Next steps
The line here is fixed, so the first step is feeding the profile a route the user chose. The Routing API returns it, and the pairing is the backbone of most outdoor applications: pick a route, see the climb.
Total ascent, steepest section and metres gained per kilometre all fall out of the elevation array without another request, and the Elevation API reference covers the single-point and batch shapes for anything else. Showing relief under the route makes the profile easier to trust, which is what Terrain Tiles are for.