Skip to content

Add deck.gl-raster as a client-side COG renderer option (deck missions only) #158

Description

@CarsonDavis

Add deck.gl-raster as a client-side COG renderer option (deck missions only)

Motivation

Today, rendering a Cloud Optimized GeoTIFF (COG) in MMGIS always goes through TiTiler: the browser asks a tile server to colorize and rescale the raster, and gets back pre-rendered image tiles. That couples the map to a running backend service, and every change to colormap or min/max means a fresh round-trip to the server for new tiles.

deck.gl-raster can render a COG directly from its file in the browser, on the GPU, with no tile server in the loop. We want to offer that as an alternative renderer for COG layers in deck.gl-based missions. The payoff is threefold:

  • Decoupling from backend services. A COG layer can render with no TiTiler dependency
  • Live parameter changes. Colormap and rescale can be adjusted on the fly in-browser, without re-requesting tiles from a server.
  • Performance on large COGs. GPU-side rendering handles large rasters client-side

A concrete near-term goal is to demo this working in a real mission context — a real COG, rendered client-side, with the existing colormap and rescale controls driving it live.

How it should work

  • COG layers keep working exactly as they do now. By default they render through TiTiler — nothing changes for existing configs.
  • A COG layer gains a new per-layer option to switch its renderer to deck.gl-raster (client-side) instead of TiTiler.
  • This option only takes effect in deck.gl-based missions. In Leaflet missions the layer falls back to the normal TiTiler rendering — the client-side renderer is simply not offered there.
  • The deck.gl-raster renderer option (and any config fields specific to it) must not appear at all in the configure UI for a Leaflet mission. A person configuring a Leaflet mission should never see the option — it's not just inert, it's absent. It only surfaces when the mission uses the deck.gl engine.
  • When a layer is set to the client-side renderer, MMGIS reads the COG directly and renders it in the browser — no tile server is contacted for that layer.
  • The existing colormap and rescale (min/max) controls drive it. A user picks a colormap and sets a min/max the same way they do today, and the client-side render reflects those choices. Changing the colormap or the min/max updates the rendering live, without a server round-trip.
  • The first version targets single-band COGs — apply a named colormap and a min/max stretch. That is the common case for the existing colormap/rescale workflow.

Done when

  • A COG layer can be configured, per layer, to use the client-side (deck.gl-raster) renderer instead of TiTiler.
  • In a deck.gl mission, a single-band COG set to the client-side renderer displays correctly with a chosen colormap and min/max — and no request is made to a tile server for that layer (verifiable in the browser network panel).
  • Changing the colormap on that layer updates the rendering live, with no tile-server request.
  • Changing the min/max (rescale) on that layer updates the rendering live, with no tile-server request.
  • Existing COG layers (no renderer option set, or set to TiTiler) render exactly as before — no visible change.
  • In a Leaflet mission, a layer flagged for the client-side renderer falls back to normal TiTiler rendering rather than breaking.
  • In the configure UI for a Leaflet mission, the deck.gl-raster renderer option (and any fields specific to it) is not shown at all — only the existing TiTiler COG options appear.
  • The behavior can be shown end-to-end with a real COG in a real mission config (demo-ready).

Out of scope

  • Multi-band / RGB composites and band math for the client-side renderer — band selection, expressions, false-color composites. Single-band colormap + rescale only for this issue; multi-band is a follow-up.
  • Changing or removing the TiTiler path. It stays as-is and remains the default.
  • Adding the client-side renderer to Leaflet missions.
  • Any new backend service work.
Draft implementation plan — written as of 42b9e0a on 2026-06-18. Rough guide; re-verify against latest code.

Current behavior

  • COG layers are configured as tile (or image) layers. COG-specific fields live in the configure UI under the COG tab: cogTransform, cogColormap, cogMin/cogMax (with currentCogMin/currentCogMax runtime overrides), cogBands, cogResampling, cogExpression, throughTileServer, tileMatrixSet. Schema: configure/src/metaconfigs/layer-tile-config.json (COG tab) and layer-image-config.json.
  • COG params are merged into a tile URL by applyCogFieldsToUrl(url, layerObj) in src/essence/Basics/Layers_/cogUrlUtils.ts — this produces the TiTiler query params (colormap_name, rescale, bidx/expression, resampling). It's shared by both the Leaflet middleware path and the deck.gl static-URL path.
  • TiTiler base URL resolution: src/essence/Tools/LayerManager/lib/utils/titiler.ts (getTiTilerBaseUrl/getTiTilerUrl), backed by src/essence/Basics/ServiceUrls/ServiceUrls.js.
  • Engine split: Map_.engine.engineType is MAP_ENGINE.DECKGL or MAP_ENGINE.LEAFLET, chosen from L_.configData.msv.mapEngine in src/essence/Basics/Map_/Map_.js. Tile layers are built in makeTileLayer() in Map_.js; the deck branch there detects a COG and calls applyCogFieldsToUrl() then buildDeckLayer(...) (a TileLayer + BitmapLayer). The deck layer factory lives in src/essence/Basics/MapEngines/Adapters/DeckGLHelpers.ts; the adapter is DeckGLAdapter.ts.
  • Colormap/rescale UI: src/essence/Tools/LayerManager/adapters/handlers.tssetColormap() and setRescale() write the current* overrides and trigger a layer refresh, which re-runs the layer build. Colormap names are shared between TiTiler (colormap_name) and the client-side js-colormaps definitions (configure/src/external/js-colormaps.js); options list in src/essence/Tools/LayerManager/lib/utils/constants.ts.

Where the change lands & rough plan

  1. Config schema + visibility gating — add a per-layer renderer option (e.g. a cogRendererMode dropdown: titiler default vs deck-raster) to the COG tab in configure/src/metaconfigs/layer-tile-config.json. This field (and any deck-raster-specific fields) must be hidden in the configure UI when the mission's map engine is Leaflet — not merely ignored at render time. Check how the configure page reads the mission's msv.mapEngine and whether the metaconfig/Maker field schema already supports a conditional-visibility / capability mechanism to gate fields on engine; if one exists, reuse it, otherwise that gating is part of this work.
  2. Layer build routing — in makeTileLayer() (deck branch) in Map_.js, when the engine is DECKGL and the layer's renderer mode is deck-raster, route to a new client-side builder instead of the TiTiler applyCogFieldsToUrl() + buildDeckLayer() path. When not DECKGL, or mode is titiler/unset, keep the existing path unchanged (this gives the Leaflet fallback for free).
  3. Client-side raster layer builder — add a buildDeckRasterLayer() (alongside buildDeckLayer()) in DeckGLHelpers.ts that reads the COG directly and renders single-band data with a colormap + min/max stretch on the GPU, using deck.gl-raster. Map the existing fields onto its inputs: cogColormap/currentCogColormap → colormap, cogMin/cogMax + currentCogMin/currentCogMax → rescale stretch. Reuse the shared js-colormaps definitions so client-side colors match the TiTiler colormap names.
  4. Live updates — the existing refresh path (setColormap/setRescale → layer rebuild) should already re-invoke the builder with updated current* values. Confirm the deck-raster layer updates in place (or rebuilds cheaply) on colormap/rescale change without a server request. Prefer updating layer props over a full refetch of the COG so the "live" interaction is genuinely instant.
  5. Dependency — deck.gl-raster (and likely a client-side GeoTIFF loader, e.g. geotiff.js / loaders.gl) becomes a new frontend dependency. Verify the version's module API for single-band colormap + linear rescale before wiring (the project was rewritten across major versions — v0.7.0 is current as of May 2026).

⚠️ Gotcha: TiTiler colormap names and the client-side js-colormaps definitions are intended to match, but verify they actually produce the same colors (including the _r reversed variants) — otherwise the same layer looks different depending on renderer, which undermines the "swap the renderer, same result" story. Also confirm CORS on the COG source: reading the file directly in-browser needs the COG host to allow cross-origin range requests, which the TiTiler path hides today.

References

  • src/essence/Basics/Map_/Map_.jsmakeTileLayer() (deck branch, COG detection)
  • src/essence/Basics/MapEngines/Adapters/DeckGLHelpers.tsbuildDeckLayer() (pattern for a new buildDeckRasterLayer())
  • src/essence/Basics/Layers_/cogUrlUtils.tsapplyCogFieldsToUrl() (how COG fields map to render params today)
  • src/essence/Tools/LayerManager/adapters/handlers.tssetColormap()/setRescale() refresh path
  • configure/src/metaconfigs/layer-tile-config.json — COG tab schema
  • src/essence/Tools/LayerManager/lib/utils/constants.ts + configure/src/external/js-colormaps.js — colormap names/definitions

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions