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

Image in raster layer shows only partially on certain zoom levels when 3D terrain is enabled.

Open danielfaust opened this issue 2 years ago • 31 comments

CEA884BC-D1C4-49DB-9611-8BBC39B1C392

maplibre-gl-js version: v2.4.0 (already present in v2.2.0-pre.2)

browser: Google Chrome Version 104.0.5112.102 (Offizieller Build) (64-Bit)

No errors or warnings appear in the console

Steps to Trigger Behavior

  1. Zoom in and out into the image to see the problem, then zoom in to prepare to toggle elevation layer
  2. Toggle elevation layer a couple of times to see the problem.

Link to Demonstration

~https://jsbin.com/qeverim/edit?html,output~

https://jsbin.com/yowixah/1/edit?html,output

Expected Behavior

The image should always fully render into the layer

Actual Behavior

The image is cropped at high zooming levels.

danielfaust avatar Aug 28 '22 12:08 danielfaust

Thx for bugreport, i looks like that only the center-coordinate of the image is rendered onto terrain. When looking into draw_raster for ImageSource a boundsBuffer object is transfered to the shader. But because terrain-tile rendering stops on tile-borders this looks not sufficient. In this case i think a list of terrain-tiles, calculated by the source.coordinates, must be rendernd. Currently i not have the time to look further onto this, anyone else?

prozessor13 avatar Aug 29 '22 04:08 prozessor13

I'll see if this is something I can try and tackle, sounds fairly straight forward from your deception.

HarelM avatar Aug 29 '22 04:08 HarelM

Unfortunately I'm not making any progress :-( I've created the following debug page in order to be able to better debug this issue: Clicking zoom one time shows the issue and allows changing the code using npm run start-debug to see the changes in the code take effect on the page. I looked at the draw_raster tileIDs parameter and it is the same between with and without terrain (it's probably related to boundsBuffer as you said, but I don't know how to make some thing out of it). I guess I just am not familiar enough with the code and where to look for the issue... :-( Leaving it to someone else, if someone is up for it.

<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Map with image source clipped</title>
    <script src="../../dist/maplibre-gl-dev.js"></script>
    <link href="../../dist/maplibre-gl.css" rel="stylesheet" />
    <style>
      body { margin: 0; padding: 0; }
      #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
  </head>

  <body>
    <div id="map"></div>
    <script>
      const center = [9.3948749, 48.4968956];
      var la_t_tile = center[1] + 0.025;
      var lo_l_tile = center[0] - 0.035;
      var la_b_tile = center[1] - 0.025;
      var lo_r_tile = center[0] + 0.035;
      const map = new maplibregl.Map({
        container: 'map',
        hash: true,
        style: {
            version: 8,
            sources: {
                image: {
                    type: 'image',
                    url: 'https://i.imgur.com/3IyX7CK.png',
                    coordinates: [
                        [lo_l_tile, la_t_tile],
                        [lo_r_tile, la_t_tile],
                        [lo_r_tile, la_b_tile],
                        [lo_l_tile, la_b_tile]
                    ]
                },
                terrain: {
                    "type": "raster-dem",
                    "url": "https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL",
                    "tileSize": 256,
                }
            },
            layers: [{
                'id': 'image',
                'type': 'raster',
                'source': 'image',
            }],
            terrain: {
                source: "terrain",
                exaggeration: 1,
            }
        },
        center: center,
        zoom: 11.5,
      });
      
      map.addControl(new maplibregl.NavigationControl({
        visualizePitch: true,
        showZoom: true,
        showCompass: true
      }));

      map.on('load', () => {
        map.showTileBoundaries = true;
      });
    </script>    
  </body>
</html>

HarelM avatar Sep 11 '22 20:09 HarelM

Seems I am having the same issue, so I will post my demo links here and close the issued #1979 I opened.

and the following appears in console: 83209271-e541-4d31-9f74-52a1663491ad:1 Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true. See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently

Link to Demonstration without 3D https://climaya.com/maplibre/mapl.html?url=https://nube1.on.gt/icon/min_temp_72.png&lon=-98&lat=17&z=4.6&p=28.5&x1=-120.125&x2=-49.875&y1=-0.125&y2=35.125&opacity=0.5

With 3D enabled image overlay does not show complete and opacity is not respected. https://climaya.com/maplibre/mapl.html?url=https://nube1.on.gt/icon/min_temp_72.png&lon=-98&lat=17&z=4.6&p=28.5&x1=-120.125&x2=-49.875&y1=-0.125&y2=35.125&opacity=0.5&d3=1

navilaufm avatar Dec 22 '22 23:12 navilaufm

Assigned L bounty. Link to parent Bounty: https://github.com/maplibre/maplibre/issues/189

HarelM avatar Mar 03 '23 20:03 HarelM

Hi @HarelM. Can I assign this issue?

manhcuongincusar1 avatar Mar 16 '23 13:03 manhcuongincusar1

Sure, please do.

HarelM avatar Mar 16 '23 13:03 HarelM

Thanks

manhcuongincusar1 avatar Mar 16 '23 13:03 manhcuongincusar1

Hi @HarelM after investigating, it seems that the partial rendering problem occurs when the image spans multiple tiles but only one tile's terrain data is utilized, leading to incorrect rendering: https://github.com/maplibre/maplibre-gl-js/blob/1cbb496f5cdf6751475986562eb0067c3ace7c4b/src/render/draw_raster.ts#L64

I think this issue is quite complicated. Can you consider to extend the bounty of this issue?

(In this screenshot, the photo belongs to tile 11/1088/718. If terrain is enabled, only the partial area inside the red rectangle will be rendered. Only at lower zoom levels (higher elevation) where the image fits into a single terrain tile. At those levels, the image will be rendered fully.) Screenshot 2023-06-29 at 13 49 57

manhcuongincusar1 avatar Jun 29 '23 07:06 manhcuongincusar1

I upgraded the demo to v3.1.0 and replaced the defunct Maptiler tiles & terrain with the demotiles from this project

https://jsbin.com/yowixah/1/edit?html,output

danielfaust avatar Jun 29 '23 09:06 danielfaust

@danielfaust I think this problem will happen with all of un-tiled data sources like Image, Heat maps #1081, 3D meshes (which not loaded from a tiled dataset). Mapbox solved this problem by utilizing ProxiedTileId class to map actual tiles to others terrain tiles to solve the problem in source cache level. That is promising idea other way is update function getTerrainData() to return postMatrix from all spanned tiles.

manhcuongincusar1 avatar Jun 29 '23 10:06 manhcuongincusar1

Only at lower zoom levels (higher elevation) where the image fits into a single terrain tile.

I wanted to say: "I don't think that this is correct. All tiles have a size of 256 and this is visualized by the red grid". But then I navigated to the border of the terrain tiles ( https://output.jsbin.com/yowixah/1#12.39/46.99612/11.39178/62.4/29 ) and noticed that these terrain tiles apparently do not match the 256 grid of OSM raster, as if they were offset. Or maybe the terrain tiles are just cut in an odd way.

Here's another view which might be helpful: https://output.jsbin.com/yowixah/1#11.38/47.1349/11.9827/-9.6/60 if you zoom out and in a bit it's odd that the terrain apparently does not seem to change in resolution when the OSM raster is changed. Maybe this could help.

I added a second image at the right border of the terrain tile boundary ( https://output.jsbin.com/yowixah#10.79/47.2697/11.7186 ) , maybe analyzing that tile could help ( https://output.jsbin.com/yowixah#10.13/47.1797/11.8557/-19.2/57 )

danielfaust avatar Jun 29 '23 10:06 danielfaust

Thank you for the sample code @danielfaust, I will ping you here when there's progress

manhcuongincusar1 avatar Jun 29 '23 10:06 manhcuongincusar1

@danielfaust Do have any ideas about the problem? I would love to here from you?

manhcuongincusar1 avatar Jun 29 '23 10:06 manhcuongincusar1

@manhcuongincusar1 I have absolutely no idea what the problem is.

Here's a modified demo with 2 animated source images.

https://jsbin.com/juwikuq/4/edit?html,output

danielfaust avatar Jun 29 '23 10:06 danielfaust

With a bigger radius: https://jsbin.com/juwikuq/7/edit?html,output

It reminds me of another issue regarding of which scale of tiles is shown at which zoom level, apparently there is a mismatch. And since here every second tile is affected, it seems to suggest that the image is living in another zoom level or something like that.

I'm unable to find the issue number, but I think that it was 2d-related as well.

Off Topic, but maybe related: https://github.com/maplibre/maplibre-gl-js/issues/1077 because at the moment when the image center crosses a tile boundary (of the non-visible zoom level), it flickers.

danielfaust avatar Jun 29 '23 11:06 danielfaust

Thank for the reference @danielfaust

manhcuongincusar1 avatar Jun 29 '23 11:06 manhcuongincusar1

What bounty did you had in mind?

HarelM avatar Jun 29 '23 11:06 HarelM

What bounty did you had in mind?

I suggest XL @HarelM

manhcuongincusar1 avatar Jun 29 '23 12:06 manhcuongincusar1

Done.

HarelM avatar Jun 29 '23 12:06 HarelM

@HarelM thank you very much

manhcuongincusar1 avatar Jun 29 '23 12:06 manhcuongincusar1

Added some controls to modify size and other things: https://jsbin.com/bucosel/2/edit?html,output

danielfaust avatar Jun 29 '23 14:06 danielfaust

I wanted to say: "I don't think that this is correct. All tiles have a size of 256 and this is visualized by the red grid". But then I navigated to the border of the terrain tiles ( https://output.jsbin.com/yowixah/1#12.39/46.99612/11.39178/62.4/29 ) and noticed that these terrain tiles apparently do not match the 256 grid of OSM raster, as if they were offset. Or maybe the terrain tiles are just cut in an odd way.

I was looking at this , and you are using the demo terrain tiles I made at https://github.com/maplibre/demotiles/tree/gh-pages/terrain-tiles .

One thing to note is the demo terrain are 512x512 sized tiles, so if you are setting them to 256 it could cause issues like over zooming I think. rio rgbify, which is used there, makes 512px tiles.

The second thing is this extract is based on a single DEM image of the N047E01 area. The edges are just based off the source file used from JAXA AW3D30, not based on tiles edge, so it is possible the tile could be bigger than the image

acalcutt avatar Jun 29 '23 14:06 acalcutt

Is this a question about the behavior of image source or this bug? It is unclear from what you wrote. If this is not related to this bug please open a different discussion or issue.

HarelM avatar May 10 '24 20:05 HarelM

This doesn't answer my question. Please open a different issue or discussion and delete the comments from this big report.

HarelM avatar May 13 '24 19:05 HarelM

Hi, added a PR with failing test and example for the repo. But I am stuck, too. I do not understand how the wrap of OverscaledTileID works as described in the PR. Currently the tiles with wrap -1 and 1 fail in my case. But actually, moving the coordinate of my cat image example from 11.43085 to 11.45085 returns the same tiles, but the cat is visible. So even the wrap part might not be the issue. What I know though, getting the source of the terrain for the image goes up zoom levels from 11 to 5 if it finds a source, otherwise up to 0 with the terrain tile being undefined.

grafik

https://github.com/maplibre/maplibre-gl-js/blob/1cbb496f5cdf6751475986562eb0067c3ace7c4b/src/source/terrain_source_cache.ts#L175

My guess is, that the tileID.scaledTo(z--) function in the while loop is not getting the correct parent tile when the tileID is wrapped. I am wondering if the parent for an image should be different, than for normal layers. But I am stuck, since I do not really understand, what wrap is used for. I only can see that it leads to different key generation for OverscaledTileID.

I would also expect the correct terrain tile data to be available, since it is rendered in hillshading.

Please correct me on this assumption or explain, how to understand wrap, but might this be happening?

dennemark avatar Jun 14 '24 10:06 dennemark

Image source bounds are bigger than its canonical tile. But it seems only the canonical tile is loaded when reading from its source_cache. Therefore only that tile will be rendered.

Image_source has this tile loaded with wrapped tiles ( 1 and -1 wrapped tiles are used on the edges of the map, to continue rendering it left and right side of a world map, i believe). And it stores it on its this.tiles[0/-1/1]. The loadTile function is called from source_cache from here

https://github.com/maplibre/maplibre-gl-js/blob/9b139a8e158224597661f4cf43f9963e62203a99/src/source/source_cache.ts#L857

And it should be called only once, otherwise the url will be loaded multiple times. That's why it would be great to have a proxy to this tile, since we can then acces the tile with a different tileID. We would need to do this for all tiles that are within the image_source bounds. I tried to do it hardcoded but did not succeed.

 if (!tile) {
            tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor());

            this._loadTile(tile, tileID.key, tile.state);
        }

        tile.uses++;
        this._tiles[tileID.key] = tile;
// new code
 if(this._source.type === 'image' && tileID.key !== 'vhvkbb'){
            const clone = tileID.clone()
            clone.canonical.x--
            clone.key = 'vhvkbb' // key of the missing tile with x: 1088 in my example file

            const proxy = new Proxy(tile, {
                get(target, prop, receiver) {
                    if(prop === "tileID"){
                        return clone
                    }
                    return target[prop]
                }
            })
            this._tiles['vhvkbb'] = proxy
        }

If I check the coords arriving in draw_raster, the 'vhvkbb' tile id is missing. And the reason seems to be somewhere in render_to_texture prepareForRender function. The terrain tries to get all renderable tiles, but somehow the 'vhvkbb' tile is missing.

I have to pause my exploration here. Hope someone can pick it up.

dennemark avatar Jun 14 '24 16:06 dennemark

@manhcuongincusar1 are you still working on this issue or can I remove your assignment?

HarelM avatar Jun 14 '24 22:06 HarelM

@HarelM, please remove me from this issue. I don’t have time for it right now. Sorry about that.

manhcuongincusar1 avatar Jun 15 '24 00:06 manhcuongincusar1

In https://jsbin.com/yutuyes/2/edit?html,output the map is updated to 4.5.0. The image oddly sticks ("bakes in") to some tiles, the entire 4.x-range has this problem.

danielfaust avatar Jun 27 '24 10:06 danielfaust