SIGN IN SIGN UP

Add 3D Tiles quantized-mesh terrain example (#4493) (#7958)

* Add example: 3D Tiles quantized-mesh terrain as a real terrain source (#4493)

Converts quantized-mesh tiles to terrain-RGB on the fly via
addProtocol (same pattern as the geotiff plugin), so the terrain
source is a normal raster-dem source - map.setTerrain(),
queryTerrainElevation(), hillshade, and plain Markers sitting on the
terrain all work with zero core changes.

The decoder (header, delta+zigzag vertices, high-water-mark indices)
is shared with the POC example; new here is barycentric resampling of
the irregular TIN onto the regular grid raster-dem expects, EPSG:4326
to Web Mercator tile-covering resolution, and a distance-based taper
where requested tiles extend past the bundled tile's coverage.

Bundles the same single real Cesium World Terrain tile as the POC
(Mont Blanc massif, zoom 9) to keep the PR light.

* Drive quantized-mesh availability from layer.json (#4493 hardening)

Replaces the hardcoded fixture tile list with the layer.json
self-description real quantized-mesh endpoints serve: tile URL
template, per-zoom available rectangle ranges, attribution. Source
bounds/minzoom/maxzoom and the fallback fade distance derive from it,
and covering-tile resolution walks availability ranges deepest-first,
stacking lower-zoom parents under patchy high-zoom coverage instead
of discarding them.

A minimal fixture layer.json describes the bundled Mont Blanc tile,
so the example still runs offline with no API key.

Assisted-By: Claude Fable 5 (claude-fable-5)

* Move quantized-mesh decode/resample off the main thread (#4493 hardening)

addProtocol handlers run on the main thread, and a tile straddling
the coverage edge costs ~200 ms of decode + resample. All CPU work
now runs in a plain DOM Worker built by stringifying the example's
own pure functions (defined once, no duplication), returning
transferable ImageBitmaps.

Also:
- honor the protocol's AbortController: an abort message stops the
  resample between row blocks instead of finishing wasted work
- bounds-check every decoder read so truncated/corrupt tiles fail
  with a clear error instead of a DataView RangeError or silently
  bogus geometry; 404s despite claimed availability degrade to the
  fallback apron instead of failing the whole Mercator tile
- cache decoded meshes with their triangle bucket index (previously
  rebuilt per requested Mercator tile) under a small LRU cap

Assisted-By: Claude Fable 5 (claude-fable-5)

* Add z10 child fixture tiles to exercise multi-zoom availability (#4493 hardening)

Bundles the four z10 children of the Mont Blanc z9 tile (~80 KB, same
Cesium World Terrain dataset), declared in the fixture layer.json, so
the availability walk, cross-zoom parent stacking, and per-zoom tile
selection run against real multi-zoom data instead of a single tile.
queryTerrainElevation at Mont Blanc improves from 5607 m to 5738 m
(exaggerated target 5769.6 m).

Assisted-By: Claude Fable 5 (claude-fable-5)

* Stream live Swiss terrain by default, with bundled offline fallback (#4493 hardening)

Pivots the example to the Valais Alps (Matterhorn, Dufourspitze, Dom,
Weisshorn, Zinalrothorn) and adds three dataset modes: live swisstopo
terrain by default (open data, no key, all of Switzerland streams
dynamically to z14), ?terrain=fixture for the bundled offline excerpt,
and ?terrain=ion&ion-token=... for Cesium World Terrain. Any live
failure falls back to the fixture automatically.

The fixture is now a z0-z9 pyramid of the Zermatt area fetched from
swisstopo open data (no API token involved anywhere), giving terrain
relief at every zoom level. Uniform top zoom avoids hillshade seams at
source-resolution boundaries.

Also: per-dataset parameterizable fetch rate limit in the worker
(maxConcurrent / minIntervalMs), a covering-walk guard against deep
availability fan-out (with coarsest-zoom fallback so small datasets
never render as flat plateau), coverage-box-authoritative resampling
(no hard terrain walls past the box), 403-as-missing tolerance, and
layer.json bounds support.

Assisted-By: Claude Fable 5 (claude-fable-5)

* Drape SWISSIMAGE imagery and the national boundary over the terrain (#4493 hardening)

Ordinary 2D raster layers drape over the 3D-Tiles-sourced terrain
automatically - one of the stated goals in #4493. SWISSIMAGE aerial
imagery textures the ground and the official swisstopo boundary layer
follows the ridge lines (both open data, no key; blank while
offline). Hillshade stays on top, kept subtle so the imagery reads.

Assisted-By: Claude Fable 5 (claude-fable-5)

* Shrink hardening example per review

Decode via @here/quantized-mesh-decoder, drop the worker offload, rate
limiter, LRU cache, ion mode, readout UI, summit markers, and imagery
overlays; keep layer.json-driven availability with the live swisstopo
dataset and the bundled fixture fallback. 829 to 355 lines, cspell
whitelist down from 3 words to 1 (swisstopo, in the endpoint URL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(example): drape an OpenStreetMap basemap over the terrain

Show that ordinary 2D layers sit on the quantized-mesh terrain: add an OSM
raster basemap under the hillshade, soften the hillshade so the draped map
reads clearly, and regenerate the thumbnail to show map-on-terrain rather
than bare relief.

* ci: retrigger (flaky windows render-test timeout in terrain/fog)

* docs(example): use the maplibre-gl-3dtiles-terrain plugin

Per review (#7958), move the inlined quantized-mesh terrain code into the
maplibre-gl-3dtiles-terrain plugin and reduce this example to a thin consumer
(load dataset -> register protocol -> addSource(sourceSpec) -> setTerrain).
Same terrain result. 373 -> 70 lines.

* Address review on the 3D Tiles terrain example

- Rely solely on the public swisstopo endpoint; drop the bundled Zermatt
  fixture tiles and the offline fallback.
- Remove the plugin-mechanics comment (it lives in the plugin README now).
- Give hillshade its own source, separate from the terrain source, to avoid
  tile-cache contention.

* Address review: drop stale .terrain gitattributes entry, more specific example title

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Rename example to match title slug (add-3d-terrain-from-quantized-mesh-tiles)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Apply suggestion from @HarelM

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Harel M <harel.mazor@gmail.com>
C
clement-igonet committed
49491068aff0f1801c942de1bdc9da5ada0297b0
Parent: a9e4a5b
Committed by GitHub <noreply@github.com> on 8/21/2026, 9:07:13 PM