Skip to content

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 line layer.

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 — contour source 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.