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:
- A
raster-demsource — tiles containing elevation data for every point on the map - The
terrainstyle 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',
// 1. Your regular base map — here a MapMetrics vector style
style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE.json&token=YOUR_TOKEN',
center: [11.39085, 47.27574],
zoom: 12,
pitch: 70, // Tilt the camera to see the 3D effect
maxPitch: 85,
});
// 2. Wait for the vector style to finish loading, then add elevation
map.on('load', () => {
// Elevation data source (free AWS terrain tiles — no API key needed)
map.addSource('terrainSource', {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
tileSize: 256,
encoding: 'terrarium',
maxzoom: 15,
});
// 3. Enable 3D terrain
map.setTerrain({ source: 'terrainSource', exaggeration: 1.5 });
});Exaggeration ​
exaggeration controls how dramatic the 3D effect looks:
| Value | Effect |
|---|---|
0 | Flat map (terrain disabled) |
1 | Real-world scale |
1.5 | Slightly dramatic (good default) |
3 | Very 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: 70or higher to see the 3D effect clearly - Set
maxPitch: 85to allow steep camera angles - Add a
skylayer for a realistic atmospheric 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,
// Replace with your own MapMetrics style URL + token
style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE.json&token=YOUR_TOKEN',
});
map.on('load', () => {
map.addSource('terrainSource', {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
tileSize: 256,
encoding: 'terrarium',
maxzoom: 15,
});
map.setTerrain({ source: 'terrainSource', exaggeration: 1.5 });
map.addLayer({ id: 'sky', type: 'sky', paint: { 'sky-type': 'atmosphere' } });
});
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';
// Replace with your own MapMetrics style URL + token
const STYLE_URL = 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE.json&token=YOUR_TOKEN';
const Terrain3D = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: STYLE_URL,
center: [11.39085, 47.27574],
zoom: 12,
pitch: 70,
maxPitch: 85,
});
map.current.on('load', () => {
map.current.addSource('terrainSource', {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
tileSize: 256,
encoding: 'terrarium',
maxzoom: 15,
});
map.current.setTerrain({ source: 'terrainSource', exaggeration: 1.5 });
map.current.addLayer({ id: 'sky', type: 'sky', paint: { 'sky-type': 'atmosphere' } });
});
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.