Closed Pessimistress closed 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
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.
But doesn't a picture like this one suggest some altitude?
That is from https://demotiles.maplibre.org/ with some pitch and rotate. When zooming in, the altitude seems to reduce...
Would be interesting to see a demo or so if you have one with deck gl and maplibre :)
@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?
Aha that is interesting. Is that the frustum something something?
Would be super helpful if you could share a drawing of these different concepts @Pessimistress
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
@wipfli
Would be interesting to see a demo or so if you have one with deck gl and maplibre :)
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);
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?
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()
.
closed by #1558
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.
What is the unit of transform.elevation
?
Meters. transform.elevation
is set to a value returned by transform.getElevation()
:
And getElevation
returns a value in meters:
Should exaggeration
be included in getCameraTargetElevation()
?
Yes. If the user sets exaggeration
to a value that is not 1
, then they want to render a 3D surface that does NOT match the real-world terrain. The peak of Mt. Everest is at the altitude 8848 meters. If you set exaggeration: 2
, then the peak of Mt. Everest in your map is at 17696 meters. For all intents and purposes, that is the ground truth of this particular map.
@danielfaust's use case
There are way too many hacks in this code, and you shouldn't try to wrangle private properties to fit your math. To use only public methods to get the z for your plane, you need something like
var modelAltitude = map.getElevationOnTerrain(modelOrigin);
var modelAsMercatorCoordinate = maplibregl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
var originU = modelAsMercatorCoordinate.x;
var originV = modelAsMercatorCoordinate.y;
var alt = modelAsMercatorCoordinate.z;
Now what is map.getElevationOnTerrain
? Mapbox has a method queryTerrainElevation that returns the corresponding altitude (in meters above sea level) at a given lnglat location. The equivalent in Maplibre would be
/// 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;
}
return 0;
}
What's up with this elevationOffset
?
I'll be honest it is the first time I heard about it. I do not see the name in the Mapbox style specification, let alone the default value 450
. Can someone link me to the source/documentation of this property?
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
@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.
@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.
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 :-)).
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
.
@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.
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.
@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...
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!
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 methodsgetCenter
,getZoom
,getPitch
,getBearing
andgetPadding
. 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.