Use a Cesium-style 3D Tiles quantized-mesh terrain dataset (swisstopo,
Cesium World Terrain, …) as ordinary terrain in MapLibre GL JS — with
map.setTerrain(), queryTerrainElevation(), hillshade and 2D-layer draping,
and no changes to MapLibre core.
MapLibre's terrain pipeline consumes a regular-grid heightmap (raster-dem);
quantized-mesh tiles are irregular TINs. This plugin resamples each requested
Web-Mercator tile from the TIN onto a regular grid, terrarium-packs it, and
serves it through maplibregl.addProtocol, so from MapLibre's side it is just a
raster-dem source.
npm install maplibre-gl-3dtiles-terrainPlain ESM, no build step. maplibre-gl is a peer dependency. You provide a
quantized-mesh decoder (e.g. @here/quantized-mesh-decoder) — it is
injected so this package stays small and you control its version.
import * as maplibregl from 'maplibre-gl';
import decode from '@here/quantized-mesh-decoder';
import {loadQuantizedMeshDataset, registerQuantizedMeshTerrain}
from 'maplibre-gl-3dtiles-terrain';
// 1. describe the dataset from its layer.json
const dataset = await loadQuantizedMeshDataset(
'https://3d.geo.admin.ch/ch.swisstopo.terrain.3d/v1/layer.json',
{
attribution: 'Terrain: © swisstopo',
// swisstopo's layer.json omits `available`; synthesize it over these
// bounds up to maxZoom (tiles the server actually lacks degrade to a
// fallback apron).
boundsOverride: {west: 5.6, south: 45.5, east: 11.0, north: 48.2},
maxZoom: 14
}
);
// 2. register the protocol and get a ready-made raster-dem source spec
const {sourceSpec} = registerQuantizedMeshTerrain(maplibregl, {dataset, decode});
// 3. use it like any terrain
map.on('load', () => {
map.addSource('qm-terrain', sourceSpec);
map.setTerrain({source: 'qm-terrain', exaggeration: 1.2});
map.addLayer({id: 'hillshade', type: 'hillshade', source: 'qm-terrain'});
});Fetches the dataset's layer.json and returns
{available, bounds, minZoom, maxZoom, fadeDistanceDeg, attribution, tileUrl(z,x,y)}.
Options:
boundsOverride{west,south,east,north}— synthesize availability for endpoints that omit theavailablearray.maxZoom— cap synthesized availability.attribution— fallback iflayer.jsonomits it.
Registers the addProtocol handler and returns a raster-dem sourceSpec
(encoding: 'terrarium', tiles: ['<protocol>://{z}/{x}/{y}'], plus
minzoom/maxzoom/bounds/attribution). Call unregister() to remove it.
Options:
dataset— fromloadQuantizedMeshDataset.decode(buffer) => {header:{minHeight,maxHeight}, vertexData, triangleIndices}— the quantized-mesh decoder.protocol— default'quantized-mesh'.tileSize— default256.fallbackHeight— default1500; apron height outside coverage.
- Quantized-mesh uses the EPSG:4326 TMS grid (2 tiles wide at zoom 0); the plugin handles the reprojection to Web Mercator internally.
- Datasets requiring an access token (e.g. Cesium World Terrain via Cesium ion)
work by building the
tileUrlwith the token; the keyless swisstopo endpoint needs none. - Resampling runs on the main thread. A worker transferring raw
ArrayBuffers is a possible future optimization.
BSD-3-Clause.