Skip to content

3D Terrain

TerrainLayer decodes a raster DEM into a 3D mesh. The Maptoolkit elevation model is terrarium-encoded, so it needs the terrarium decoder. The Mapbox Terrain-RGB decoder shown in most deck.gl examples produces plausible but incorrect elevations rather than an error, which makes the mistake easy to miss.

See 3D Buildings and Terrain for the encoding and for what the other libraries support.

import { DeckGL } from '@deck.gl/react';
import { TerrainLayer } from '@deck.gl/geo-layers';

const terrain = new TerrainLayer({
  id: 'terrain',
  // Terrarium: height = (R * 256 + G + B / 256) - 32768
  elevationDecoder: { rScaler: 256, gScaler: 1, bScaler: 1 / 256, offset: -32768 },
  elevationData: 'https://tiles.mapterhorn.com/{z}/{x}/{y}.webp',
  maxZoom: 13,
  color: [200, 210, 220]
});

<DeckGL
  initialViewState={{ longitude: 6.86, latitude: 45.87, zoom: 11, pitch: 60, bearing: 20 }}
  controller={true}
  layers={[terrain]}
/>

Texturing the mesh

TerrainLayer takes a texture prop, but Maptoolkit.org serves vector tiles and has no raster basemap endpoint to point it at. For a textured 3D map, use the usual deck.gl arrangement instead: a MapLibre map with a -3d style as the basemap, and your deck.gl layers drawn over it. MapLibre applies the terrain and you keep the cartography.

// MapLibre handles the terrain, deck.gl handles your data on top
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://styles.maptoolkit.org/summer-3d.json',
  center: [6.86, 45.87],
  zoom: 11,
  pitch: 60
});

This example reaches the tile host directly rather than through a style, so the attribution requirements are yours to satisfy in your own interface.