Skip to content

3D Terrain ​

Make your map look like a real landscape by enabling 3D terrain. The map reads elevation data from a special tile source (raster-dem) and pushes mountains up into 3D.

No external libraries needed. This is built directly into MapMetrics GL — no three.js, no plugins.

Drag to pan · Hold right-click and drag to tilt · Use the terrain button (mountain icon) to toggle 3D

How It Works ​

3D terrain needs two things:

  1. A raster-dem source — tiles containing elevation data for every point on the map
  2. The terrain style property — tells the map to use that elevation data to push the land surface up into 3D
javascript
const map = new mapmetricsgl.Map({
  container: 'map',
  style: {
    version: 8,
    sources: {
      // 1. Your regular base map (OSM, satellite, etc.)
      osm: {
        type: 'raster',
        tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
        tileSize: 256,
      },
      // 2. Elevation data source (free AWS terrain tiles — no API key needed)
      terrainSource: {
        type: 'raster-dem',
        tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
        tileSize: 256,
        encoding: 'terrarium',
        maxzoom: 15,
      },
    },
    layers: [
      { id: 'osm', type: 'raster', source: 'osm' },
    ],
    // 3. Enable 3D terrain
    terrain: {
      source: 'terrainSource',
      exaggeration: 1.5,
    },
  },
  pitch: 60,      // Tilt the camera to see the 3D effect
  maxPitch: 85,
});

Exaggeration ​

exaggeration controls how dramatic the 3D effect looks:

ValueEffect
0Flat map (terrain disabled)
1Real-world scale
1.5Slightly dramatic (good default)
3Very exaggerated mountains

Add a Terrain Toggle Button ​

javascript
map.addControl(new mapmetricsgl.TerrainControl({
  source: 'terrainSource',
  exaggeration: 1.5,
}), 'top-right');

This adds a mountain icon button that toggles 3D terrain on/off.

Tips ​

  • Set pitch: 60 or higher to see the 3D effect clearly
  • Set maxPitch: 85 to allow steep camera angles
  • Add sky: {} to the style for a realistic sky background
  • Mountains and valleys look best at zoom 8–14

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',
        zoom: 12,
        center: [11.39085, 47.27574],
        pitch: 70,
        maxPitch: 85,
        style: {
          version: 8,
          sources: {
            osm: {
              type: 'raster',
              tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
              tileSize: 256,
              attribution: '&copy; OpenStreetMap Contributors',
              maxzoom: 19,
            },
            terrainSource: {
              type: 'raster-dem',
              tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
              tileSize: 256,
              encoding: 'terrarium',
              maxzoom: 15,
            },
          },
          layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
          terrain: { source: 'terrainSource', exaggeration: 1.5 },
          sky: {},
        },
      });

      map.addControl(new mapmetricsgl.NavigationControl({ visualizePitch: true }), 'top-right');
      map.addControl(new mapmetricsgl.TerrainControl({ source: 'terrainSource', exaggeration: 1.5 }), 'top-right');
    </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 terrainStyle = {
  version: 8,
  sources: {
    osm: {
      type: 'raster',
      tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
      tileSize: 256,
      attribution: '&copy; OpenStreetMap Contributors',
      maxzoom: 19,
    },
    terrainSource: {
      type: 'raster-dem',
      tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
      tileSize: 256,
      encoding: 'terrarium',
      maxzoom: 15,
    },
  },
  layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
  terrain: { source: 'terrainSource', exaggeration: 1.5 },
  sky: {},
};

const Terrain3D = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);

  useEffect(() => {
    if (map.current) return;
    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      style: terrainStyle,
      center: [11.39085, 47.27574],
      zoom: 12,
      pitch: 70,
      maxPitch: 85,
    });

    map.current.addControl(new mapmetricsgl.NavigationControl({ visualizePitch: true }), 'top-right');
    map.current.addControl(new mapmetricsgl.TerrainControl({ source: 'terrainSource', exaggeration: 1.5 }), 'top-right');

    return () => { map.current?.remove(); map.current = null; };
  }, []);

  return <div ref={mapContainer} style={{ height: '100vh', width: '100%' }} />;
};

export default Terrain3D;

For more information, visit the MapMetrics GitHub repository.