Satellite Map with Terrain Elevation
Combine satellite imagery with 3D terrain elevation to get a realistic aerial view of the landscape. Mountains rise up from the satellite photo, making it feel like you're looking at a real landscape from above.
No three.js or external libraries needed. Satellite + terrain is built into MapMetrics GL.
How It Works
The setup is simple — just swap the base map tiles from OpenStreetMap to a satellite imagery provider:
javascript
const map = new mapmetricsgl.Map({
container: 'map',
pitch: 60,
maxPitch: 85,
style: {
version: 8,
sources: {
// Satellite imagery (free from ESRI)
satellite: {
type: 'raster',
tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'],
tileSize: 256,
attribution: 'Tiles © Esri',
maxzoom: 19,
},
// Elevation data (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: 'satellite', type: 'raster', source: 'satellite' },
],
// Enable 3D terrain
terrain: {
source: 'terrainSource',
exaggeration: 1.5,
},
},
});Free Satellite Tile Sources
| Provider | URL pattern | Notes |
|---|---|---|
| ESRI World Imagery | ...World_Imagery/MapServer/tile/{z}/{y}/{x} | Free, global coverage |
| OpenStreetMap | https://a.tile.openstreetmap.org/{z}/{x}/{y}.png | Standard road map |
Note: ESRI tiles use
{z}/{y}/{x}(y before x), not the standard{z}/{x}/{y}.
Add Hillshade Over Satellite
Adding a semi-transparent hillshade on top of satellite tiles gives extra depth:
javascript
{
id: 'hillshade',
type: 'hillshade',
source: 'terrainSource',
paint: {
'hillshade-shadow-color': '#000000',
'hillshade-exaggeration': 0.3, // subtle — don't overpower the satellite image
'hillshade-illumination-anchor': 'viewport',
},
}Toggle 3D vs Flat
javascript
// Switch to 3D view
map.easeTo({ pitch: 60, duration: 800 });
// Switch to flat (top-down) view
map.easeTo({ pitch: 0, duration: 800 });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: 11,
center: [11.39085, 47.27574],
pitch: 60,
maxPitch: 85,
style: {
version: 8,
sources: {
satellite: {
type: 'raster',
tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'],
tileSize: 256,
attribution: 'Tiles © Esri',
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: 'satellite', type: 'raster', source: 'satellite' }],
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 SatelliteTerrain = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
zoom: 11,
center: [11.39085, 47.27574],
pitch: 60,
maxPitch: 85,
style: {
version: 8,
sources: {
satellite: {
type: 'raster',
tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'],
tileSize: 256,
attribution: 'Tiles © Esri',
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: 'satellite', type: 'raster', source: 'satellite' }],
terrain: { source: 'terrainSource', exaggeration: 1.5 },
sky: {},
},
});
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 SatelliteTerrain;For more information, visit the MapMetrics GitHub repository.