Show and Tell - Dash Leaflet

Hello @Emil
This is super useful. Thank you for sharing.
I do have a question and hoping to get some suggestions.
I would like to use the ImageOverlay to overlay an image on the map. However, my overlay image is regenerated based on changes to the map viewport (to show a different level of granularity and features as the user zoom or pan the map). Therefore I would like to ask how to use the image as PIL.Image instead of loading it from file or URL. Or perhaps you can suggest a work-around.
Thank you.

I have started working on an implementation of a VectorGrid component. However, as I understand (please correct me if I am wrong, @btucker) it supports the following two formats,

  • Either protobuf vector tiles (such as mapbox) + a style mapping (typically a rather long dict specifying how to style each type of element)
  • A data source (e.g. a GeoJSON file) which is sliced into tiles on-the-fly, and the resulting tiles are drawn on the map

I am not sure that any of these options would suit your usecase, @fallspinach? Do you have more information on how the tiles were generated and/or do you have the original GeoJSON data? If you do, slicing mechanism of the VectorGrid component might be a possible solution.

@lnguyen7 - One way to achieve this behavior would be to write the image to disk on a location from which it is served (such as the assets folder). To avoid caching issues, you should write to a unique file name each time (you could use e.g. an UUID).

That being said, it sounds like you are reinventing the tile rendering mechanism. Hence, what i would recommend you to do would be to use a tile server to serve the data instead. Don’t reinvent the wheel, when you don’t have to :slight_smile:

1 Like

Ah, I see. Much appreciated. :bowing_man:

Hi @Emil , in our use case, all the vector data are pre-sliced into many small tiles. The overall size at high zoom level is too big (gigabytes) and very hard to load or even to partially load.

I do have the original GeoJSON data and I sliced them into tiles myself. The original data are from the HydroBASINS database here: HydroSHEDS. They provide basin polygon shapefiles at different “levels”. Higher levels have more and smaller polygons. For example, the shapefile at the highest level is about 1.5G in size. I sliced them up and coarsened the lines with respect to the zoom level. This process itself is a bit tricky because I need to avoid breaking any polygon in any tile to make sure the metadata shows up correctly and the highlighting works correctly. I guess this could be done by some automatic tiling mechanism but I have never tried. BTW, I’m a hydrologist instead of a GIS professional - my way of doing things could be completely wrong or outdated. Anybody can know this better than me - please let me know if this could be done more properly.

@fallspinach I guess the best solution would then be to convert your current tiles to the mapbox format. It seems that the format you currently have is out of date, or at least not common. However, I am not a GIS professional either - I am a physicist :slight_smile:

Thanks so much Emil! Will try that!

Has anyone tried to implement click_feature callbacks with multiple layers? I have a map with a polygon style geojson that I’d like to include behind some markers that a user can click on and interact with. However, when dash-leaflet loads up, clicking on the points doesn’t trigger click_feature on the points. I have to toggle the both layers off using the layercontrol, then turn them both back on, making sure to turn on the markers last. After doing that, click_feature works as expected. Has anyone encountered this issue?

Very nice component @Emil.
It seems though as it doesn’t play that well in a dbc.Tabs environment. In the code below the map loads, but only a portion of it is shown. Zooming seems to update the small portion, so things are happening behind the scenes.
What’s more strange is that when I do an ‘Inspect element’ in the browser, suddenly the whole map loads and renders as expected.

Any tips or tricks to why this is happening?

Code

import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_leaflet as dl

app = dash.Dash(__name__, , external_stylesheets=[dbc.themes.BOOTSTRAP])

tab1_content = dbc.Card(
    dbc.CardBody(
        [
            html.H2("This is a heading"),
            html.P("Here we wright something"),
            dbc.Row(dl.Map(dl.TileLayer(maxZoom=20), style={
                    'width': '100%', 'height': '500px'}))
        ]
    )
)

tabs = dbc.Container([
        dbc.Tabs([
            dbc.Tab(tab1_content, label="Map in a tab-environment"),
        ])
    ])

app.layout = html.Div([
    tabs
])

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


@karosc - Per default, the markers are all written to a default pane. You could try wrapping the markers in Pane component. You should then be able to control the order via the zindex of the Pane.

@thilles - Thanks! I have seen this issue before. I believe it is because map container must be visible when the leaflet map is constructed in React. When you use the Tabs component, the map is initialized too early. I haven’t found the fix yet, but as a quick fix you can delay the initialization yourself via a callback, e.g. something like this

import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_leaflet as dl
from dash.dependencies import Output, Input

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
tab1_content = dbc.Card(
    dbc.CardBody(
        [
            html.H2("This is a heading"),
            html.P("Here we wright something"),
            dbc.Row(id="map")
        ],
        id="trigger")
)
tabs = dbc.Container([
    dbc.Tabs([
        dbc.Tab(tab1_content, label="Map in a tab-environment"),
    ])
])
app.layout = html.Div([
    tabs
])


@app.callback(Output("map", "children"), Input("trigger", "children"))
def draw_map(_):
    return dl.Map(dl.TileLayer(maxZoom=20), style={'width': '1000px', 'height': '500px'})


if __name__ == '__main__':
    app.run_server(debug=True, port=7778)
1 Like

Worked beautifully! Thanks very much for the quick response!

1 Like

hello @Emil,
There is way to change the markers colors that are generated using the function dl.GeoJSON()? or maybe should i create each maker independently with dl.Marker()?

Thanks for your advice,

Both is possible. How many markers do you have?

1 Like

The number of markers change depending of the option i choose from a drop down. but the maximum number of markers would be like 200.

I have been looking for a way to do it with dl.GeoJSON but i haven’t found any example :sweat_smile: or advice, so i would very happy thankfull if you can help me.

When you have more than 100 markers, i would recommend using the GeoJSON component for the best performance. However, it requires writing a small javascript snippet that creates the custom marker. Here is a small example where flags are used as markers.

import dash_html_components as html
import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash import Dash
from dash_extensions.javascript import assign

# A few countries.
countries = [dict(name="Denmark", iso2="dk", lat=56.26392, lon=9.501785),
             dict(name="Sweden", iso2="se", lat=59.334591, lon=18.063240),
             dict(name="Norway", iso2="no", lat=59.911491, lon=9.501785)]
# Generate geojson with a marker for each country and name as tooltip.
geojson = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in countries])
# Create javascript function that draws a marker with a custom icon, in this case a flag hosted by flagcdn.
draw_flag = assign("""function(feature, latlng){
const flag = L.icon({iconUrl: `https://flagcdn.com/64x48/${feature.properties.iso2}.png`, iconSize: [64, 48]});
return L.marker(latlng, {icon: flag});
}""")
# Create example app.
app = Dash()
app.layout = html.Div([
    dl.Map(children=[
        dl.TileLayer(), dl.GeoJSON(data=geojson, options=dict(pointToLayer=draw_flag), zoomToBounds=True)
    ], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}, id="map"),
])

if __name__ == '__main__':
    app.run_server()
1 Like

Thank a los, it looks like its working but the icons are not loading on the map.


Do you know what could be the problem? i use the same that is in your example code

Did you run the code as-is? Do you have the newest versions of dash-leaflet and dash-extensions?

I’m sorry i was missing the properties.iso2 in each feature, now is working just fine. Thanks again :).
If i want yo use my own marker.png, i should put the image in the same folder that the python file and put the name of the file in iconUrl? or should i put the full path for the image’s location?

Then you should add it to the assets folder and set the iconURL accordingly, i.e. something like /assets/marker.png. Or alternatively you can use an icon hosted elsewhere.

1 Like