Interactive Map with clickable countries

Hello, hope that this is not a redondant topic, I searched for similar things but did not find what I was looking for.

I would like to make an interactive world map with Dash : the aim is to see every country, some are highlighted (with a custom variable), and the highlited ones must be clickable.

In other terms I have a list of countries and I want to see them in a global map, and if I click on one of them it should make a call.

I have for now no idea how to get the map, how to make buttons out of countries nor how to display it.

What are the best tools/procedures/tutorials to do that ? I am a bit aware of Dash but not for maps and so.

Many thanks,

Bill

You could take a look at the Dash Leaflet package. I recently added a GeoJSON component, which might suit you needs.

1 Like

Hi, thanks for the advice ! Could you be a bit more explicit on how to link the elements, from the GeoJSON component (can I convert a geopandas map to geoJSON ?) to the dash graph please ?

Hi Emil, thanks to your advice and the examples found on the Dash-Leaflet page I managed to get an interactive map. I have a question though, I do not find the way to modify the children property of the map : I would like to add Markers (using the Markers and Clusters example in the page), but the marker list should come from a callback -> typically a dropdown callback : you select Europe in a dropdown, the result of the callback is the list of the European countries with the markers associated, but I do not achieve to put this list of markers in the Map children.
In the example the Map has a “cluster” in a list of children, and I would like to modify this component with a callback. Could you help me with that please ?

Thanks in advance,
Bill

Hi Bill,

You can use e.g. the LayerGroup component as an intermediate container and target its children property with the callback. Here is a small example,

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_leaflet as dl

from dash.dependencies import Output, Input

# Marker options.
positon_map = {"Nordics": [(56, 10), (57, 12), (55, 8)], "Asia": [(25, 135), (22, 140), (30, 120)]}
options = [{"label": key, "value": key} for key in positon_map]
# Create app.
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([dl.Map([dl.TileLayer(), dl.LayerGroup(id="container")]), dcc.Dropdown(options=options, id="dd")],
                      style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"})


@app.callback(Output("container", "children"), [Input("dd", "value")])
def update_markers(value):
    return dl.MarkerClusterGroup([dl.Marker(position=pos) for pos in positon_map[value]], id=value)


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

Thank you very much for the help and example ! And also for the very quick answer, that helps me a lot. I can’t wait to try that

Hi Emil, sorry to bother you again, I have another question about the markers. I would like to change the color of the marker based on an information about a country, resulting of a callback (with the position of the marker) for example. I found that the argument “icon” should allow that, but did not manage to understand how to use this argument in python, only examples that I find are for react.js. Do you have any example where I can find a syntax to modify the “icon” of a marker ?

Thanks,
Bill

Hi Bill,

You should be able to pass a dict that matches the Leaflet icon constructor. Here is a small example,

import dash
import dash_html_components as html
import dash_leaflet as dl

# Setup icon options, example from Leaflet doc (https://leafletjs.com/examples/custom-icons/).
icon = {
    "iconUrl": 'https://leafletjs.com/examples/custom-icons/leaf-green.png',
    "shadowUrl": 'https://leafletjs.com/examples/custom-icons/leaf-shadow.png',
    "iconSize": [38, 95],  # size of the icon
    "shadowSize": [50, 64],  # size of the shadow
    "iconAnchor": [22, 94],  # point of the icon which will correspond to marker's location
    "shadowAnchor": [4, 62],  # the same for the shadow
    "popupAnchor": [-3, -76]  # point from which the popup should open relative to the iconAnchor
}
# Create example app.
app = dash.Dash()
app.layout = html.Div([
    dl.Map([dl.TileLayer(), dl.Marker(position=(56, 10), icon=icon)],
           id="map", style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}),
])

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

Note that all the arguments are not mandatory; in principle it’s enough just to pass the iconUrl (if you don’t need custom anchors, shadow, etc.).

Hi Emil, thank you very much again, I could not hope for a best example !
Have a nice day,
Bill

Hi Emil, I could use your help once more. I would like to disable the scroll wheel zoom, i.e. a fixed map in which you can not zoom in and zoom out.

I tried to pass an argument like this “scrollWheelZoom=False” but it does not seem to change anything.

Is there another argument/workaround to achieve this ?

Thank you,
Bill

Hi Bill,

I haven’t tried it, but i guess that if you set minZoom and maxZoom equal to the same value, zooming will effectively be disabled.

\emher

Thank you ! Indeed the zoom is disabled. But it induces a sort of graphical bug, all the map becomes grey except for the boundaries specified in the GeoJson. Removing the minZoom and maxZoom reset to normal display. Ever had this problem ?

No, i haven’t experienced anything like that. Could you post an MWE? If i just use a simple standard map, it works as expected,

import dash
import dash_leaflet as dl

app = dash.Dash()
app.layout = dl.Map(dl.TileLayer(), zoom=5, minZoom=5, maxZoom=5, style={'width': '1000px', 'height': '500px'})

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

Sorry I did not prepare an MWE, I will see if I have time this evening. But I can describe more precisely the problem :
this is my object :

dl.Map(id=‘map’,children=[dl.TileLayer(), geojson, info, dl.LayerGroup(id=“container”),
dl.LayerGroup(id=“container2”)], center=[30, 0], zoom=2.5, minZoom=2.5, zoom=3.55, noMoveStart=True, animate=False, bounds = [[65, -30],[-40, 40]] )

Now how it happens : the problem lies in the minZoom argument -> before 2 no problem, I can set any number. But >2 I can only set 2.5, 3, 3.5 etc. Anything else like 2.3 will occur as a grey map (pictures below). I can live with that so no problem if there is no fix or if the problem is only local, but if it bothers you I can surely provide an MWE.

I always fix the zoom argument to the same value as the minZoom.


Thanks,
Bill