Terrain Viewer
Dev

ArcGIS LERC Elevation Protocol

lerc:// — decoding Esri's float raster codec into Terrarium tiles, and why the tile pyramid is the only anonymous way into an ArcGIS elevation service

lerc:// (lib/lerc-protocol.ts) consumes ArcGIS tiled elevation services, which serve real float rasters compressed with LERC — Esri's open, Apache-2.0 raster codec — rather than any RGB packing. It decodes a tile to metres and re-encodes it as Terrarium, so MapLibre gets an ordinary raster-dem tile and nothing downstream needs to know.

The motivating sources are Esri's two global blends:

ServiceWhat it isWhere
WorldElevation3D/TopoBathy3Dbest available, with the seafloor — one surface from summit to trenchbuilt in, alongside Mapterhorn / Mapbox / MapTiler / AWS
WorldElevation3D/Terrain3Dthe same on land, ocean flat at 0 mLibrary

The combined product is the built-in because it is the more generally useful of the two; the land-only variant is worth reaching for when a coastline should be a hard edge, since a −7000 m trench in the range wrecks a hypsometric ramp keyed to land relief.

The URL

lerc://<host+path, no scheme>/tile/{z}/{y}/{x}

ArcGIS orders the tile placeholders z/y/x, not z/x/y. That needs no special handling: MapLibre substitutes each placeholder wherever it finds it, so the service's own native ordering is simply what you paste.

Use the tile pyramid, not exportImage

This is the trap worth documenting, because the wrong endpoint appears to work.

The same ImageServer exposes exportImage, which is what the float32dem sources use elsewhere and the obvious thing to reach for. Anonymously, it only ever answers from a coarse overview — the response names its own catalog item, WorldDTM_OV256, roughly 2.5 km/px. Passing an explicit fine pixelSize does not change it.

The symptom is a raster that decodes cleanly, has plausible-looking values, and is completely wrong: a 2 km window over the Alps came back as a smooth north–south ramp from 948 m to 963 m where the real range is 577–2216 m.

/tile/{z}/{y}/{x} serves the real pyramid, keyless, with Access-Control-Allow-Origin: *.

257 × 257

ArcGIS elevation tiles are 257 × 257, not 256: one extra row and column so neighbouring tiles share an edge of vertices. MapLibre's raster-dem wants a plain square tile of its declared size, so the protocol drops the shared edge rather than resampling — it is the first cell of the next tile, and MapLibre backfills tile borders from neighbours anyway.

Holes

LERC carries a validity mask alongside the pixels (mask, or a per-band bandMasks), plus an optional noDataValue. A pixel failing any of those is written as 0 m with alpha 254 — the app-wide convention described in Derived Terrain Protocol: MapLibre ignores alpha and draws flat ground, while this app's own decoders read the cell as invalid. A transparent pixel would premultiply to the Terrain-RGB floor and dig a 10 km pit.

Beyond a service's real coverage the whole tile comes back masked, so it renders flat rather than as a crater — but declaring the source's maxzoom is still better, since MapLibre then overzooms the last real parent instead of fetching empty tiles. Both shipped entries declare 16, matching the service's own tileInfo.lods.

Datum

Measured, not assumed: six points across an Innsbruck tile average −0.6 m against a reference DEM, with an 18 m spread that is ordinary sampling noise between two models in steep ground. The values are orthometric — unlike ArcticDEM and REMA, no geoid correction is wanted.

Loading

Both the decoder (~64 kB) and its wasm (~117 kB) are pulled in on the first lerc:// tile and never before. The protocol is registered at startup like every other, but almost no session uses an ArcGIS elevation service, so a static import would put all of it in the initial bundle:

let lercReady: Promise<typeof LercDecode> | null = null
function ensureLerc() {
  if (!lercReady) {
    lercReady = import("lerc")
      .then(async (m) => { if (!m.isLoaded()) await m.load({ locateFile: () => lercWasmUrl }); return m.decode })
      .catch((e) => { lercReady = null; throw e })
  }
  return lercReady
}

lercWasmUrl comes from import "lerc/lerc-wasm.wasm?url", which Vite compiles to a string constant and emits as a hashed asset. The package's own loader looks for the wasm next to the script, which is wrong for any bundled build.

Licence. These are Esri ArcGIS services. They answer without a token and with CORS open, but Esri's Terms of Use contemplate use with ArcGIS products; consuming them from a non-Esri client is not clearly granted. The Library entries say so, and it is worth checking before relying on one in anything published.

Quantized mesh, for comparison

Cesium's quantized mesh is now supported too — see Quantized Mesh Protocol. The short version of how they differ:

lerc:// (Esri)quantized-mesh:// (Cesium ion)
Source shapea real raster grida TIN, rasterised here
Tokennonerequired; every ion asset is 401 without one
Datumorthometric (−0.6 m measured)ellipsoidal (+48.1 m measured)
Practical ceilingz16level 15

For global terrain the LERC services are the easier choice. Quantized mesh earns its place for ion-hosted private assets.

On this page