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

Reply via email to