This is an automated email from the ASF dual-hosted git repository.
petern pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new c8f8eb3f04 Implement minimum_bounding_radius (#2502)
c8f8eb3f04 is described below
commit c8f8eb3f04829930b89e15377ad29fbb35f31fd4
Author: Krishna C Vemulakonda <[email protected]>
AuthorDate: Fri Nov 14 08:30:19 2025 -0700
Implement minimum_bounding_radius (#2502)
---
python/sedona/spark/geopandas/base.py | 126 +++++++++++++++------
python/sedona/spark/geopandas/geoseries.py | 10 +-
python/tests/geopandas/test_geoseries.py | 23 +++-
.../tests/geopandas/test_match_geopandas_series.py | 5 +-
4 files changed, 124 insertions(+), 40 deletions(-)
diff --git a/python/sedona/spark/geopandas/base.py
b/python/sedona/spark/geopandas/base.py
index 639785c2b6..468303e759 100644
--- a/python/sedona/spark/geopandas/base.py
+++ b/python/sedona/spark/geopandas/base.py
@@ -613,38 +613,48 @@ class GeoFrame(metaclass=ABCMeta):
@property
def convex_hull(self):
- """
- Return the convex hull of each geometry.
+ """Return a ``GeoSeries`` of geometries representing the convex hull
+ of each geometry.
- The convex hull is the smallest convex Polygon that contains
- all the points of the geometry.
+ The convex hull of a geometry is the smallest convex `Polygon`
+ containing all the points in each geometry, unless the number of points
+ in the geometric object is less than three. For two points, the convex
+ hull collapses to a `LineString`; for 1, a `Point`.
Examples
--------
- >>> from shapely.geometry import Point, Polygon, LineString
>>> from sedona.spark.geopandas import GeoSeries
+ >>> from shapely.geometry import Polygon, LineString, Point, MultiPoint
>>> s = GeoSeries(
... [
- ... Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
- ... LineString([(0, 0), (2, 1)]),
+ ... Polygon([(0, 0), (1, 1), (0, 1)]),
+ ... LineString([(0, 0), (1, 1), (1, 0)]),
+ ... MultiPoint([(0, 0), (1, 1), (0, 1), (1, 0), (0.5, 0.5)]),
+ ... MultiPoint([(0, 0), (1, 1)]),
... Point(0, 0),
... ]
... )
>>> s
- 0 POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
- 1 LINESTRING (0 0, 2 1)
- 2 POINT (0 0)
+ 0 POLYGON ((0 0, 1 1, 0 1, 0 0))
+ 1 LINESTRING (0 0, 1 1, 1 0)
+ 2 MULTIPOINT ((0 0), (1 1), (0 1), (1 0), (0.5 0...
+ 3 MULTIPOINT ((0 0), (1 1))
+ 4 POINT (0 0)
dtype: geometry
>>> s.convex_hull
- 0 POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
- 1 POLYGON ((0 0, 2 1, 0 0))
- 2 POINT (0 0)
+ 0 POLYGON ((0 0, 0 1, 1 1, 0 0))
+ 1 POLYGON ((0 0, 1 1, 1 0, 0 0))
+ 2 POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))
+ 3 LINESTRING (0 0, 1 1)
+ 4 POINT (0 0)
dtype: geometry
- See also
+ See Also
--------
- GeoSeries.envelope : axis-aligned bounding rectangle
+ GeoSeries.concave_hull : concave hull geometry
+ GeoSeries.envelope : bounding rectangle geometry
+
"""
return _delegate_to_geometry_column("convex_hull", self)
@@ -723,27 +733,71 @@ class GeoFrame(metaclass=ABCMeta):
# raise NotImplementedError("This method is not implemented yet.")
def minimum_bounding_circle(self):
- """
- Returns a ``GeoSeries`` containing the minimum bounding circle of each
geometry.
- The minimum bounding circle is the smallest circle that completely
encloses
- the geometry. The result is returned as a circular polygon
approximation.
- Returns
- -------
- GeoSeries
- A GeoSeries containing the minimum bounding circle for each
geometry.
+ """Return a ``GeoSeries`` of geometries representing the minimum
bounding
+ circle that encloses each geometry.
+
Examples
--------
- >>> from shapely.geometry import Polygon
>>> from sedona.spark.geopandas import GeoSeries
- >>> gs = GeoSeries([Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])])
- >>> gs.minimum_bounding_circle
- 0 POLYGON ((...))
+ >>> from shapely.geometry import Polygon, LineString, Point
+ >>> s = GeoSeries(
+ ... [
+ ... Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]),
+ ... LineString([(0, 0), (1, 1), (1, 0)]),
+ ... Point(0, 0),
+ ... ]
+ ... )
+ >>> s
+ 0 POLYGON ((0 0, 1 1, 0 1, 0 0))
+ 1 LINESTRING (0 0, 1 1, 1 0)
+ 2 POINT (0 0)
+ dtype: geometry
+
+ >>> s.minimum_bounding_circle()
+ 0 POLYGON ((1.20711 0.5, 1.19352 0.36205, 1.1532...
+ 1 POLYGON ((1.20711 0.5, 1.19352 0.36205, 1.1532...
+ 2 POINT (0 0)
dtype: geometry
+
+ See Also
+ --------
+ GeoSeries.convex_hull : convex hull geometry
"""
return _delegate_to_geometry_column("minimum_bounding_circle", self)
- # def minimum_bounding_radius(self):
- # raise NotImplementedError("This method is not implemented yet.")
+ def minimum_bounding_radius(self):
+ """Return a `Series` of the radii of the minimum bounding circles
+ that enclose each geometry.
+
+ Examples
+ --------
+ >>> from sedona.spark.geopandas import GeoSeries
+ >>> from shapely.geometry import Point, LineString, Polygon
+ >>> s = GeoSeries(
+ ... [
+ ... Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]),
+ ... LineString([(0, 0), (1, 1), (1, 0)]),
+ ... Point(0,0),
+ ... ]
+ ... )
+ >>> s
+ 0 POLYGON ((0 0, 1 1, 0 1, 0 0))
+ 1 LINESTRING (0 0, 1 1, 1 0)
+ 2 POINT (0 0)
+ dtype: geometry
+
+ >>> s.minimum_bounding_radius()
+ 0 0.707107
+ 1 0.707107
+ 2 0.000000
+ dtype: float64
+
+ See Also
+ --------
+ GeoSeries.minumum_bounding_circle : minimum bounding circle (geometry)
+
+ """
+ return _delegate_to_geometry_column("minimum_bounding_radius", self)
# def minimum_clearance(self):
# raise NotImplementedError("This method is not implemented yet.")
@@ -862,29 +916,31 @@ class GeoFrame(metaclass=ABCMeta):
# raise NotImplementedError("This method is not implemented yet.")
def force_2d(self):
- """
- Forces the dimensionality of each geometry to 2D.
- Removes the Z and M coordinates (if present) from each geometry and
returns a
- GeoSeries with 2D geometries. 2D inputs are returned unchanged.
+ """Force the dimensionality of a geometry to 2D.
+
+ Removes the additional Z and M coordinate dimension from all
geometries.
+
Returns
-------
GeoSeries
+
Examples
--------
- >>> from shapely import Polygon, LineString, Point
>>> from sedona.spark.geopandas import GeoSeries
+ >>> from shapely import Polygon, LineString, Point
>>> s = GeoSeries(
... [
... Point(0.5, 2.5, 0),
... LineString([(1, 1, 1), (0, 1, 3), (1, 0, 2)]),
... Polygon([(0, 0, 0), (0, 10, 0), (10, 10, 0)]),
- ... ]
+ ... ],
... )
>>> s
0 POINT Z (0.5 2.5 0)
1 LINESTRING Z (1 1 1, 0 1 3, 1 0 2)
2 POLYGON Z ((0 0 0, 0 10 0, 10 10 0, 0 0 0))
dtype: geometry
+
>>> s.force_2d()
0 POINT (0.5 2.5)
1 LINESTRING (1 1, 0 1, 1 0)
diff --git a/python/sedona/spark/geopandas/geoseries.py
b/python/sedona/spark/geopandas/geoseries.py
index 0e60256aa7..65c87201c1 100644
--- a/python/sedona/spark/geopandas/geoseries.py
+++ b/python/sedona/spark/geopandas/geoseries.py
@@ -1037,9 +1037,13 @@ class GeoSeries(GeoFrame, pspd.Series):
returns_geom=True,
)
- def minimum_bounding_radius(self):
- # Implementation of the abstract method.
- raise NotImplementedError("This method is not implemented yet.")
+ def minimum_bounding_radius(self) -> pspd.Series:
+ spark_struct = stf.ST_MinimumBoundingRadius(self.spark.column)
+ spark_radius = spark_struct.getField("radius")
+ return self._query_geometry_column(
+ spark_radius,
+ returns_geom=False,
+ )
def minimum_clearance(self):
# Implementation of the abstract method.
diff --git a/python/tests/geopandas/test_geoseries.py
b/python/tests/geopandas/test_geoseries.py
index efadad1794..30d791b5bf 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -1309,7 +1309,28 @@ e": "Feature", "properties": {}, "geometry": {"type":
"Point", "coordinates": [3
self.check_sgpd_equals_gpd(df_result, expected)
def test_minimum_bounding_radius(self):
- pass
+ s = GeoSeries(
+ [
+ Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]),
+ LineString([(0, 0), (1, 1), (1, 0)]),
+ Point(0, 0),
+ ]
+ )
+
+ expected = pd.Series(
+ [
+ 0.707107, # radius for the square
+ 0.707107, # radius for the line
+ 0.000000, # radius for the point
+ ]
+ )
+
+ result = s.minimum_bounding_radius()
+ self.check_pd_series_equal(result, expected)
+
+ gdf = s.to_geoframe()
+ df_result = gdf.minimum_bounding_radius()
+ self.check_pd_series_equal(df_result, expected)
def test_minimum_clearance(self):
pass
diff --git a/python/tests/geopandas/test_match_geopandas_series.py
b/python/tests/geopandas/test_match_geopandas_series.py
index 16a9fee9de..98b0c528a7 100644
--- a/python/tests/geopandas/test_match_geopandas_series.py
+++ b/python/tests/geopandas/test_match_geopandas_series.py
@@ -781,7 +781,10 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
self.check_sgpd_equals_gpd(sgpd_result, gpd_result, tolerance=0.5)
def test_minimum_bounding_radius(self):
- pass
+ for geom in self.geoms:
+ sgpd_result = GeoSeries(geom).minimum_bounding_radius()
+ gpd_result = gpd.GeoSeries(geom).minimum_bounding_radius()
+ self.check_pd_series_equal(sgpd_result, gpd_result)
def test_minimum_clearance(self):
pass