Summary
Follow-up ideas for the GeoMap 2D/3D engine from an architectural comparison against MapLibre Native (tile pyramid, caching, terrain, scheduling, imagery fallback, network layers). Each item is independently actionable; none is urgent. Context: PR #14821 (perf work), #14819 (terrain fetch pipeline), #14820 (elevation pyramid).
Baseline finding for the record: MapLibre Native has no 3D terrain mesh (hillshade shading only), and its retain/holdForFade tile logic is the same pattern as GeoMap's retiring covers — the core architecture is validated, these are refinements.
Items
1. Antimeridian behavior (possible latent gap)
MapLibre has dedicated wrap handling (handleWrapJump in tile_pyramid.cpp): panning across ±180° longitude re-keys resident tiles to the new world copy instead of flushing them. GeoMap's _visibleGroundRect clamps to world bounds and TileMath does not wrap — behavior when panning across the dateline (e.g. near Fiji) is untested and the world may simply end. Action: manually test; if broken, decide whether wrap support is in scope or the camera should clamp gracefully.
2. Tile fade-in (visual polish)
Patches currently pop in when heights/imagery arrive. MapLibre fades arriving tiles (~300 ms, holdForFade/previouslyRenderedTiles). In QML this is an opacity animation on the patch delegate triggered by ready — cheap, hides the LOD-flash moment during zoom, and makes the engine feel finished.
3. Derive budgets from viewport instead of magic constants
MapLibre sizes its tile cache as (screen_w / tileSize) * (screen_h / tileSize) * zoomLevels * 0.5. GeoMap uses hand-picked constants (kMaxPatches = 256, kMaxRetiredImages = 128, add/removal caps). When tablet profiling data arrives and budgets need per-device tuning, derive them from viewport size and refresh rate rather than adding per-platform constants.
4. Prefetch zoom delta (pan/zoom UX)
MapLibre optionally fetches lower-zoom "pan tiles" first (prefetchZoomDelta) so something renders immediately during movement, refining afterward. GeoMap's _desiredPatches could order coarse ancestors first when large areas of the desired set are missing. Softens the cold-cache first-paint experience (complements #14819/#14820, does not replace them).
5. Request prioritization (fold into #14819)
MapLibre demotes no-longer-ideal tiles to Priority::Low and cancels pending network work for them; visible/required tiles fetch first. GeoMap cancels culled patches' height requests but issues the rest FIFO — no center-first ordering. Belongs to the #14819 pipeline redesign; recorded here for cross-reference.
6. DEM border backfill (if LOD cracks appear)
MapLibre eliminates elevation seams by copying edge pixels from the 8 neighboring DEM tiles as they load, tracked with a neighbor bitmask (DEMTileNeighbors, backfillBorder() in raster_dem_tile.cpp). If inter-patch/inter-LOD cracks show up in GeoMap with real terrain, this is cheaper than skirt geometry.
7. Overscale cap experiment
MapLibre caps how many zoom levels a parent tile may be stretched as a fallback (maxParentTileOverscaleFactor, ~3) to avoid obviously mushy upsampling. GeoMap's kMaxAncestorFallbackLevels = 8 allows 256x area stretch. One-constant experiment: try 3–4 and compare fallback appearance vs coverage.
Considered and rejected (recorded so it isn't re-proposed)
Parent-texture-with-scaled-UVs imagery fallback (MapLibre renders a parent tile's texture with adjusted UVs instead of CPU-compositing): would replace GeoMap's composite cache, but (a) children would reference live parent textures, trading cache invalidation for texture lifetime pinning against the retired-image LRU; (b) it only covers the ancestor-crop direction — child composites would require MapLibre-style retain rendering of actual child patches (a deeper architecture change); (c) sub-rect bilinear sampling bleeds across sibling borders (needs half-texel UV insets); (d) sharing textures across delegates crosses the model/view boundary. Benchmark data shows composites are no longer in the hot path, so the win is GPU memory only. Revisit only if tablet profiling shows composite time or texture memory as a real cost (QQuick3D Texture.scaleU/positionU make it feasible if so).
Summary
Follow-up ideas for the GeoMap 2D/3D engine from an architectural comparison against MapLibre Native (tile pyramid, caching, terrain, scheduling, imagery fallback, network layers). Each item is independently actionable; none is urgent. Context: PR #14821 (perf work), #14819 (terrain fetch pipeline), #14820 (elevation pyramid).
Baseline finding for the record: MapLibre Native has no 3D terrain mesh (hillshade shading only), and its retain/
holdForFadetile logic is the same pattern as GeoMap's retiring covers — the core architecture is validated, these are refinements.Items
1. Antimeridian behavior (possible latent gap)
MapLibre has dedicated wrap handling (
handleWrapJumpintile_pyramid.cpp): panning across ±180° longitude re-keys resident tiles to the new world copy instead of flushing them. GeoMap's_visibleGroundRectclamps to world bounds and TileMath does not wrap — behavior when panning across the dateline (e.g. near Fiji) is untested and the world may simply end. Action: manually test; if broken, decide whether wrap support is in scope or the camera should clamp gracefully.2. Tile fade-in (visual polish)
Patches currently pop in when heights/imagery arrive. MapLibre fades arriving tiles (~300 ms,
holdForFade/previouslyRenderedTiles). In QML this is an opacity animation on the patch delegate triggered by ready — cheap, hides the LOD-flash moment during zoom, and makes the engine feel finished.3. Derive budgets from viewport instead of magic constants
MapLibre sizes its tile cache as
(screen_w / tileSize) * (screen_h / tileSize) * zoomLevels * 0.5. GeoMap uses hand-picked constants (kMaxPatches = 256,kMaxRetiredImages = 128, add/removal caps). When tablet profiling data arrives and budgets need per-device tuning, derive them from viewport size and refresh rate rather than adding per-platform constants.4. Prefetch zoom delta (pan/zoom UX)
MapLibre optionally fetches lower-zoom "pan tiles" first (
prefetchZoomDelta) so something renders immediately during movement, refining afterward. GeoMap's_desiredPatchescould order coarse ancestors first when large areas of the desired set are missing. Softens the cold-cache first-paint experience (complements #14819/#14820, does not replace them).5. Request prioritization (fold into #14819)
MapLibre demotes no-longer-ideal tiles to
Priority::Lowand cancels pending network work for them; visible/required tiles fetch first. GeoMap cancels culled patches' height requests but issues the rest FIFO — no center-first ordering. Belongs to the #14819 pipeline redesign; recorded here for cross-reference.6. DEM border backfill (if LOD cracks appear)
MapLibre eliminates elevation seams by copying edge pixels from the 8 neighboring DEM tiles as they load, tracked with a neighbor bitmask (
DEMTileNeighbors,backfillBorder()inraster_dem_tile.cpp). If inter-patch/inter-LOD cracks show up in GeoMap with real terrain, this is cheaper than skirt geometry.7. Overscale cap experiment
MapLibre caps how many zoom levels a parent tile may be stretched as a fallback (
maxParentTileOverscaleFactor, ~3) to avoid obviously mushy upsampling. GeoMap'skMaxAncestorFallbackLevels = 8allows 256x area stretch. One-constant experiment: try 3–4 and compare fallback appearance vs coverage.Considered and rejected (recorded so it isn't re-proposed)
Parent-texture-with-scaled-UVs imagery fallback (MapLibre renders a parent tile's texture with adjusted UVs instead of CPU-compositing): would replace GeoMap's composite cache, but (a) children would reference live parent textures, trading cache invalidation for texture lifetime pinning against the retired-image LRU; (b) it only covers the ancestor-crop direction — child composites would require MapLibre-style retain rendering of actual child patches (a deeper architecture change); (c) sub-rect bilinear sampling bleeds across sibling borders (needs half-texel UV insets); (d) sharing textures across delegates crosses the model/view boundary. Benchmark data shows composites are no longer in the hot path, so the win is GPU memory only. Revisit only if tablet profiling shows composite time or texture memory as a real cost (QQuick3D
Texture.scaleU/positionUmake it feasible if so).