Draw a polygon around point in scattermapbox using python

I am using plotlys scattermapbox to plot points on a map. I’d like to draw the polygon that cover 'x' mile radius from a POI.

dcc.Graph(id="map-graph"),

@application.callback([

                         Output("map-graph", "figure"),
                      
                      ],
                      [

                         Input("address", "value"),
                         Input("type", "value")
                      ]
                      
                     )
def update_graph(address, type):
    
    for i, row in df.iterrows():

        lat = row["Lat"]
        lng = row["Long"]

        data.append({

                     "type": "scattermapbox",
                     "lat": [lat],
                     "lon": [lng],
                     "name": "Location",
                     "showlegend": False,
                     "hoverinfo": "text",
                     "mode": "markers",
                     "marker": {
                                "symbol": "circle",
                                "size": 8,
                                "opacity": 0.8,
                                "color": "black"
                               }
                      }
        )

     # Plot POI 
     POI_Lat = 37.785908
     POI_Long = -122.400803

     data.append({
                    "type": "scattermapbox",
                    "lat": [POI_Lat],
                    "lon": [POI_Long],
                     "marker": {
                        "symbol": "circle,
                        "size": 28,
                        "opacity": 0.7,
                        "color": "rgb(128, 128, 128)"
                        }
                    }
        )

df is a pandas dataframe that includes coordinates for locations within x miles of POI. How do I update the map-graph to draw a polygon that covers all the points?

Adding a layer to layout dictionary:

gdf = circles(Lat, Long, radius=1609.34)

print(gdf['geometry'][0])

POLYGON ((385272.0167249573 3768678.19769511, 385264.2673129799 3768520.454790493,.......))


layout = {

                        "autosize": True,
                        "hovermode": "closest",
                        "mapbox": {

                            "accesstoken": MAPBOX_KEY,
                            "bearing": 0,
                            "center": {
                                "lat": layout_lat,
                                "lon": layout_lon
                            },
                            "layers": [
                                         {
                                             "source": json.loads(gdf.geometry.to_json()),
                                             "below": "traces",
                                             "type": "line",
                                             "color": "purple",
                                             "line": {"width": 1.5},
                                         }
                            ],
                            "pitch": 0,
                            "zoom": zoom,
                            "style": "outdoors",

                        },

                        "margin": {
                           "r": 0,
                           "t": 0,
                           "l": 0,
                           "b": 0,
                           "pad": 0
                       }

           }

Answer

  • based on answer to this duplicate question Obtain coordinates of a Polygon / Multi-polygon around a point in python
  • no sample data provided in question, so I’ve used UK hospital data
  • have created a helper function poi_poly(). NB radius is in meters as per UTM geometry
  • UTM geometry is used to create a polygon of specified radius
  • markers are then intersected with this polygon. Then get the convex hull
  • have provided option to return radius polygon as well, in example below I’ve returned this to demonstrate that the convex hull polygon is within the radius of the POI
import shapely.geometry
import pandas as pd
import geopandas as gpd
import requests, io, json
import plotly.express as px
import random


def poi_poly(
    df,
    radius=10 ** 5,
    poi={"Longitude": 0.06665166467428207, "Latitude": 51.19034957885742},
    lon_col="Longitude",
    lat_col="Latitude",
    include_radius_poly=False,
):

    # generate a geopandas data frame of the POI
    gdfpoi = gpd.GeoDataFrame(
        geometry=[shapely.geometry.Point(poi["Longitude"], poi["Latitude"])],
        crs="EPSG:4326",
    )
    # extend point to radius defined (a polygon).  Use UTM so that distances work, then back to WSG84
    gdfpoi = (
        gdfpoi.to_crs(gdfpoi.estimate_utm_crs())
        .geometry.buffer(radius)
        .to_crs("EPSG:4326")
    )

    # create a geopandas data frame of all the points / markers
    if not df is None:
        gdf = gpd.GeoDataFrame(
            geometry=df.loc[:, ["Longitude", "Latitude"]]
            .dropna()
            .apply(
                lambda r: shapely.geometry.Point(r["Longitude"], r["Latitude"]), axis=1
            )
            .values,
            crs="EPSG:4326",
        )
    else:
        gdf = gpd.GeoDataFrame(geometry=gdfpoi)

    # create a polygon around the edges of the markers that are within POI polygon
    return pd.concat(
        [
            gpd.GeoDataFrame(
                geometry=[
                    gpd.sjoin(
                        gdf, gpd.GeoDataFrame(geometry=gdfpoi), how="inner"
                    ).unary_union.convex_hull
                ]
            ),
            gpd.GeoDataFrame(geometry=gdfpoi if include_radius_poly else None),
        ]
    )


# get some public addressess - hospitals.  data that can be scattered
dfhos = pd.read_csv(
    io.StringIO(
        requests.get("http://media.nhschoices.nhs.uk/data/foi/Hospital.csv").text
    ),
    sep="¬",
    engine="python",
)


# generate polygon of markers within 5 mile radius of Point of Interest
poi = dfhos.loc[random.randint(0, len(dfhos) - 1), ["Longitude", "Latitude"]].to_dict()
gdf = poi_poly(dfhos, poi=poi, radius=1609.34 * 5, include_radius_poly=True)

fig = (
    px.scatter_mapbox(
        dfhos,
        lat="Latitude",
        lon="Longitude",
        color="Sector",
        hover_data=["OrganisationName", "Postcode"],
    )
    .update_traces(marker={"size": 10})
    .update_layout(
        mapbox={
            "style": "open-street-map",
            "zoom": 9,
            "center": {"lat": poi["Latitude"], "lon": poi["Longitude"]},
            "layers": [
                {
                    "source": json.loads(gdf.geometry.to_json()),
                    "below": "traces",
                    "type": "line",
                    "color": "purple",
                    "line": {"width": 1.5},
                }
            ],
        },
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
    )
)
fig.show()

enter image description here

draw just a circle polygon

  • poi_poly() has been updated. DataFrame is no longer mandatory for finding markers within POI
  • simple example of creating a circle (actually a polygon) centred on a single set of GPS co-ordinates
import plotly.graph_objects as go

poi = {"Latitude": 37.785908, "Longitude": -122.400803}

go.Figure(go.Scattermapbox()).update_layout(
    mapbox={
        "style": "open-street-map",
        "zoom": 9,
        "center": {"lat": poi["Latitude"], "lon": poi["Longitude"]},
        "layers": [
            {
                "source": json.loads(poi_poly(None, poi=poi, radius=1609).to_json()),
                "below": "traces",
                "type": "line",
                "color": "purple",
                "line": {"width": 1.5},
            }
        ],
    },
    margin={"l": 0, "r": 0, "t": 0, "b": 0},
)

enter image description here