maplibre-gl-js icon indicating copy to clipboard operation
maplibre-gl-js copied to clipboard

Closest tiles disappearing when 3D terrain is used

Open openSourcerer9000 opened this issue 3 years ago • 23 comments
trafficstars

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?

image

maplibre-gl-js version: 2.2.0-pre.2

browser:Chrome

openSourcerer9000 avatar May 20 '22 20:05 openSourcerer9000

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: '&copy; 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>

wipfli avatar May 20 '22 21:05 wipfli

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: '&copy; 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).

christian-nils avatar May 23 '22 07:05 christian-nils

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 });
        });

grafik

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?

wiesehahn avatar May 24 '22 09:05 wiesehahn

Are tiles of different zoom level used by distance?

As far as I know, yes.

HarelM avatar May 24 '22 10:05 HarelM

This comment may help put some perspective on the issue

openSourcerer9000 avatar May 24 '22 13:05 openSourcerer9000

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.

dechristopher avatar May 24 '22 16:05 dechristopher

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.

openSourcerer9000 avatar May 24 '22 19:05 openSourcerer9000

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.

dechristopher avatar May 24 '22 19:05 dechristopher

Having the same issue... and switching to 512 tiles didn't solve the issue.

comlib1 avatar May 24 '22 19:05 comlib1

@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.

openSourcerer9000 avatar May 24 '22 19:05 openSourcerer9000

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.

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.

christian-nils avatar May 25 '22 07:05 christian-nils

Sample of white tiles issues with vector tiles basemap: https://bertt.github.io/terrain_tiles/amerongen/

bertt avatar Jun 01 '22 18:06 bertt

@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?

acalcutt avatar Jun 01 '22 21:06 acalcutt

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 avatar Jun 01 '22 21:06 acalcutt

@acalcutt in this case I've used Dutch AHN3/PDOK height data see https://github.com/bertt/terrain_tiles/blob/main/amerongen/README.md

bertt avatar Jun 01 '22 22:06 bertt

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 avatar Jun 02 '22 02:06 acalcutt

@acalcutt don't think it's a bounds issue, don't see whitespace with 1 Jaxa DTM https://bertt.github.io/terrain_tiles/spain/

Screenshot 2022-06-02 at 08 39 54

bertt avatar Jun 02 '22 06:06 bertt

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.

timokorkalainen avatar Jun 04 '22 11:06 timokorkalainen

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.

wiesehahn avatar Jun 04 '22 17:06 wiesehahn

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 avatar Jun 21 '22 10:06 flother

@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 avatar Jun 21 '22 17:06 HarelM

@HarelM Done! (The unit test, at least).

flother avatar Jun 21 '22 21:06 flother

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.

https://www.flother.is/blog/geldingadalsgos/map/

flother avatar Jun 26 '22 17:06 flother

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.

zdila avatar Aug 18 '22 18:08 zdila

It looks like that's a link to localhost — it doesn't resolve for me. Is there a public link you can share?

flother avatar Aug 19 '22 09:08 flother

Oh, sorry. I've fixed the link.

zdila avatar Aug 19 '22 09:08 zdila

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.

zdila avatar Aug 19 '22 09:08 zdila

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/

image

bertt avatar Aug 19 '22 09:08 bertt

Walking with the camera into terrain is still an open issue :/

prozessor13 avatar Aug 19 '22 10:08 prozessor13

I'm closing this as this is fixed I believe. If there's another issue let's open a different one.

HarelM avatar Aug 21 '22 14:08 HarelM