I have a shapefile of points, defined by X and Y coordinates, ad the ID feature. I have at least 3 different points with the same ID number.
I would like to define, for each ID, the shapefile of a circle that circumscribes the points.
How can this be done in python environment?
Advertisement
Answer
- there is a library that does it: https://pypi.org/project/miniball/
- it’s pretty forward to integrate in standard pandas pattern https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html
- solution really reduces to this:
JavaScript
x
8
1
def circle(points):
2
p, r = miniball.get_bounding_ball(np.array([points.x, points.y]).T)
3
return shapely.geometry.Point(p).buffer(math.sqrt(r))
4
5
col = "group"
6
# generate circles around groups of points
7
gdf_c = cities.groupby(col, as_index=False).agg(geometry=("geometry", circle))
8
- with sample example and visualisation, circles do become distorted due to epsg:4326 projection limitations
full working example
JavaScript
1
32
32
1
import geopandas as gpd
2
import numpy as np
3
import shapely
4
import miniball
5
import math
6
import pandas as pd
7
8
cities = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
9
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
10
11
# a semi-synthetic grouping of cities
12
world["size"] = world.groupby("continent")["pop_est"].apply(
13
lambda d: pd.cut(d, 2, labels=list("ab"), duplicates="drop").astype(str)
14
)
15
cities = cities.sjoin(world.loc[:, ["continent", "iso_a3", "size", "geometry"]])
16
cities["group"] = cities["continent"] + cities["size"]
17
18
19
def circle(points):
20
p, r = miniball.get_bounding_ball(np.array([points.x, points.y]).T)
21
return shapely.geometry.Point(p).buffer(math.sqrt(r))
22
23
col = "group"
24
# generate circles around groups of points
25
gdf_c = cities.groupby(col, as_index=False).agg(geometry=("geometry", circle))
26
27
# visualize it
28
m = cities.explore(column=col, height=300, width=600, legend=False)
29
gdf_c.loc[~gdf_c["geometry"].is_empty].explore(
30
m=m, column=col, marker_kwds={"radius": 20}, legend=False
31
)
32