Nested Maps with Dash-Leaflet

A quick guide to improving Map Visualization.

Rodrigo И. Gonzalez
MCD-UNISON
3 min readDec 1, 2022

--

Photo by Adolfo Félix on Unsplash

A geospatial dashboard is a fantastic way to tell our data story, but what happens when we have too much information in a limited space? Disastrous and unreadable maps overwhelm the view.

Nested interactive maps are ideal for presenting distinct levels of our geodata. Therefore, let’s go step-by-step into the creation of our dashboard using Dash-Leaflet. We will use a dataset of the Mexican territory and its divisions provided by the National Institute of Statistics and Geography (INEGI).

You can find more about the dataset and its download link here.

Importing geospatial data

Even though it is possible to use georeferenced datasets without taking their variables as geometry, it is essential to work with a package to take full advantage of this data type.

GeoPandas allows us to extract records from geospatial format files (such as KML, Shapefile, or GeoJSON) and store them in a GeoDataFrame object (a pandas.DataFrame that has a column with geometry). You can learn more about the library in this blog tutorial.

That being said, let’s start importing the libraries and reading our geospatial files:

from dash import Dash, html
from dash.dependencies import Input, Output
import dash_leaflet as dl
import geopandas as gpd
import json

mexico_shape=gpd.read_parquet('data/mexico_shape.parquet')
estados_shape=gpd.read_parquet('data/estados_shape.parquet')
municipios_shape=gpd.read_parquet('data/municipios_shape.parquet')

Dash-Leaflet Components

Once importing the libraries, let’s define the components that will be shown in the dashboard. Dash-leaflet allows using elements from our map as callbacks (via click_feature property), which increases the interactivity of the map and the dashboard.

dl.Map(children=[
dl.TileLayer(url=tile_url),
dl.GeoJSON(
click_feature=None,
zoomToBoundsOnClick=False,
id='shapes')],
id='map',
center=[23.634501, -102.552784],
zoom=5,
zoomControl=False,
doubleClickZoom=False,
dragging=False,
scrollWheelZoom=False,
preferCanvas=True)

The GeoJSON component must be left without any data parameter, it will be from the callback that will be assigned a data frame to display. This is the code in addition to the other dash components:

app.layout = html.Div([
html.Div([
html.H1(children='MEXICO TREEMAP'),
html.Div([
dl.Map(children=[
dl.TileLayer(url=tile_url),
dl.GeoJSON(
click_feature=None,
zoomToBoundsOnClick=False,
id='shapes'),
html.Button('↩', id='btn', n_clicks=0, className='btn')],
id='map',
center=[23.634501, -102.552784],
zoom=5,
zoomControl=False,
doubleClickZoom=False,
dragging=False,
scrollWheelZoom=False,
preferCanvas=True)],
id='map_div', className='map_div')],
className='main_div')],
className='container')

So far, the app should look something like this:

Integrating callbacks

Following, we define some dash callbacks that will allow us to rewrite the parameters in the GeoJSON component. Thus, using the click_feature property as input, we can obtain the value of the previously selected element, accessing a specific shape and being able to display its nested map. To go back to the higher levels, let’s add a button to perform this function.

@app.callback(Output('shapes', 'data'),
Output('shapes', 'zoomToBoundsOnClick'),
Input('shapes', 'click_feature'))
def shape_clicked(feature):
if feature is not None:
if feature['properties']['SHP_TYPE']==0:
return json.loads(estados_shape.to_json()), True
else:
return json.loads(municipios_shape[municipios_shape['CVEGEO']==feature['properties']['CVEGEO']].to_json()), True
else:
return json.loads(mexico_shape.to_json()), False

@app.callback(
Output('shapes', 'click_feature'),
Output('map', 'center'),
Output('map', 'zoom'),
Input('btn', 'n_clicks'))
def displayClick(btn):
return None, [23.634501, -102.552784], 5

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

Finally, integrating this last section of code, the dashboard will look as follows:

You can access the full code, assets, and datasets through this repository.

--

--