Skip to content

Add Contour Lines

Add contour lines to your map from a raster-dem source.

const demSource = new mlcontour.DemSource({
    url: 'https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png',
    encoding: 'mapbox',
    maxzoom: 12,
    // offload contour line computation to a web worker
    worker: true
});

// calls maplibregl.addProtocol to register a dynamic vector tile provider that
// downloads raster-dem tiles, computes contour lines, and encodes as a vector
// tile for each tile request from maplibre
demSource.setupMaplibre(maplibregl);

const map = new maplibregl.Map({
    container: 'map',
    zoom: 13,
    center: [11.3229, 47.2738],
    hash: true,
    style: {
        version: 8,
        glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',
        sources: {
            hillshadeSource: {
                type: 'raster-dem',
                // share cached raster-dem tiles with the contour source
                tiles: [demSource.sharedDemProtocolUrl],
                tileSize: 512,
                maxzoom: 12
            },
            contourSourceFeet: {
                type: 'vector',
                tiles: [
                    demSource.contourProtocolUrl({
                    // meters to feet
                        multiplier: 3.28084,
                        overzoom: 1,
                        thresholds: {
                        // zoom: [minor, major]
                            11: [200, 1000],
                            12: [100, 500],
                            13: [100, 500],
                            14: [50, 200],
                            15: [20, 100]
                        },
                        elevationKey: 'ele',
                        levelKey: 'level',
                        contourLayer: 'contours'
                    })
                ],
                maxzoom: 15
            }
        },
        layers: [
            {
                id: 'hills',
                type: 'hillshade',
                source: 'hillshadeSource',
                layout: {visibility: 'visible'},
                paint: {'hillshade-exaggeration': 0.25}
            },
            {
                id: 'contours',
                type: 'line',
                source: 'contourSourceFeet',
                'source-layer': 'contours',
                paint: {
                    'line-opacity': 0.5,
                    // "major" contours have level=1, "minor" have level=0
                    'line-width': ['match', ['get', 'level'], 1, 1, 0.5]
                }
            },
            {
                id: 'contour-text',
                type: 'symbol',
                source: 'contourSourceFeet',
                'source-layer': 'contours',
                filter: ['>', ['get', 'level'], 0],
                paint: {
                    'text-halo-color': 'white',
                    'text-halo-width': 1
                },
                layout: {
                    'symbol-placement': 'line',
                    'text-size': 10,
                    'text-field': [
                        'concat',
                        ['number-format', ['get', 'ele'], {}],
                        '\''
                    ],
                    'text-font': ['Noto Sans Bold']
                }
            }
        ]
    }
});
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Add Contour Lines</title>
    <meta property="og:description" content="Add contour lines to your map from a raster-dem source." />
    <meta property="og:created" content="2025-06-25" />
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel='stylesheet' href='https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css' />
    <script src='https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js'></script>
    <style>
        body { margin: 0; padding: 0; }
        html, body, #map { height: 100%; }
    </style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/maplibre-contour@0.0.5/dist/index.min.js"></script>
<script>
    const demSource = new mlcontour.DemSource({
        url: 'https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png',
        encoding: 'mapbox',
        maxzoom: 12,
        // offload contour line computation to a web worker
        worker: true
    });

    // calls maplibregl.addProtocol to register a dynamic vector tile provider that
    // downloads raster-dem tiles, computes contour lines, and encodes as a vector
    // tile for each tile request from maplibre
    demSource.setupMaplibre(maplibregl);

    const map = new maplibregl.Map({
        container: 'map',
        zoom: 13,
        center: [11.3229, 47.2738],
        hash: true,
        style: {
            version: 8,
            glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',
            sources: {
                hillshadeSource: {
                    type: 'raster-dem',
                    // share cached raster-dem tiles with the contour source
                    tiles: [demSource.sharedDemProtocolUrl],
                    tileSize: 512,
                    maxzoom: 12
                },
                contourSourceFeet: {
                    type: 'vector',
                    tiles: [
                        demSource.contourProtocolUrl({
                        // meters to feet
                            multiplier: 3.28084,
                            overzoom: 1,
                            thresholds: {
                            // zoom: [minor, major]
                                11: [200, 1000],
                                12: [100, 500],
                                13: [100, 500],
                                14: [50, 200],
                                15: [20, 100]
                            },
                            elevationKey: 'ele',
                            levelKey: 'level',
                            contourLayer: 'contours'
                        })
                    ],
                    maxzoom: 15
                }
            },
            layers: [
                {
                    id: 'hills',
                    type: 'hillshade',
                    source: 'hillshadeSource',
                    layout: {visibility: 'visible'},
                    paint: {'hillshade-exaggeration': 0.25}
                },
                {
                    id: 'contours',
                    type: 'line',
                    source: 'contourSourceFeet',
                    'source-layer': 'contours',
                    paint: {
                        'line-opacity': 0.5,
                        // "major" contours have level=1, "minor" have level=0
                        'line-width': ['match', ['get', 'level'], 1, 1, 0.5]
                    }
                },
                {
                    id: 'contour-text',
                    type: 'symbol',
                    source: 'contourSourceFeet',
                    'source-layer': 'contours',
                    filter: ['>', ['get', 'level'], 0],
                    paint: {
                        'text-halo-color': 'white',
                        'text-halo-width': 1
                    },
                    layout: {
                        'symbol-placement': 'line',
                        'text-size': 10,
                        'text-field': [
                            'concat',
                            ['number-format', ['get', 'ele'], {}],
                            '\''
                        ],
                        'text-font': ['Noto Sans Bold']
                    }
                }
            ]
        }
    });
</script>
</body>
</html>