maplibre-gl-js
maplibre-gl-js copied to clipboard
Closest tiles disappearing when 3D terrain is used
When 3D terrain is on, maplibre is removing the closer raster tiles from view. Something to do with the camera being distorted from the viewport? It's like it thinks the closest tiles are out of view.
The issue is present with exaggeration of 1, but gets dramatically worse by increasing the exaggeration z-factor (TerrainLayer.jsx). Vector basemaps seem immune, but geojson vector layers also disappear in unison with raster basemaps and custom raster tiles.
The problem has been identified to occur in flat areas which are also down at sea level. All the terrain examples use the Swiss Alps, etc (because it looks awesome), so the camera may just be calibrated to elevations of 5km. Given an elevation of 0-5m, something may not be set up correctly.
You can fork this demo repo to test the issue, or just zoom to center: [-93.32780,29.8834], pitch:85 using this terrain and a raster imagery basemap.
type: "raster-dem",
tiles: ["https://vtc-cdn.maptoolkit.net/terrainrgb/{z}/{x}/{y}.webp"],
https://github.com/openSourcerer9000/MapLibreGL-3D-terrain-tiles-disappearing-issue-demo
May be related to this algorithm https://github.com/maplibre/maplibre-gl-js/issues/1080?

maplibre-gl-js version: 2.2.0-pre.2
browser:Chrome
Thanks for reporting! Do you think you can reproduce this problem by tuning the debug example below?
<!DOCTYPE html>
<html>
<head>
<title>MapLibre GL JS Terrain3D Example</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet" />
<style>
body {
margin: 0;
padding: 0;
}
html,
body,
#map {
height: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
var map = window.map = new maplibregl.Map({
container: 'map',
zoom: 12,
center: [11.39085, 47.27574],
pitch: 52,
hash: true,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap Contributors',
maxzoom: 19
},
terrainSource: {
type: 'raster-dem',
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
tileSize: 256
},
hillshadeSource: {
type: 'raster-dem',
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
tileSize: 256
},
},
layers: [
{
id: 'osm',
type: 'raster',
source: 'osm'
},
{
id: 'hills',
type: 'hillshade',
source: 'hillshadeSource',
layout: {'visibility': 'visible'},
paint: {'hillshade-shadow-color': '#473B24'}
}
],
terrain: {
source: 'terrainSource',
exaggeration: 1
}
},
maxZoom: 18,
maxPitch: 85
});
map.addControl(new maplibregl.NavigationControl({
visualizePitch: true,
showZoom: true,
showCompass: true
}));
map.addControl(
new maplibregl.TerrainControl({
source: 'terrainSource',
exaggeration: 1
})
);
</script>
</body>
</html>
Hi,
I experience the same issue. I used your demo code, and modified a bit to recreate the issue:
var map = window.map = new maplibregl.Map({
container: 'map',
zoom: 12,
center: [14.9799, 56.5254],
pitch: 52,
hash: true,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap Contributors',
maxzoom: 19
},
terrainSource: {
type: 'raster-dem',
url: 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=MY_MAPTILER_API_KEY',
tileSize: 256
},
hillshadeSource: {
type: 'raster-dem',
url: 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=MY_MAPTILER_API_KEY',
tileSize: 256
}
},
layers: [
{
id: 'osm',
type: 'raster',
source: 'osm'
},
{
id: 'hills',
type: 'hillshade',
source: 'hillshadeSource',
layout: { visibility: 'visible' },
paint: { 'hillshade-shadow-color': '#473B24' }
}
],
terrain: {
source: 'terrainSource',
exaggeration: 1
}
},
maxZoom: 18,
maxPitch: 85
})
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true,
showZoom: true,
showCompass: true
})
)
map.addControl(
new maplibregl.TerrainControl({
source: 'terrainSource',
exaggeration: 1
})
)
I changed the terrain-rgb to the tiles.json provided by MapTiler's API. Then I inspected a region of the world where the terrain is not that high. Here is the result:
https://user-images.githubusercontent.com/2658014/169762340-f12a99ea-3d41-4321-a4d8-de3455ebc7df.mp4
Anyway, I take the opportunity to thank you for your work on this fantastic feature!
EDIT: BTW, the issue can be seen on https://maptoolkit.net as well. Go to a area that is quite flat (let's say Malmö), activate the 3D and zoom quite much on the map, tiles will start disappearing (https://maptoolkit.net/#/@12.99916,55.6034,18.4,59.9,60,terrain,3d).
I experience the same issue with e.g.
const map= new maplibregl.Map({
container: "map",
style: 'https://api.maptiler.com/maps/hybrid/style.json?key=XXX ',
center: [10.61,51.78],
pitch: 65,
zoom: 13
});
map.on('load', () => {
map.addSource("terrain", {
"type": "raster-dem",
"url": "https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=XXX ",
});
map.setTerrain({
source: "terrain",
exaggeration: 3 });
});

I guess before exaggerating the tiles cover the window and then when exaggerated they are "lifted" to reveal the gap.
Also I recognized artifacts along horizontal tile borders in the distance. Are tiles of different zoom level used by distance?
Are tiles of different zoom level used by distance?
As far as I know, yes.
This comment may help put some perspective on the issue
I think this issue is due to the tileSize property in the terrain source. You need to set tileSize: 512 since most of the DEM sources from MapTiler aren't 256 pixels in size. When I added this, the issue was resolved. Hope this helps.
Hm OK in the map I'm working on I'm using custom terrain tiles. I can try gen'ing them at 512px instead and see if it works. If so, the common factors exposing the bug would be low elevation, flat slopes, and tiles at 256px.
As for the flat slopes and low elevation, make sure you're generating the tiles to support the equation given here:
height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
This is how MapLibre decodes raster-dem RGB sources to retrieve elevation data in meters.
I can't imagine there's a specification that demands 512px tiles for 3D terrain though.
Having the same issue... and switching to 512 tiles didn't solve the issue.
@dechristopher
I split the single band terrain into Terrarium RGB using this rasterio script that seems to be floating around, and then set encoding='terrarium'
with rasterio.open('dem_plus_body.tif') as src:
dem = src.read(1)
r = np.zeros(dem.shape)
g = np.zeros(dem.shape)
b = np.zeros(dem.shape)
v = dem + 32768
r += np.floor(v / 256.0)
g += np.floor(v % 256.0)
b += np.floor((v - np.floor(v)) * 256.0)
meta = src.profile
meta["dtype"] = rasterio.uint8
meta["nodata"] = None
meta["count"] = 3
with rasterio.open('encoded_elevation.tif', 'w', **meta) as dst:
dst.write_band(1, r.astype(rasterio.uint8))
dst.write_band(2, g.astype(rasterio.uint8))
dst.write_band(3, b.astype(rasterio.uint8))
The encoding tag didn't throw an error, so I assume this format is supported? Also my elevation units were feet, so I guess if terrarium is m it would make it 3x flatter.
I think this issue is due to the tileSize property in the terrain source. You need to set
tileSize: 512since most of the DEM sources from MapTiler aren't 256 pixels in size. When I added this, the issue was resolved. Hope this helps.
Not sure if this is the right way to fix the issue. I am using the winter style provided by MapTiler (https://api.maptiler.com/maps/winter/style.json?key=MY_MAPTILER_API_KEY) and the source "terrain-rgb" does not have a tileSize property set to 256 so it should use the default of 512px. I do have the problem with this source as well.
Sample of white tiles issues with vector tiles basemap: https://bertt.github.io/terrain_tiles/amerongen/
@bertt
I was looking at your example and was able to replicate your issue with my terrain tiles here https://wifidb.net/demo/terrain_raster/test4_notworking.html
The only two things I notice was 1.) Right now, Terrain and Hillshade are supposed to have separate sources due to the way source caching works at the moment. ( https://github.com/maplibre/maplibre-gl-js/issues/1186#issuecomment-1100927180 ) 2.) Exaggeration was very high, Set to 10. It looks like you used my process for JAXA based terrain tiles, so the exaggeration should be the default of 1 with those tiles.
I tried fixing both those things. When I added the second source for hillshade, the issue persisted. When I set the exageration to 1, the issue with white tiles seems much better https://wifidb.net/demo/terrain_raster/test4.html
So maybe this is an issue when a high exaggeration is set?
Actually I think I'm wrong. I still see the issue on the second example, I jut have to be zoomed in a lot further
@acalcutt in this case I've used Dutch AHN3/PDOK height data see https://github.com/bertt/terrain_tiles/blob/main/amerongen/README.md
Could this be something with going over the maxzoom of the tile sources?
I notice if I take the first example in this thread and limit MaxZoom to 14, the issue doesn't occur https://stackblitz.com/edit/web-platform-fsf4vk?file=index.html
However if I go a little higher, to MaxZoom 16, that issue appears https://stackblitz.com/edit/web-platform-fembpm?file=index.html
For the other example @bertt , I also wonder about it being a bounds issue, since most of the sources I have used were full planet.
@acalcutt don't think it's a bounds issue, don't see whitespace with 1 Jaxa DTM https://bertt.github.io/terrain_tiles/spain/
Are the tiles available for all zoom levels? I believe I had an issue with a source providing only certain tile levels that meant the Terrain3D algorithm using a specific level to make initial height calculations couldn't perform and in turn seemed to produce similar errors.
To me it looks more like tile coverage is calculated before exaggeration, and then if lifted too heavy it does not fit the viewport anymore. In my basic understanding additional tiles pointing towards the viewer should be requested. Based on exaggeration and pitch.
I have a 3D terrain map in development that I've used to test @prozessor13's proposed solution (352bc03e) and as far as I can see it fixes the problem. With a TerrainRGB layer, a satellite raster layer, and an exaggeration of 1.5 the closest tiles to the camera are now visible.
Would it possible to publish a v2.2.0-pre.3 release for this? Once it's available via npmjs.com I would be happy to publish my map as a test for the fix.
@flother as far as I can see in the PR a unit test is missing for this case. If you could create a unit test to cover this case (and maybe a render test too) and send a PR against the branch of the linked PR (#1300) I think we could push this forward faster. Thanks!!
@HarelM Done! (The unit test, at least).
I think that this is fixed now that v2.2.0-pre.3 has been released. The terrain map I mentioned earlier in the thread is running on that version and I no longer have the problem of missing tiles.
May be a different issue, but in my case not only closest tiles but all. Open https://labs.maptiler.com/samples/maplibre/terrain/#style=satellite&lat=48.57344071&lng=20.46078220&zoom=17.16&bearing=-127.95&pitch=76.00&3d=true then zoom a little bit and tiles starts to disappear. If you refresh the page, nothing is shown until you zoom out. MapLibre 2.3.0.
It looks like that's a link to localhost — it doesn't resolve for me. Is there a public link you can share?
Oh, sorry. I've fixed the link.
What I noticed with https://labs.maptiler.com/samples/maplibre/terrain/ is that I can't zoom inside the hill, but with my demo I can do it, which probably causes all the tiles to (gradually) disappear.
I can confirm this issue is fixed after upgrade from v2.2.0-pre.2 to 2.3.0. Demo: https://bertt.github.io/terrain_tiles/amerongen/

Walking with the camera into terrain is still an open issue :/
I'm closing this as this is fixed I believe. If there's another issue let's open a different one.