emilhe / dash-leaflet

MIT License
213 stars 37 forks source link

Bug: Updating TileLayer via callback with non-integer zoom generates failing tiles requests #236

Open Farkites opened 4 months ago

Farkites commented 4 months ago

Description

When the zoom value is not an integer and the dl.TileLayer is updated via callback the initial tiles requests are wrong and therefore failing. The URL for these requests is using the zoom value instead of it corresponding zoom level, which should be the closest integer.

For example, if the zoom is set to 17.5 and the children prop is updated via callback to a different dl.TileLayer the first requests would be https://b.tile.openstreetmap.org/17.5/52778/63857.png (which is shows an invalid tile error) when it should instead be https://b.tile.openstreetmap.org/18/52778/63857.png

After these failed requests if the zoom value changes it then generates the correct URLs for the tiles requests.

Reproduce the bug running the code below:

import dash
from dash import html, dcc, callback, Input, Output
import dash_leaflet as dl

CARTO_TILES = dl.TileLayer(
    url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
    maxZoom=18,
    attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
)

def serve_layout():
    return html.Div(
        children=[
            "Tiles dropdown",
            dcc.Dropdown(
                id="tiles_dropdown",
                options=[{"label": x, "value": x} for x in ["OSM", "CARTO"]],
                value="CARTO",
            ),
            dl.Map(
                [CARTO_TILES],
                center=[45.519, -73.576],
                zoom=17.5,
                zoomSnap=0,
                style={"width": "100%", "height": "400px"},
                id="leaflet",
            ),
        ],
    )

app = dash.Dash(__name__)
server = app.server
app.layout = serve_layout

@callback(
    Output("leaflet", "children"),
    Input("tiles_dropdown", "value"),
    prevent_initial_call=True,
)
def update_tiles1(tiles):
    if tiles == "OSM":
        return dl.TileLayer()
    else:
        return CARTO_TILES

if __name__ == "__main__":
    app.run_server(debug=True)

Scenario 1: before changing zoom value

Scenario 2: using zoomControl tooltip to have integer zoom values

Demo

https://github.com/emilhe/dash-leaflet/assets/35932204/3e1489fe-d519-4ec9-81f9-2485e92b2d0c

Expected result

The zoom value shouldn't be used to build the tiles requests in any case as these requests are incorrect. It's closest zoom level should be used instead.