kms
kms

Reputation: 2014

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
                       }

           }

Upvotes: 0

Views: 5481

Answers (2)

Rob Raymond
Rob Raymond

Reputation: 31146

  • 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

Upvotes: 2

bas
bas

Reputation: 15722

See an example in the documentation here

import plotly.graph_objects as go

fig = go.Figure(go.Scattermapbox(
    mode = "markers",
    lon = [-73.605], lat = [45.51],
    marker = {'size': 20, 'color': ["cyan"]}))

fig.update_layout(
    mapbox = {
        'style': "stamen-terrain",
        'center': { 'lon': -73.6, 'lat': 45.5},
        'zoom': 12, 'layers': [{
            'source': {
                'type': "FeatureCollection",
                'features': [{
                    'type': "Feature",
                    'geometry': {
                        'type': "MultiPolygon",
                        'coordinates': [[[
                            [-73.606352888, 45.507489991], [-73.606133883, 45.50687600],
                            [-73.605905904, 45.506773980], [-73.603533905, 45.505698946],
                            [-73.602475870, 45.506856969], [-73.600031904, 45.505696003],
                            [-73.599379992, 45.505389066], [-73.599119902, 45.505632008],
                            [-73.598896977, 45.505514039], [-73.598783894, 45.505617001],
                            [-73.591308727, 45.516246185], [-73.591380782, 45.516280145],
                            [-73.596778656, 45.518690062], [-73.602796770, 45.521348046],
                            [-73.612239983, 45.525564037], [-73.612422919, 45.525642061],
                            [-73.617229085, 45.527751983], [-73.617279234, 45.527774160],
                            [-73.617304713, 45.527741334], [-73.617492052, 45.527498362],
                            [-73.617533258, 45.527512253], [-73.618074188, 45.526759105],
                            [-73.618271651, 45.526500673], [-73.618446320, 45.526287943],
                            [-73.618968507, 45.525698560], [-73.619388002, 45.525216750],
                            [-73.619532966, 45.525064183], [-73.619686662, 45.524889290],
                            [-73.619787038, 45.524770086], [-73.619925742, 45.524584939],
                            [-73.619954486, 45.524557690], [-73.620122362, 45.524377961],
                            [-73.620201713, 45.524298907], [-73.620775593, 45.523650879]
                        ]]]
                    }
                }]
            },
            'type': "fill", 'below': "traces", 'color': "royalblue"}]},
    margin = {'l':0, 'r':0, 'b':0, 't':0})

fig.show()

polygon and point Adjust the above based on your point and polygon coordinates.

If you want to use another mapbox style:

The accepted values for layout.mapbox.style are one of:
"white-bg" yields an empty white canvas which results in no external HTTP requests
"open-street-map", "carto-positron", "carto-darkmatter", "stamen-terrain", "stamen-toner" or "stamen-watercolor" yield maps composed of raster tiles from various public tile servers which do not require signups or access tokens
"basic", "streets", "outdoors", "light", "dark", "satellite", or "satellite-streets" yield maps composed of vector tiles from the Mapbox service, and do require a Mapbox Access Token or an on-premise Mapbox installation.
A Mapbox service style URL, which requires a Mapbox Access Token or an on-premise Mapbox installation. A Mapbox Style object as defined at https://docs.mapbox.com/mapbox-gl-js/style-spec/

https://plotly.com/python/mapbox-layers/

Upvotes: 1

Related Questions