maplibre / maplibre-gl-js

MapLibre GL JS - Interactive vector tile maps in the browser
https://maplibre.org/maplibre-gl-js/docs/
Other
6.5k stars 695 forks source link

API to access the camera target elevation when using terrain #1318

Closed Pessimistress closed 2 years ago

Pessimistress commented 2 years ago

Motivation

To use deck.gl (or other visualization overlay solutions) with Maplibre's terrain feature, the overlay needs to be able to construct viewport matrices that match those of the base map. Currently deck.gl accesses transform properties via Map class methods getCenter, getZoom, getPitch, getBearing and getPadding. These are not sufficient to match the camera if terrain is used.

Relevant discussion: https://github.com/visgl/deck.gl/issues/7064

Design

Expose a new method along the lines of map.getCenterAltitude(): number

Implementation

It seems that this should be pretty easy to implement, just by forwarding map.transform.elevation. However, an official API ensures future stability and compatibility as the code base involves.

HarelM commented 2 years ago

Sounds good to me. But before doing so, can you make sure it actually solves the issue by using the internal property? Also there's a need to define what this method will return when no terrain

Pessimistress commented 2 years ago

I have confirmed that it is exactly what I need.

When no terrain I would expect it to return 0, since the camera targets the ground.

wipfli commented 2 years ago

But doesn't a picture like this one suggest some altitude?

image

wipfli commented 2 years ago

That is from https://demotiles.maplibre.org/ with some pitch and rotate. When zooming in, the altitude seems to reduce...

wipfli commented 2 years ago

Would be interesting to see a demo or so if you have one with deck gl and maplibre :)

Pessimistress commented 2 years ago

@wipfli I think you are confusing the altitude of the camera with the altitude of the target. Maybe getCenterElevation is more consistent with the terminologies used in this code base?

wipfli commented 2 years ago

Aha that is interesting. Is that the frustum something something?

wipfli commented 2 years ago

Would be super helpful if you could share a drawing of these different concepts @Pessimistress

wipfli commented 2 years ago

Maybe one reason why transforms might be easier to us in MapLibre GL JS than in Mapbox v2 is that the zoom center is not under the mouse point? I don't know... https://github.com/maplibre/maplibre-gl-js/issues/1024

Pessimistress commented 2 years ago

@wipfli

image

Pessimistress commented 2 years ago

Would be interesting to see a demo or so if you have one with deck gl and maplibre :)

https://github.com/visgl/deck.gl/pull/7114/files#diff-c804cf7ecd745e0fa2dfef7ea3a3a1871e74f10d35025ada4274ec12b4510384

danielfaust commented 2 years ago

Related: https://stackoverflow.com/questions/73409827/maplibre-gl-js-with-terrain-layer-how-to-pin-a-horizontal-plane-to-a-specific-a

I have found a way to do this, but it currently only works with Terrain RGB tiles from Maptiler.

map.getCenterAltitude() would be basically this code:

var terrain = map.getTerrain();
var exaggeration = terrain.exaggeration;
var heightInMetersAtCenter = ((map.transform.elevation - (450.0 * exaggeration)) / exaggeration);
acalcutt commented 2 years ago

I'm pretty sure these are the RGB to Elevation functions here. https://github.com/acalcutt/maplibre-gl-js/blob/main/src/data/dem_data.ts#L70-L96 Do these not include exaggeration? Could these functions be used?

danielfaust commented 2 years ago

No, these are used for unpacking the data, the exaggeration is applied later.

While searching for its usage in the code, I found the following line in terrain.ts:

this.elevationOffset = typeof options.elevationOffset === 'number' ? options.elevationOffset : 450; // ~ dead-sea

which seems to indicate that what I'm using is correct. I was wondering where the 450 came from since I didn't get it from the code, but through trial and error.

From the Wikipedia:

The lake's surface is 430.5 metres (1,412 ft) below sea level,[4][5] making its shores the lowest land-based elevation on Earth.

A few lines below the elevation is computed as:

getElevation(tileID: OverscaledTileID, x: number, y: number, extent: number = EXTENT): number {
    return (this.getDEMElevation(tileID, x, y, extent) + this.elevationOffset) * this.exaggeration;
}

This raises a new issue, since elevationOffset is not exposed. Ideally it would be accessible through the object returned by map.getTerrain().

birkskyum commented 2 years ago

closed by #1558

Pessimistress commented 2 years ago

To consolidate the conversation happening on multiple threads, I'll attempt to address the comment by @birkskyum in https://github.com/maplibre/maplibre-gl-js/pull/1558#discussion_r956758521 and @danielfaust in https://github.com/maplibre/maplibre-gl-js/pull/1558#issuecomment-1229507560 here.

birkskyum commented 2 years ago

The elevation offset is here on line 3580, but maybe not in the documentation yet (I'll look into that!) - it was introduced as part of the PR adding 3d terrain functionality (#1022), which is why it's not necessarily how things work in mapbox-gl-js: https://github.com/maplibre/maplibre-gl-js/blob/1c592d4c28f6acd55529a4a3541d255434bdbb34/src/style-spec/reference/v8.json#L3825-L3857

danielfaust commented 2 years ago

@Pessimistress thanks your summary and the hint, I wasn't aware of the getElevation() method on the transform and am glad to know that it exists.

One correction: map.getElevationOnTerrain() should be divided by terrain.exaggeration:

/// map.ts
getElevationOnTerrain(lnglat: LngLatLike) {
  var terrain = this.style.terrain;
  if (terrain) {
    lnglat = LngLat.convert(lnglat);
    return (this.transform.getElevation(lnglat, terrain) - terrain.elevationOffset * terrain.exaggeration) / terrain.exaggeration;
  }
  return 0;
}

https://jsbin.com/yizafinone/edit?html,console,output at "log comparison to console" half way down the code.

Doesn't MapLibre GL JS try to be a drop-in replacement for Mapbox GL JS? I'm a bit surprised to see that queryTerrainElevation(lnglat, options?) would get implemented as getElevationOnTerrain(lnglat). I'm asking because I'm new to this project since I was using Leaflet up to now.

birkskyum commented 2 years ago

@danielfaust

queryTerrainElevation

We try to retain compatibility to the mapbox v1, but keep in mind that mapbox v2 is proprietary, so it would be a break of their license to go copy whatever features they make - therefore our implementations of everything new are designed independently.

HarelM commented 2 years ago

No, MapLibre is no longer a drop-in replacement as of version 2.x. If it was we'll get sued that we are copying Mapbox 2.x code which is proprietary. 450 is the dead sea meters below sea level. I had a case where the terrain there was missing. This was the fix that was done in order to prevent this missing terrain and rendering below sea level, but it might not be relevant anymore as can be read in one of the terrain 3D issues (don't ask me which :-)).

Pessimistress commented 2 years ago

map.getElevationOnTerrain() should be divided by terrain.exaggeration

No, it shouldn't. Please look at my whole code sample how the returned value is used by MercatorCoordinate.fromLngLat.

Pessimistress commented 2 years ago

@HarelM was your issue that under-sea-level terrain was clipped? If so, the proper fix should be to adjust the calculation of the far plane, not offsetting the z of the whole map. When used with deck.gl the 3D contents will not interact correctly if the terrain is not rendered at the real altitude.

birkskyum commented 2 years ago

An example of this clipping issue can be seen with npm run start-debug, and the test page with 3d satellite terrain pointed at the dead sea http://0.0.0.0:9966/test/debug-pages/terrain-satellite.html#9.72/31.5224/35.5095/33.4

In the code (test/debug-pages/terrain-satellite.html), the elevationOffset is currently set to 0, and if set to 450 it resolves the issue at the dead sea where the texture disappears when zooming in. The clipping issue persists in other places where there are points further below sea level in this map (i.e. indentations in the Mediterranean sea). Let's look into how we can move the far plane.

Screen Shot 2022-08-29 at 10 14 59
HarelM commented 2 years ago

@Pessimistress any help with this area would be greatly appreciated. We lack some webgl knowledge in our maintainers community ATM. I would love to remove this elevation offset "hack" and so would @prozessor13 who wrote it. As a side not, some of the mountains around the dead sea are also under see level, not just the dead sea itself...

prozessor13 commented 2 years ago

The far plane is calculated between: https://github.com/maplibre/maplibre-gl-js/blob/main/src/geo/transform.ts#L847 and https://github.com/maplibre/maplibre-gl-js/blob/main/src/geo/transform.ts#L868

Some sort of offset is needed to render the dead-sea. But may this offset can also be subtracted from transform.cameraToSeaLevelDistance as well? Would be great to get ridd of the elevationOffset thing!