Search code examples
pythonplotly

Drawing a circle on a map by given a centroid


I have a radius and centroid (lan=114, long=22) and I would like to plot a circle on a map to show the area impacted. On top of the circle, I want to add an arrow to show the radius. Something like this but on a map.

enter image description here

I found these codes from Plotly python Problem with adding shape to maps

I tried to modify it to my centroid but I am unfamiliar with plotting on a map as it isn't using the add_shape. Any help is appreciated.

import plotly.express as px
import geopandas as gpd
import numpy as np
import pandas as pd

df = px.data.election()

# prep geometry
gdf = gpd.GeoDataFrame.from_features(px.data.election_geojson())
gdf = gdf.join(
    gdf["geometry"].centroid.apply(lambda g: pd.Series({"lon": g.x, "lat": g.y}))
)

# plot circles at various lat / lon
fig = px.scatter_mapbox(
    df.merge(gdf, on="district"), lat="lat", lon="lon", size="total"
).update_layout(mapbox={"style": "carto-positron"})

# generate a 10000m circle at a random black as geojson
cgeo = (
    gdf.set_crs("epsg:4326")
    .sample(1)
    .pipe(lambda d: d.to_crs(d.estimate_utm_crs()))["geometry"]
    .centroid.buffer(10000)
    .to_crs("epsg:4326")
    .__geo_interface__
)

# add circle geometry as layer to mapbox figure
fig.update_layout(
    mapbox={
        "layers": [
            {"source": cgeo, "color": "PaleTurquoise", "type": "fill", "opacity":.5},
        ]
    }
)

Solution

    • it's really more of the same.
      1. The circle is as per original solution just refactored code a little
      2. The dot is just another circle with a smaller radius
      3. The arrow is constructing it as segments of a LineString
    import plotly.express as px
    import geopandas as gpd
    import numpy as np
    import pandas as pd
    from shapely.geometry import LineString
    
    df = px.data.election()
    
    # prep geometry
    gdf = gpd.GeoDataFrame.from_features(px.data.election_geojson())
    gdf = gdf.join(
        gdf["geometry"].centroid.apply(lambda g: pd.Series({"lon": g.x, "lat": g.y}))
    )
    
    # plot circles at various lat / lon
    fig = px.scatter_mapbox(
        df.merge(gdf, on="district"), lat="lat", lon="lon", size="total"
    ).update_layout(mapbox={"style": "carto-positron"})
    
    gdf_ = gdf.set_crs("epsg:4326").sample(1)
    utm = gdf_.estimate_utm_crs()
    gdf_ = gdf_.to_crs(utm)
    b = 10000
    m = b // 10
    p = gdf_.centroid.values[0]
    arrow = LineString(
        [p, (p.x + b, p.y), (p.x + b - m, p.y - m), (p.x + b - m, p.y + m), (p.x + b, p.y)]
    )
    
    
    def geojson(shape, utm):
        return gpd.GeoSeries([shape], crs=utm).to_crs("epsg:4386").__geo_interface__
    
    
    # add circle geometry as layer to mapbox figure
    fig.update_layout(
        mapbox={
            "layers": [
                {
                    "source": geojson(p.buffer(b), utm),
                    "color": "PaleTurquoise",
                    "type": "fill",
                    "opacity": 0.5,
                },
                {
                    "source": geojson(p.buffer(m), utm),
                    "color": "red",
                    "type": "fill",
                    "opacity": 0.5,
                },
                {
                    "source": geojson(arrow, utm),
                    "color": "blue",
                    "type": "line",
                    "opacity": 0.5,
                },
            ]
        }
    )
    

    enter image description here