Add Contour Lines ​
Contour lines are lines that connect points of the same elevation — like the rings you see on a topographic map. They show you how steep or flat the terrain is without needing 3D.
No three.js or external libraries needed. Contour lines are drawn using a standard GeoJSON
linelayer.
Contour lines with elevation labels — thin lines every 100m, thick lines every 200m
How Contour Lines Work ​
Contour lines are just line features in a GeoJSON source. Each line has an elevation property. You add two layers:
- Minor lines (thin) — every 100m interval
- Major lines (thick) — every 200m or 500m interval
javascript
map.addSource('contours', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { elevation: 500 },
geometry: {
type: 'LineString',
coordinates: [[lng1, lat1], [lng2, lat2], ...]
}
},
// more contour lines...
]
}
});
// Thin lines for all contours
map.addLayer({
id: 'contour-lines',
type: 'line',
source: 'contours',
paint: {
'line-color': '#8B4513',
'line-width': 0.8,
'line-opacity': 0.6,
}
});
// Thick lines for major intervals (every 200m)
map.addLayer({
id: 'contour-major',
type: 'line',
source: 'contours',
filter: ['==', ['%', ['get', 'elevation'], 200], 0],
paint: {
'line-color': '#5c3317',
'line-width': 2,
}
});Add Elevation Labels ​
Place labels along the contour lines using symbol-placement: 'line':
javascript
map.addLayer({
id: 'contour-labels',
type: 'symbol',
source: 'contours',
layout: {
'text-field': ['concat', ['to-string', ['get', 'elevation']], 'm'],
'symbol-placement': 'line', // follow the line path
'text-size': 11,
},
paint: {
'text-color': '#5c3317',
'text-halo-color': '#fff',
'text-halo-width': 1.5,
}
});Real Contour Data Sources ​
In production, use a real contour data source:
- MapTiler Contours — vector tiles with elevation data
- OpenTopoData API — free elevation API
- Mapbox Terrain —
contoursource layer in Mapbox terrain tiles
Complete Example ​
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
<style>body { margin: 0; } #map { height: 100vh; width: 100%; }</style>
</head>
<body>
<div id="map"></div>
<script>
const map = new mapmetricsgl.Map({
container: 'map',
style: '<StyleFile_URL_with_Token>',
center: [11.39085, 47.27574],
zoom: 11,
});
map.on('load', () => {
map.addSource('contours', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{ type: 'Feature', properties: { elevation: 800 }, geometry: { type: 'LineString', coordinates: [[11.36,47.26],[11.38,47.275],[11.42,47.26],[11.38,47.248],[11.36,47.26]] } },
{ type: 'Feature', properties: { elevation: 1000 }, geometry: { type: 'LineString', coordinates: [[11.375,47.268],[11.390,47.272],[11.40,47.256],[11.375,47.258],[11.375,47.268]] } },
]
}
});
map.addLayer({
id: 'contours', type: 'line', source: 'contours',
paint: { 'line-color': '#8B4513', 'line-width': 1, 'line-opacity': 0.7 }
});
map.addLayer({
id: 'contour-labels', type: 'symbol', source: 'contours',
layout: {
'text-field': ['concat', ['to-string', ['get', 'elevation']], 'm'],
'symbol-placement': 'line',
'text-size': 11,
'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
},
paint: { 'text-color': '#5c3317', 'text-halo-color': '#fff', 'text-halo-width': 1.5 }
});
});
</script>
</body>
</html>jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const contourData = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', properties: { elevation: 800 }, geometry: { type: 'LineString', coordinates: [[11.36,47.26],[11.38,47.275],[11.42,47.26],[11.38,47.248],[11.36,47.26]] } },
{ type: 'Feature', properties: { elevation: 1000 }, geometry: { type: 'LineString', coordinates: [[11.375,47.268],[11.390,47.272],[11.40,47.256],[11.375,47.258],[11.375,47.268]] } },
]
};
const ContourLines = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: '<StyleFile_URL_with_Token>',
center: [11.39085, 47.27574],
zoom: 11,
});
map.current.on('load', () => {
map.current.addSource('contours', { type: 'geojson', data: contourData });
map.current.addLayer({
id: 'contours', type: 'line', source: 'contours',
paint: { 'line-color': '#8B4513', 'line-width': 1, 'line-opacity': 0.7 }
});
map.current.addLayer({
id: 'contour-labels', type: 'symbol', source: 'contours',
layout: {
'text-field': ['concat', ['to-string', ['get', 'elevation']], 'm'],
'symbol-placement': 'line',
'text-size': 11,
'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
},
paint: { 'text-color': '#5c3317', 'text-halo-color': '#fff', 'text-halo-width': 1.5 }
});
});
return () => { map.current?.remove(); map.current = null; };
}, []);
return <div ref={mapContainer} style={{ height: '100vh', width: '100%' }} />;
};
export default ContourLines;For more information, visit the MapMetrics GitHub repository.