Skip to content

3D Terrain in Maptoolkit Maps JS

Maptoolkit Maps JS supports true 3D terrain rendering by combining a raster-dem elevation source with the map’s terrain option. This example shows how to enable 3D terrain with pitch and a sky layer to create a realistic mountain landscape view. Use this technique to build hiking apps, route planners, or any visualization where actual elevation matters more than a flat hillshade.

const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        zoom: 12,
        center: [11.39085, 47.27574],
        pitch: 70,
        maxZoom: 18,
        maxPitch: 85,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('terrain-rgb', {
            type: 'raster-dem',
            tiles: [`https://tiles.maptoolkit.net/terrain/{z}/{x}/{y}.webp?api_key=${API_KEY}`],
            tileSize: 256,
            minzoom: 5,
            maxzoom: 12,
            encoding: 'terrarium'
        });

        map.setTerrain({ source: 'terrain-rgb', exaggeration: 1.0 });

        // Force one render after the DEM source loads so terrain shows on initial load
        map.once('idle', () => map.triggerRepaint());

        map.setSky({
            'sky-color': '#199EF3',
            'sky-horizon-blend': 0.5,
            'horizon-color': '#ffffff',
            'horizon-fog-blend': 0.5,
            'fog-color': '#0000ff',
            'fog-ground-blend': 0.9
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>3D Terrain - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Go beyond hillshade and show elevation in actual 3D." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.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>
    const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        zoom: 12,
        center: [11.39085, 47.27574],
        pitch: 70,
        maxZoom: 18,
        maxPitch: 85,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('terrain-rgb', {
            type: 'raster-dem',
            tiles: [`https://tiles.maptoolkit.net/terrain/{z}/{x}/{y}.webp?api_key=${API_KEY}`],
            tileSize: 256,
            minzoom: 5,
            maxzoom: 12,
            encoding: 'terrarium'
        });

        map.setTerrain({ source: 'terrain-rgb', exaggeration: 1.0 });

        // Force one render after the DEM source loads so terrain shows on initial load
        map.once('idle', () => map.triggerRepaint());

        map.setSky({
            'sky-color': '#199EF3',
            'sky-horizon-blend': 0.5,
            'horizon-color': '#ffffff',
            'horizon-fog-blend': 0.5,
            'fog-color': '#0000ff',
            'fog-ground-blend': 0.9
        });
    });
</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.

Use the Maptoolkit Connector. Create an interactive map with zoom level 12, centered around [11.39085, 47.27574], with pitch 70. Enable 3D terrain with a sky effect.

How it works

Terrain is two separate things: a source that carries elevation, and a camera angle that makes it visible.

The source is raster-dem, not raster. A raster source would paint the tiles as a picture; raster-dem tells the renderer to read each pixel as a height value and build a mesh from it.

encoding: 'terrarium' is required. The tiles are Terrarium-encoded even though the path says terrain, and the wrong encoding does not error: it decodes the same pixels with the wrong formula and produces plausible but incorrect elevation.

setTerrain runs inside map.on('load') because it attaches to the loaded style. Calling it at construction time throws.

pitch: 70 is what makes the relief visible, and maxPitch: 85 raises the ceiling above the default so a user can tilt further toward the horizon. Looking straight down hides the effect completely.

minzoom: 5 and maxzoom: 12 describe the tileset rather than a preference. Zoom 12 is its maximum, so the renderer overzooms past that point instead of fetching more detail.

Next steps

Tuning comes first. exaggeration on setTerrain scales the relief, and above 1 it flatters the landscape rather than describing it, which matters if anyone plans from the map.

Terrain then works as a stage for something else. A route drawn over tilted terrain is much easier to read than the same route flat, and the Routing API returns that geometry while the Elevation API turns it into a climb profile. Giving the user a toggle rather than starting tilted is usually right, since terrain is the most expensive thing on the page.