pythonplotlymapboxplotly-dash

Plotly mapbox : Get the geometry of current view / zoom level


I am rendering a scattermapbox using plotly in my Flask / Dash Application. I have set a default zoom level and lat,long coords. As the user pans the map via the plotly interface and changes the zoom level, I'd like to update the map with points and data layer.

Here's some code for reference:

import pandas as pd
import geopandas as gpd

# import mapbox
import requests
import plotly.graph_objects as go

# Update with host url
df_geo = gpd.GeoDataFrame.from_features(
    requests.get(
        "https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_050_00_20m.json"
    ).json()
)

import plotly.graph_objects as go

fig = go.Figure(
    go.Choroplethmapbox(
        geojson=df_geo.set_index("GEO_ID")["geometry"].__geo_interface__,
        locations=df_geo["GEO_ID"],
        z=df_geo["CENSUSAREA"],
        autocolorscale=False,
        colorscale="Viridis",
        zmin=df_geo["CENSUSAREA"].min(),
        zmax=df_geo["CENSUSAREA"].quantile(0.95),
        marker_line_width=0,
        colorbar={"orientation": "h", "x": 0.5, "yanchor": "middle", "y": 0.1},
    )
)


fig.update_layout(
    mapbox_style="carto-positron",
    # mapbox_accesstoken=token,
    mapbox_zoom=3,
    mapbox_center={"lat": 37.0902, "lon": -95.7129},
    margin={"r": 0, "t": 0, "l": 0, "b": 0},
)

fig.show()

My question is, how do I obtain the geometries / bbox or lat,longs coords of the current map view?

Links to docs for reference:

https://plotly.com/python/reference/#scattermapbox

https://dash.plotly.com/dash-core-components/graph


Solution

  • import pandas as pd
    import geopandas as gpd
    import requests
    import plotly.graph_objects as go
    from jupyter_dash import JupyterDash
    import dash
    from dash.dependencies import Input, Output, State
    import json
    
    # Update with host url
    df_geo = gpd.GeoDataFrame.from_features(
        requests.get(
            "https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_050_00_20m.json"
        ).json()
    )
    
    fig = go.Figure(
        [
            go.Choroplethmapbox(
                geojson=df_geo.set_index("GEO_ID")["geometry"].__geo_interface__,
                locations=df_geo["GEO_ID"],
                z=df_geo["CENSUSAREA"],
                autocolorscale=False,
                colorscale="Viridis",
                zmin=df_geo["CENSUSAREA"].min(),
                zmax=df_geo["CENSUSAREA"].quantile(0.95),
                marker_line_width=0,
                name="choropleth"
                # colorbar={"orientation": "h", "x": 0.5, "yanchor": "middle", "y": 0.1},
            ),
            go.Scattermapbox(
                name="scatter", marker={"size": 30, "color": "red", "opacity": 1}
            ),
        ]
    )
    
    fig.update_layout(
        mapbox_style="carto-positron",
        # mapbox_accesstoken=token,
        mapbox_zoom=3,
        mapbox_center={"lat": 37.0902, "lon": -95.7129},
        margin={"r": 0, "t": 0, "l": 0, "b": 0},
        datarevision=0,
        height=300,
        width=600,
        autosize=False,
    )
    
    # Build App
    app = JupyterDash(__name__)
    app.layout = dash.html.Div(
        [
            dash.dcc.Checklist(
                options=[{"label":"refesh", "value":"yes"}],
                id="refresh",
            ),
            dash.dcc.Graph(id="mapbox_fig", figure=fig),
            dash.html.Div(
                id="debug_container",
            ),
            dash.dcc.Store(
                id="points-store",
                data={
                    "lat": [],
                    "lon": [],
                },
            ),
        ]
    )
    
    
    @app.callback(
        Output("points-store", "data"),
        Output("debug_container", "children"),
        Input("mapbox_fig", "relayoutData"),
        Input("refresh","value")
    )
    def mapbox_cb(mapbox_cfg, refresh):
        try:
            refresh = refresh[0]=="yes"
        except Exception:
            refresh = False
        if mapbox_cfg and "mapbox.zoom" in mapbox_cfg.keys() and refresh:
            bbox = np.array(mapbox_cfg["mapbox._derived"]["coordinates"])
            # bbox = bbox * .8
            data = {
                "lon": bbox[:, 0].tolist() + [mapbox_cfg["mapbox.center"]["lon"]],
                "lat": bbox[:, 1].tolist() + [mapbox_cfg["mapbox.center"]["lat"]],
            }
    
            return data, [
                dash.html.Pre(json.dumps(mapbox_cfg, indent=2)),
                dash.html.Pre(json.dumps(data, indent=2)),
            ]
        else:
            raise dash.exceptions.PreventUpdate
    
    
    app.clientside_callback(
        """
        function(data, fig) {
            fig.data[1]['lat'] = data['lat'];
            fig.data[1]['lon'] = data['lon'];
            fig.layout.datarevision = fig.layout.datarevision + 1;
            /* return fig; */
            return JSON.parse(JSON.stringify(fig)); 
        }
        """,
        Output("mapbox_fig", "figure"),
        Input("points-store", "data"),
        State("mapbox_fig", "figure"),
    )
    
    app.run_server(mode="inline")