protomaps / protomaps-leaflet

Lightweight vector map rendering + labeling and symbology for Leaflet
https://protomaps.com/docs/frontends/leaflet
BSD 3-Clause "New" or "Revised" License
764 stars 43 forks source link

protomaps leaflet layer making multiple calls to backend on render? #133

Closed csbrown closed 9 months ago

csbrown commented 9 months ago

In some situations, the protomaps leaflet layer seems to issue multiple calls to the given url when rendering. The results are generally different, and have very strange coordinates.

I have reduced this to the smallest example I could manage (attached) and have searched through the protomaps-leaflet code, but do not see any obvious place to start debugging this. The example.zip contains the (tippecanoe-created) vector tiles I am testing against.

example.zip

For ease of reference, here is the script verbatim from the example.zip index.html file:

        const map = L.map('map').setView([0, 0], 0);

        class CountySymbolizer {
            draw(context,geom,z,feature) {
                if (feature.props.NAME == "Maui") {
                  console.log(geom)
                }
                context.fillStyle = "dodgerblue";
                context.strokeStyle = "black";
                context.beginPath()
                for (var poly of geom) {
                    for (var p = 0; p < poly.length; p++) {
                        let pt = poly[p]
                        if (p == 0) 
                          context.moveTo(pt.x,pt.y)
                        else 
                          context.lineTo(pt.x,pt.y)
                    }
                }
                context.fill()
                context.stroke()
            }
        }

        let PAINT_RULES = [
            {
                dataLayer:"GEOID",
                symbolizer:new CountySymbolizer()
            } 
        ]

        let URL = "county/cb_2018_us_county_20m.pmtiles";
        protomapsL.leafletLayer({
            url:URL,
            paint_rules: PAINT_RULES,
            label_rules: [],
            maxZoom:22,
            maxDataZoom:22,
            noWrap: true,
        }).addTo(map)

If you take a look into the draw method on the symbolizer, there is a console log that logs info about Maui County specifically (so if you want to use this example to investigate, zoom in on Maui). Sometimes, this renders once. Sometimes, it renders mutliple times, depending on unknown causes. The multiple renderings seem to be caused by multiple get requests to the .pmtiles url, but I am unable to read the payload.

Here is an example of the output of the console log resulting from a single zoom event:

Screenshot 2024-02-06 at 10 49 00 AM

Note the extremely large x coordinates in the second geometry.

I have actually reduced this from a more complex example in our production application. In that instance, I am witnessing up to four calls to the .pmtiles url, some have abnormally large x, some abnormally large y and some abnormally large x and y.

bdon commented 9 months ago

This looks expected because every 256x256 screen tile is drawn independently, and the feature detection is buffered to implement line stroking without edge artifacts. if Maui is close to the corner of a tile the symbolizer could be called up to 4 times.

I don't see any duplicate calls to the backend in my network tab. Do you see any visual errors?

Screenshot 2024-02-07 at 11 28 34
csbrown commented 9 months ago

Ah. I am calculating centroids for painting in my application. It seems that when polygons are split over tiles, I will need to do something interesting.

Did you notice the large coordinate values? Are those modulo the map size in some way?

bdon commented 9 months ago

Yes, the internal data storage for tiles is on a 1024x1024 grid, and features can exist slightly outside of 1024 depending on the buffer size in at tile creation (tippecanoe).

It is not generally possible to determine correct polygon centroids on tiled data, because a polygon may be clipped at tile edges. You may be able to take advantage of Tippecanoe features to pre-calculate centroids and store those as points in the tileset.

csbrown commented 9 months ago

"I don't see any duplicate calls to the backend in my network tab"

I see that your network tab shows a "byte range" - the latest two requests were for adjacent byte ranges. Is it normal to have multiple requests on a zoom event, then?

Are you using some sort of tool to help debug this? Your screenshot looks like it includes a lot of extremely helpful information. :)

bdon commented 9 months ago

when you create a leafletLayer pass debug: "blue" or any color and it draws additional information on top

For a single zoom level it will make as many tile data requests as # of tiles needed to cover your screen. The defaults in v1.x are a single data tile (request) covers 1024x1024 screen pixels. In 2.x this will change to 512x512 screen pixels. So for a browser window taking up part of the screen there could be 6 requests. For fullscreen on a 32 inch monitor it could be more like 12. there are also 1-4 additional requests of directory fetch overhead.

csbrown commented 9 months ago

Thanks, this has all been extremely helpful.