You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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).
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.iorepo 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: 7812781378147284728875497863
Other layer types like COPC, 3Dtiles, see options:
3DTilesRendererJS version following TerrainRGB Terrarium TMS integration, globe controls, soon hillshade, postprocessing etc see TerrainRGB, Terrarium Plugins: Further Improvements NASA-AMMOS/3DTilesRendererJS#1691: port a version of the app/renderer from maplibre to pure threejs with 3DTilesRendererJS, probably via r3f, Globe, threejs lighting effects, overlays, react-postprocessing etc. No COG yet for basemaps/terrain
deckgl-raster for basemaps/terrain, and eventually loaders.gl from visgl for formats like copc reed-soul/wasm-spatial-core/.../copc-loader, already supports 3dtiles, cog etc
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.
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
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.
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.
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.
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?)
MAPTERHORN: custom stac terrain sources catalog, with footprint, Web-map-link extension or cog, or country isoa3 or regional and ingest stac, or Nextgis-QMS equivalent like terria catalog stac editor stac-manager
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.
Quick Fixes
Iconem/historical-satellitesee UI redesign v2 mockup : SidePanel for Layers and BottomPanel for Timeline historical-satellite#30 and Epic tracker historical-satellite#58terrainSrc1=terrainId, is it added from sample to BYOD? can one prepopulate with a election like ign-dsm and ign-dtm?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.
iconem/River-REMin 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).tindex create: add support for writing STAC Geoparquet PDAL/PDAL#5007https://heatmap-external-b.strava.com/tiles/all/blue/{z}/{x}/{y}.pngSome 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?
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
Medium
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
lucide, tabler, hugeicons, phospor, remixiconsat cli#migrate-icons viapnpm 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
Maplibre features (shaders) & bugfixes threads:
Maplibre features threads
POST /linksorigin domain api [FEAT] CORS support for browser-based API calls dubinc/dub#3502 and [FEAT] Accept base64 image upload forimagefield on link creation dubinc/dub#3503resamplingpaint property to raster, hillshade, color relief layers maplibre/maplibre-gl-js#7154raster-demWMS float32 raw-elevation geotiff source support maplibre/maplibre-gl-js#7127Todo 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 :
Actually want to store deltas only between pose-1 and pose-2, so no-need to compress.