Skip to content

Dem Elevation API#7201

Open
eddy-geek wants to merge 4 commits intomaplibre:mainfrom
eddy-geek:demapi
Open

Dem Elevation API#7201
eddy-geek wants to merge 4 commits intomaplibre:mainfrom
eddy-geek:demapi

Conversation

@eddy-geek
Copy link

Add a DEM elevation query API on RasterDEMTileSource.

I propose 2 new APIs (review commits separately), without or with fetching required tiles.

The goal is for rich map applications to be able to display elevation under pointer, or add elevation to displayed shapes, without duplicating DEM logic & download or hacking maplibre internals.

In the first API the implem is simpler but we have to rely on eg hillshade layer to download DEM tiles, so it is a bit more fragile for advanced cases (elevate shape partly off-screen).

The second is easier/more consistent to use but has more impact eg we need to decide how the tile caches interact.

I wasn't sure which option fit better the project, I hope at least one of them makes sense :)

Without fetch

Add queryElevations API on RasterDEMTileSource.

- Introduce ElevationQueryResult and queryElevations(lnglats, minzoom?, maxzoom?) on raster DEM sources.
- Implement bilinear DEM sampling and highest-resolution-first tile search via calculateTileKey.
- Add TileManager.findTileByKey(key) to unify lookup across in-view tiles and out-of-view cache.
- Export ElevationQueryResult from public index.
- Add unit tests
- Add test/examples/get-elevation-values.html using mapterhorn terrarium raster-dem source and live hover sampling.

With fetch

Add queryElevationsWithFetch that fetches missing DEM tiles and reuses queryElevations sampling.

- Runs sync query first, then fetches unresolved tiles.
- Selects fetch zoom by cache budget (highest zoom that fits, fallback to min zoom).
- Temporarily grows out-of-view cache via setMaxSize during fetch pass and restores it in finally to avoid evicting tiles between query passes.
- Keeps fetch failures non-fatal (results remain null where data is unavailable).
- Unit tests for fetch behavior, deduplication, zoom fallback, cache growth, and failure handling.
- example: test/examples/get-elevation-value-with-fetch.html for queryElevationsWithFetch.
image

Launch Checklist

  • all AI code reviewd by me ;)
  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!
  • Briefly describe the changes in this PR.
  • Link to related issues.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality.
  • Document any changes to public APIs.
  • Post benchmark scores.
  • Add an entry to CHANGELOG.md under the ## main section.

@eddy-geek eddy-geek changed the title Demapi Dem Elevation API Mar 2, 2026
@HarelM
Copy link
Collaborator

HarelM commented Mar 2, 2026

I would avoid the "with fetch" option as a developer can simply fetch the relevant tiles and get the relevant elevation data.
Also what about the current get elevation method? Is it not covering this use case?

@eddy-geek
Copy link
Author

eddy-geek commented Mar 2, 2026

what about the current get elevation method?

what I see is map.queryTerrainElevation(lngLat) (@src/ui/camera.ts#1659-1662) -> calls terrain.getElevationForLngLat(...) -> getElevation -> getDEMElevation (@src/render/terrain.ts#224-226)

all except the last one apply terrain exaggeration and depend on terrain being enabled (return null otherwise) -- and only the first one is part of public API

    /**
     * Gets the elevation at a given location, in meters above sea level.
     * Returns null if terrain is not enabled.
     * If terrain is enabled with some exaggeration value, the value returned here will be reflective of (multiplied by) that exaggeration value.
     * This method should be used for proper positioning of custom 3d objects, as explained [here](https://maplibre.org/maplibre-gl-js/docs/examples/adding-3d-models-using-threejs-on-terrain/)
     * @param lngLatLike - [x,y] or LngLat coordinates of the location
     * @returns elevation in meters
     */
    queryTerrainElevation(lngLatLike: LngLatLike): number | null {
        if (!this.terrain) {
            return null;
        }
        return this.terrain.getElevationForLngLat(LngLat.convert(lngLatLike), this.transform);
    }

whereas new approach works even in 2D, and returns elevation in meters independently of exaggeration. I could try to share a tiny bit more code (bilinear sampling?) but that would mean some refactoring.

did I miss anything else ?

I would avoid the "with fetch" option as a developer can simply fetch the relevant tiles and get the relevant elevation data.

but then the developer has to redo the tile fetch & decode, not terrible but I think it's fairer to then say 'the functionality is limited to in-view tiles, otherwise write your own' as the fallback becomes more code than the implementation. but I'm fine with that, at least for my use, it's a rather niche case.

@HarelM
Copy link
Collaborator

HarelM commented Mar 2, 2026

I have the following code in my app to get elevation, it's not a lot of code so I don't mind having it on my app.
It also allows fetching a tile from a db and not from the network.
https://github.com/IsraelHikingMap/Site/blob/e07798830463e0951f3cf02a0d84eb36c09d4e8e/IsraelHiking.Web/src/application/services/elevation.provider.ts

I'll think about it a bit more...

EdwardO added 2 commits March 2, 2026 22:41
Add a DEM elevation query API on RasterDEMTileSource.
unlike terrain.getElevation, works even in 2D, and returns elevation in meters independently of exaggeration.

- Introduce ElevationQueryResult and queryElevations(lnglats, minzoom?, maxzoom?) on raster DEM sources.
- Implement bilinear DEM sampling and highest-resolution-first tile search via calculateTileKey.
- Add TileManager.findTileByKey(key) to unify lookup across in-view tiles and out-of-view cache.
- Export ElevationQueryResult from public index.
- Add unit tests
- Add test/examples/get-elevation-values.html using mapterhorn terrarium raster-dem source and live hover sampling.
@eddy-geek
Copy link
Author

clear, I overestimated how hard it is to have a clean room implem. I've removed the fetch version so we can focus on the basic one.

also simplified/refactored a bit. I guess even returning zoom is a bit overkill.

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.70%. Comparing base (0912f0f) to head (4e7a7e2).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
src/source/raster_dem_tile_source.ts 93.54% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7201      +/-   ##
==========================================
+ Coverage   92.68%   92.70%   +0.02%     
==========================================
  Files         289      289              
  Lines       24085    24100      +15     
  Branches     5102     5112      +10     
==========================================
+ Hits        22322    22341      +19     
+ Misses       1763     1759       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

* @param fx - Fractional x position within the tile, in [0, 1)
* @param fy - Fractional y position within the tile, in [0, 1)
*/
function sampleDEMBilinear(dem: DEMData, fx: number, fy: number): number {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this code already in the demdata file? Can't we reuse it?

Copy link
Author

@eddy-geek eddy-geek Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find bilinear interpolation (or any duplication) in dem_data.ts

I only found bilinear interpolation in @src/render/terrain.ts#163-185, but it works in terrain tile-coordinate space after matrix transform/normalization -- still possible to extract a common core but it needs to be put somewhere else. do you want me to do it ? and put it in dem_data ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either there or a separate file just for that I guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants