Skip to content

Epic Tracker ToDos - Terrain-Viewer #5

Description

@jo-chemla

Quick Fixes

  • Phasing-out/Sunsetting original Iconem/historical-satellite see UI redesign v2 mockup : SidePanel for Layers and BottomPanel for Timeline historical-satellite#30 and Epic tracker historical-satellite#58
  • When using url-param terrainSrc1=terrainId, is it added from sample to BYOD? can one prepopulate with a election like ign-dsm and ign-dtm?
  • in compare and blend, capture date should default to source + date rather than off.
  • bug in beta.historical, when no latlon zoom pitch etc present in url, it's not using the default low-zoom whole earth defaults;
  • in historical imagery timeline, the labels below the timeline should only appear if there are just 2x1 mode, only show for A if A is historical and on B if B is historical.
  • match-colors do some sort of caching so we can easily toggle on/off the CSS filter toggle, probably cache just the transform so it gets reapplied instant
  • Re-parse and read all docs, evaluate what's missing. Could eg add sections on:
    • Export DSM/Contours/Historical/Share to socials.
    • Project Import/Export and local storage persistence for draws, sources, local COG, and settings section for this showing usage and free-up. local COG is probably mentioned in BYOD, not sure how detailed
    • Add a River-REM docs, eventually from a markdown mdx on the river-rem repo itself which would be source of truth, imported here from its github link?
    • tour kbd shortcuts link to the docs for up-to-date, description, so it appears at least in the docs walkthrough screenshot.
    • Add mention about feature iterator in drawings tools docs page
    • Add a note re streaming via maplibre geomatico vs titiler if it's not already present in the docs, as well as high precision quantization
    • in docs changelog page, add a note that it is also present in a settings dedicated panel
  • matcap has a weird fade-in on the center of the screen, is it connected to the ray divergence from optical axis? Looks wrong
  • We might want to rework phong at some point for sharpness, add some notes in your emory regarding your paragraph informative:
    Phong detail — it's not the lighting equation. I checked the ray/divergence hypothesis: the live phong uses a constant view vector for specular, which affects where highlights sit, not sharpness — a constant term can't blur anything spatially. The remaining softness vs native hillshade comes from the architecture: (1) native hillshade computes derivatives per device pixel in the fragment shader straight from the DEM texture, while our layer bakes normals into a fixed texel grid per tile — Nyquist-limited at that grid; (2) those normals are quantized to 8 bits per component (steps of ~1/127), which flattens subtle low-slope detail; (3) the normal texture is LINEAR-filtered, adding its own smoothing. If you want to chase native parity, the real fix is sampling the DEM texture and deriving normals per fragment in the live shader (a bigger job, but it would eliminate all three); cheaper options are a higher-precision normal encoding or simply re-adding a sharpness zoom level as an opt-in. Say the word on which, if any.
  • Add meta-app-micro-frontends postMessage to forward url nuqs state from embedded iframe to meta wrapper
  • Fix mobile timeline panel
  • hard to use double pill slider on mobile for lin max bounds
  • add mapbox and maptiler satellite from provided api keys
  • integrate osm-editor-kit/maplibre-editor-layer-index osmlab/editor-layer-index osm editor layer index and editor kit see offerings in placemark integrating maplibre Move to MapLibre and provider-neutral routing placemark/placemark#269
  • Mapterhorn Imagery Mapterhorn Imagery - Initial Ideas & Sources mapterhorn/mapterhorn#139 and
  • placemark maplibre pr talk about Nextgis QMS and Mapterhorn imagery issue/pr
  • showcased on awesome-maplibre, feature, river threads on socials etc, river-rem, historical, add to bellingcat-toolkit (repo) see thread Tool suggestion: Historical-Satellite and Terrain-Viewer from Iconem bellingcat/toolkit#892
  • In terrain mode, we might want to let the user have the choice for viz-modes other than basemap to apply to all map views (sync viz modes across views) or to only a selection, via the button groups shaped like the view grid for every viz mode, rather than checkbox. It could also be useful in that case to port not only 2x1 side, but also N-grid up to 4x2 by porting from historical appMode.
  • Eventually port the client-side iconem/River-REM in its own Tools panel or rather a complete appMode in itself (rather than tool actionable via toggle). the fetch rivers, build and compute detrended DSM minus Water Surface Level and applying colorramp, with slider/colorramp bounds, plus the button group switch between pure DSM vs RiverREM (each with its own slider and colorramp bounds).
  • search-eo/historical-imagery availability data freshness over an AOIs list/samples,eg unesco sites last year Imagery availability and freshness over AOIs samples/list via search-eo / historical satellite #18
  • Maxar API for Basemaps, Seamlines, STAC search: Integrate Maxar API for Basemaps, Seamlines, STAC search historical-satellite#37
  • Smart hierarchical DSM/DTM source? Like a live-mapterhorn equivalent? like a STAC or 3D-tiles / 2D-tiles equivalent, parent groups hierarchy or resolution going always finer further down the tree hierarchy, to composite multiple sources and fetch the highest resolution available given a viewport (or matching viewport resolution), which would choose between IGN french, spain, mexico etc from samples? Also look at PDAL tindex geopackage export PR here tindex create: add support for writing STAC Geoparquet PDAL/PDAL#5007
  • TBD integrate geojson editor like placemark.io repo or mapbox geojson.io (repo), for viewing feature properties, editing them, drawing etc
  • Finish beta.historical replacement of legacy historical, pashing out (that could still be served by legacy.historical-satellite.iconem.com). make sure pure historical-satellite redirects to the new terrain-viewer
  • upgrade to maplibre-gl-js v6. Also, potentially useful terrain bugfixes upstream on maplibre-gl-js/pulls that landed at v5 and v6: 7812 7813 7814 7284 7288 7549 7863
  • Other layer types like COPC, 3Dtiles, see options:
  • Eventually Add support for quantized-mesh like Moon, Mars Cesium datasets and projects, or Plugins: Add support for other tiled formats NASA-AMMOS/3DTilesRendererJS#943 also see clement-igonet/maplibre-gl-3dtiles-terrain for a start probably
  • ---
  • Look at awesome-maplibre whether anything interesting is visible
  • Do a shadscan, fix ux recommendations for accessibility etc, see scan prev. headless-shadcn. Some recommendations
    • apart from a11y
    • using toasts for all notifications (and there are various, during export, success, warnings or info message scattered across components),
    • eventually command bar Ctrl+K, start typing hillshade and it would return the hillshade checkbox or slider to be controlled via other keyboard like space + arrow keys (eventually focus to this control in sidebar, and give hint that user can use space and arrows to control opacity, or down to select other option like hillshade type, light direction (arrow keys again). Probably mostly up/down keys for selecting type, left/right for the slider values input.
  • renovate bot to update package deps, also explore bundle size and tree shakeable.
  • See inspiration combining DSM, DTM, slope faultines etc from IGN itself or jgaffuri/CartoHD and demo
  • Test with more frescoes:
    • Cambodia frescoes (kutsheka battle, barratage mer de lait),
    • Jam unroll,
    • Biot,
    • vatican missing tiles, etc.
  • ---
  • Add strava see below source in details, requires cors proxy https://heatmap-external-b.strava.com/tiles/all/blue/{z}/{x}/{y}.png
  • BYOD STAC sources to assemble multiple COGs or search collection
  • STAC federated collection discovery 2026-07-30-stac-discovery blog post, live prod maap version and developmentseed/stac-collection-discovery frontend repo and backend stac-fastapi-collection-discovery. Also atlas stac-index and repo and stac-index
  • Feature parity matrix: contours, export capability to WMS, Tilejson etc Feature matrix: Elevation source types vs Features + TiTiler case #16
  • Attempt importing samples for DTMs (from DSMs + AI) of openlandmap/GEDTM30 global (paper, zenodo dataset) and south-america ANADEM, See OpenTopo article
  • perform react-doctor by millionjs aidenybai millionco/react-doctor
  • analyze react-scan results and recommendations, look for unwanted react components redraws etc
  • tracker for compression support in geotiffjs [Tracker] for compression support in geotiffjs / Utifjs or sharp/jimp historical-satellite#38 and source update flicker (see if still problematic) Fix flicker of historical imagery historical-satellite#44
Some important Implementation choices

strava:

{
"id": "custom-basemap-1787928683294",
"name": "strava heatmap",
"url": "https://cors-proxy.mrgriscom.workers.dev/?https://heatmap-external-b.strava.com/tiles/all/blue/{z}/{x}/{y}.png",
"type": "tms",
"description": "",
"role": "overlay",
"opacity": 100,
"maxzoom": 11
}

  • Real shadow-mapping (render depth from the light's viewpoint into a framebuffer, then compare per-camera-fragment) needs two rendering passes reading back a texture across tiles — that's not expressible as an independent-per-tile raster protocol. It would fit the custom-WebGL-layer architecture Phong/Matcap's "2D Fast" already uses (since those already read getTerrainData() live per frame for a different reason — camera-relative perspective). Concretely: a light-pass rendering an orthographic depth texture of the visible terrain from the sun's direction, then a camera-pass sampling that texture per-fragment (with the usual shadow-acne bias) — same tile-covering/get_elevation plumbing already built for Phong/Matcap, just two passes instead of one.
    It's a meaningfully bigger lift than the other "2D Fast" layers (multi-pass FBO management, depth-texture encoding, bias tuning) — roughly on the order of the Matcap 2D Fast build. Want me to go ahead and build it as a new custom-GL-layer "2D Fast" mode for Shadows, or hold off on that one for now?

  • SVF Fast could be nice to try only 2 samples, 2px per octave ray marching to accumulate very fast even for 8px searching. Probably similar for openness. 

  • OPFS don't seem to be fixed on import, same error, but at least now when I load the local COG it does not disappear, only disappears on reload on the other computer, could it be because of LAN IP? 

    • new layer in light visualization section: shadows, that would do hard-shadows with some opacity, projected from sun position / light direction azimuth/elevation. Also eventually another layer could be good, hours in the sun (better with vector tile source with buildings height, but could be nice with DSM / DTM), but probably postpone this one.
  • LRM WMS vs TMS half-pixel — the recentering math is identical and correct for both; that was never the bug. What's different is that TMS/COG ancestors come from a real pre-built overview pyramid, while a WMS server resamples "live" for coarse zooms — often with a much cheaper/blockier resampler than bilinear. Fix: for the WMS-raw path only (regex-gated in lrm-protocol.ts), request the ancestor at a reduced size (rewriteWmsRequestSize, clamped ≥32px) and bilinearly upsample it ourselves client-side (upsampleDecodedTile) — bypassing the WMS server's own resampler entirely. Mapterhorn/TMS ancestors are untouched.

  • Parallelization/thread count — not a real worker/thread count (browser tile compute for these modes is single-threaded on the main thread; only the network fetch overlaps). tile-timing-stats.ts instead tracks an empirical maxConcurrency: at every tile request it computes requestedCount − completedCount (how many are genuinely in flight right now) and keeps a running max. The "time remaining" estimate divides pending tiles by that observed concurrency instead of assuming everything is strictly sequential — that's what fixed the over-conservative estimate.

  • while higher resolution tiles are loading for slow sources like SVF, Openness, Local Dominance, keep the lower resolution tiles, probably similar to what has been added to deckgl lod tiles pre-fetching here have a look at feat(geo-layers): coverage LOD tile prefetching visgl/deck.gl#10306. Possible cause:
    MapLibre placeholder-tile investigation — traced it into maplibre-gl-dev.js: cancelPendingTileRequestsWhileZooming defaults to true and cancels in-flight tile requests the moment you zoom past their level. For slow ray-marched modes, that meant the ancestor's expensive compute almost never got to finish before being canceled mid-zoom, so there was nothing valid sitting in the cache for maplibre's own (already generous, up to 10 levels) overzoom-placeholder logic to reuse. Setting cancelPendingTileRequestsWhileZooming={false} on the fixed it — a single prop, not the dual-source approach you ruled out.
    cancelPendingTileRequestsWhileZooming={false} does something narrower than it sounds: in maplibre-gl-dev.js's _updateRetainedTiles, that flag only stops MapLibre from aborting a tile it has already started tracking. It does nothing about tiles that haven't started yet.
    The actual bottleneck is almost certainly this: MapLibre runs all image/tile fetches (base map, hillshade, terrain elevation, and every custom-protocol analysis tile) through one global FIFO queue (ImageRequest in the same bundle). While the map isMoving() — which includes zooming — that queue's concurrency cap drops from MAX_PARALLEL_IMAGE_REQUESTS (16) to MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME (8), and a slot isn't freed until the request's promise resolves. A multi-second ray-marched tile (openness, local dominance, live Phong) occupies one of those 8 slots for its whole duration, and can also simply sit queued behind other fast requests during a zoom gesture, never getting a turn to even start.

Todo Next

  • Other detectors, like standard blob intensity (just pick the layer you designed to have great visual blobs at their given user-controlled range bounds etc. Eventually voronoi cells diagram islands from tops local extremas above threshold.
  • look at deckgl-raster and Kyle barron geotiffjs alternative
  • Create Points at Highest and Lowest Locations: An additional analysis process in the contour creation tool identifies local high and low points by identifying a specified number of concentric, or nested, contours. Additional parameters stipulating a required minimum distance between identified spot heights can be added, but the peaks and depressions identified are based on the contour lines generated by Global Mapper
  • Even more feature maps New feature maps, descriptors like shape index, LS factor, breaklines #15
  • Raster Line detector - feed it a given feature-map render, Canny, Sobel, or even ridge detectors like skimage

Medium

  • change detection between 2 COGs, no concrete usecase besides landslide open-data COG before-after, urban transformation etc, the data might be hard to find
  • Vector tiles, probably only tilejson or style json, as overlays and basemaps): yes, feasible - MapLibre natively supports type: "vector" sources with .pbf tiles, and a TileJSON-based vector variant would parallel your existing "TileJSON (Raster Basemap)" pattern nicely. It's a bigger feature though (needs a layer/style picker, not just a URL field), worth its own turn if you want it
  • make sure every component you want to reuse can live as its own eg ge historical imagery, custom slope protocol, WMS raw altitude terrain etc
  • import configuration to qgis, with opacities per layer, terrain source, hillshade applied with same angles. Some cannot be done live like our slope or 1st/2nd roder derivatives or LRM? should we offer the user the ability to compute them for offline use from the loaded terrain dem source?
  • can some of our custom protocols layers be ported to live qgis fetch and compute? Via VRT pixel compute python functions. The same way qgis decodes on the fly the raster tile source terrarium encdoig into raw elevation, would be great to compute these layers live.
  • Export footprint and options via watergis/maplibre-gl-export see doc, shows PrintableArea, let user control format, dpi, page orientation, northIcon and marker styles, scalebar now supported see [FEATURE] Support scale control (and possibly other non-interactive controls) watergis/maplibre-gl-export#383
  • Zero-shot change detection in-browser: Add zero-shot change detection / dino-v3 similarity via opengeos/geoai or decision-labs/geoai.js historical-satellite#33
  • stockpile volume calculation 2.5D: let the user draw new polygon (at some point could even be launched for every feature of terradraw, but would be more precise if done at the current resolution. flat-out the polygon and measure volume over/under interpolated surface: chrome://flags/#touch-events — set it to Enabled, not the default Automatic. "Automatic" is a heuristic Chrome uses to decide whether a display exposes the Touch Events API at all, and it's exactly the kind of heuristic that can misfire on an unusual large-format digitizer like a Surface Hub's — forcing it on bypasses that guess entirely.

Ideas / To be discussed

Ideas / TBD

  • Server-mode: Server mode: accounts, saved views + byod, trending etc #14
  • SVF, Openness via GPU postprocessing compositing pass faster? Raster-source + screen-space post-process idea: render the depth or normal tiles as a real raster layer (inherits correct 2D/3D/globe positioning automatically, zero custom math), then a separate lightweight "2d" custom layer immediately after it in z-order grabs those just-rendered pixels (framebuffer copy) and does the matcap/phong math as a full-screen shader pass with live uniforms. Elegant and would also work everywhere for free, but haven't verified MapLibre's custom-layer render loop cleanly supports mid-frame framebuffer capture between two specific layers without other passes interleaving.
  • Custom feature maps combination, linear combination weighted, to define advanced-user-controlled assemblage of indicators from terrain analysis or relief visualization.
  • Dura dsm and see if can do geomatico fake geo
  • Harden gpkg loading (in CRS other than 4326) via loadersgl or warn when wrong proj4 crs not 4326 etc, see Issues with polyfills and vite visgl/loaders.gl#2379
  • eventually blue-green on cloudflare pages or vercel?
  • while higher resolution tiles are loading for slow sources like SVF, Openness, Local Dominance, keep the lower resolution tiles, probably similar to what has been added to deckgl lod tiles pre-fetching here have a look at feat(geo-layers): coverage LOD tile prefetching visgl/deck.gl#10306. Possible cause:
  • migrating icons, shadcn just introduced icons migration from to lucide, tabler, hugeicons, phospor, remixicons at cli#migrate-icons via pnpm dlx shadcn@latest migrate icons --from lucide --to phosphor --yes. Did icon migration via shadcn cli migrate, each on its own throwaway branch, but it only migrated the components icons. I also wanted to migrate my own app icons, currently lucide icons, attempt to do it? Maybe do a table of all lucide icons I'm using, with the suggested icons from tabler hugeicons phosphor remixicon, then apply them each in their respective branch so I can see?

Wont-do

  • add overlay primitives geometry to hide unwanted areas like rc artefacts, or cog mask or no data clip or alpha
  • add a layer tree organization structure for byod COGs, allowing user to do folders, collapse or expand etc on both terrain and basemaps/overlays layers.
  • Probably not necessary: Mode simplified choose what stays in the sidebar in edit simple mode, useful for single slider or a few checkboxes on mobile in floating panel.
  • not very useful, drag and drop import files (into terradraw section?)

Maplibre features (shaders) & bugfixes threads:

Maplibre features threads

Todo long-term

Instructions

Animation rework instructions

Currently, I've got a react-map-gl maplibre app, with control-panel which expose an animation section with CameraButtons. These buttons let me set play360 play/pause, as well as set pose1, pose2, and play/pause animation and scrub via timeline slider. It's not very maintainable, and I want the given requirements :

  • animationDuration, loopMode, should be controlled by nuqs url query-params (currently duration is part of animState, it should not otherwise i cannot edit/update duration after setting psoes)
  • playing for keyframe animation, and playing360 would also usefully be url-controlled by nuqs
  • resolution, fps, render quality should be controlled by atoms with storage
  • ideally, pose1, pose2, should live in url as well, probably compressed, eg base64 encode/decode to avoid overflowing url length
  • i want 2 modes, one smooth, smoothCamera (also url controlled by nuqs) which would only interpolate camera pose, and one complete, which would also interpolate every other state variable, mostly numericValues, like opacities etc. but using nuqs shallow mode, to not propagate to url
  • in addition, there are some local state variables, like elapsed (only elapsed ?)
  • if I close and reopen the sidepanel, I want to still be able to clear raf by pausing anim.

Actually want to store deltas only between pose-1 and pose-2, so no-need to compress.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions