Search code examples
pythonmappinggisgeopandas

Create map boundaries from points within a geodataframe in python


I have a geodataframe called map which contains a list of points and a column, Closest_TrainStation_name which contains the name of the closest train station to that point.

geometry Closest_TrainStation_name
1 POINT(1,1) Station_1
2 POINT(10,10) Station_2
... ... ...

Is it possible to create boundary polygons containing each group like in the picture below? With each polygon having the name of the nearest train station from the original file.

The points were made using a nearest neighbour algorithm so they won't intersect with each other.

I also have a boundary geodataframe for the whole country called boundary which I think might be needed for defining the outer bounds of this map. And a file of the train stations.

Map of the points with the nearest train station

All of the methods I can find are about making boundaries from points on the boundary, e.g. convex_hull.


Solution

    • have used hospitals in England as source of data (effectively station and hospital and interchangeable)
    • have geometry that is boundary of England (to clip Voronoi polygons)
    • it then becomes simple to generate polygons that represent all the hospitals
    • use sjoin() to associate polygon with point that carries all the associated attributes
    • have used plotly to visualize and demonstrate polygons have hospital attributes associated with them
    import shapely.ops
    
    # points for hospitals
    gdf = gpd.GeoDataFrame(
        geometry=dfhos.loc[:, ["Longitude", "Latitude"]].apply(
            shapely.geometry.Point, axis=1
        )
    )
    
    # generate voroni polygon for each hospital
    gdfv = gpd.GeoDataFrame(
        geometry=[
            p.intersection(uk)
            for p in shapely.ops.voronoi_diagram(
                shapely.geometry.MultiPoint(gdf["geometry"].values)
            ).geoms
        ]
    )
    
    # spatial join polygons to points to pick up full details of hospital
    gdf3 = gpd.sjoin(gdfv, gdf, how="left").merge(
        dfhos, left_on="index_right", right_index=True
    )
    gdf3["Color"] = pd.factorize(gdf3["Postcode"], sort=True)[0]
    
    # and visualize
    fig = (
        px.choropleth_mapbox(
            gdf3,
            geojson=gdf3.__geo_interface__,
            locations=gdf3.index,
            hover_data=["OrganisationCode","OrganisationName","Postcode"],
            color="Color",
            color_continuous_scale="phase",
        )
        .update_layout(
            mapbox={
                "style": "carto-positron",
                "center": {
                    "lon": sum(gdf3.total_bounds[[0, 2]]) / 2,
                    "lat": sum(gdf3.total_bounds[[1, 3]]) / 2,
                },
                "zoom": 5,
            },
            margin={"l": 0, "r": 0, "t": 0, "b": 0},
            coloraxis={"showscale":False}
        )
    )
    
    fig
    

    enter image description here

    get locations of hospitals in England and polygon that is boundary of England

    import geopandas as gpd
    import shapely.geometry
    import numpy as np
    import plotly.express as px
    import requests, io
    from pathlib import Path
    from zipfile import ZipFile
    import urllib
    import pandas as pd
    
    # fmt: off
    # uk geometry
    url = "http://geoportal1-ons.opendata.arcgis.com/datasets/687f346f5023410ba86615655ff33ca9_1.zip"
    f = Path.cwd().joinpath(urllib.parse.urlparse(url).path.split("/")[-1])
    
    if not f.exists():
        r = requests.get(url, stream=True, headers={"User-Agent": "XY"})
        with open(f, "wb") as fd:
            for chunk in r.iter_content(chunk_size=128):
                fd.write(chunk)
        zfile = ZipFile(f)
        zfile.extractall(f.stem)
    
    f2 = Path.cwd().joinpath("uk.geojson")
    if not f2.exists():
        gdf2 = gpd.read_file(list(f.parent.joinpath(f.stem).glob("*.shp"))[0])
        gdf2 = gdf2.loc[gdf2["ctyua16cd"].str[0] == "E"]
        uk = gpd.GeoDataFrame(geometry=[p for p in shapely.ops.unary_union(gdf2.to_crs(gdf2.estimate_utm_crs())["geometry"].values).simplify(5000).geoms]).set_crs(gdf2.estimate_utm_crs()).to_crs("EPSG:4326")
    
        uk.to_file(Path.cwd().joinpath("uk.geojson"), driver='GeoJSON')
    uk = gpd.read_file(f2)
    uk = shapely.geometry.MultiPolygon(uk["geometry"].values)
    # fmt: on
    
    # get hospitals in UK
    dfhos = pd.read_csv(io.StringIO(requests.get("https://assets.nhs.uk/data/foi/Hospital.csv").text),sep="Č",engine="python",)
    dfhos = dfhos.loc[lambda d: d["Sector"].eq("NHS Sector") & d["SubType"].eq("Hospital")].groupby("ParentODSCode").first()