Skip to content
Advertisement

Inset Maps in Plotly (Python)

I am currently trying to plot a few maps by using Plotly Express and Mapbox in Python.

I need to have a big map with the country and, next to it, an inset map with a “zoom” to a particular region (see Image attached as an example) INSET MAP EXAMPLE.

I managed to plot the bigger map (i.e. the whole country) but can’t find a proper way to create the inset map.

I tried to make 2 subplot (1 row, 2 cols), but did not work as expected. Also tried to plot two figures (one bigger, the whole country, and one smaller, the inset map) at the same time, but the two of them “collide” (the inset map turns out to be on top of the other one).

Any idea or possible turnaround for this?

Thank you.

Advertisement

Answer

  • have used dash to enable callbacks from main choropeth to update inset choropeth (The way I have structured dash HTML means it is outset…)
  • to bring it all together I have used some geo mapping data from UK government sources
  • have just modified center of choropeth in callback. Could also modify zoom and/or range

get some geo data

import requests
import geopandas as gpd
import pandas as pd

# https://ons-inspire.esriuk.com/arcgis/rest/services/Administrative_Boundaries/Local_Authority_Districts_December_2017_Boundaries/MapServer/3/query?where=1%3D1&outFields=*&outSR=4326&f=json
# res = requests.get("https://opendata.arcgis.com/datasets/ae90afc385c04d869bc8cf8890bd1bcd_3.geojson")
res = requests.get(
    "https://opendata.arcgis.com/datasets/69dc11c7386943b4ad8893c45648b1e1_0.geojson"
)
# use 2019 so Buckinghamshire is included
rescounties = requests.get(
    "https://opendata.arcgis.com/datasets/37363d379f4f40fa8c3f0d28eedfdd37_0.geojson"
)
gdf = pd.concat(
    [
        gpd.GeoDataFrame.from_features(res.json()["features"], crs="CRS84")
        .pipe(lambda d: d.rename(columns={c: c.lower() for c in d.columns}).drop(columns="lad20nmw"))
        .rename(columns={"lad20cd": "areaCode", "lad20nm": "areaName"}),
        gpd.GeoDataFrame.from_features(rescounties.json()["features"], crs="CRS84")
        .pipe(lambda d: d.rename(columns={c: c.lower() for c in d.columns}))
        .rename(columns={"cty19cd": "areaCode","cty20cd": "areaCode","cty19nm": "areaName","st_areashape": "shape__area",
                         "st_lengthshape": "shape__length",}),
    ]
).set_index("areaCode").assign(country=lambda d: d.index.str[0])

build base figure

import plotly.express as px
fig = px.choropleth_mapbox(
    gdf,
    geojson=gdf.geometry,
    locations=gdf.index,
    hover_name="areaName",
    color="country",
    color_discrete_sequence=["green", "yellow", "orange", "red", "magenta"],
    center={"lat": (gdf.total_bounds[1] + gdf.total_bounds[3])/2, "lon": (gdf.total_bounds[0] + gdf.total_bounds[2])/2},
    mapbox_style="carto-positron",
    zoom=5,
).update_layout(margin={"l":0,"r":0,"b":0,"t":0})

dash app

import json
import numpy as np
import plotly.graph_objects as go
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc

import dash_table
from dash.dependencies import Input, Output, State
from jupyter_dash import JupyterDash

ifig = go.Figure(fig.data).update_layout(fig.layout).update_layout(showlegend=False)

# Build App
app = JupyterDash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div(
    [
        dbc.Row(
            [
                dbc.Col(
                    dcc.Graph(id="map", figure=fig, style={"height": "90vh"}),
                    width=9,
                ),
                dbc.Col(
                    dcc.Graph(id="insetmap", style={"height": "25vh"}),
                    width=3,
                ),
            ]
        ),
    ],
    style={"font-family": "Arial", "font-size": "0.9em"},
)


@app.callback(
    Output("insetmap", "figure"),
    Input("map", "selectedData"),
)
def mapSelect(selectData):
    global ifig
    if selectData and "range" in selectData.keys():
        r = selectData["range"]["mapbox"]
        ifig = ifig.update_layout(
            mapbox={
                "center": {
                    "lon": (r[0][0] + r[1][0]) / 2,
                    "lat": (r[0][1] + r[1][1]) / 2,
                }
            }
        )
        print(ifig.layout["mapbox"])
        return ifig
    return ifig


# Run app and display result inline in the notebook
app.run_server(mode="inline")

output

enter image description here

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement