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 762d6f8500 [GH-2476] Implement convex_hull (#2484)
762d6f8500 is described below

commit 762d6f85004c17b3bafef73dc3bb65a3a058e38d
Author: Krishna C Vemulakonda <[email protected]>
AuthorDate: Mon Nov 10 09:41:36 2025 -0700

    [GH-2476] Implement convex_hull (#2484)
    
    Co-authored-by: Peter Nguyen <[email protected]>
---
 python/sedona/spark/geopandas/base.py              | 39 ++++++++++++++++++++--
 python/sedona/spark/geopandas/geoseries.py         | 11 +++---
 python/tests/geopandas/test_geoseries.py           | 25 +++++++++++++-
 .../tests/geopandas/test_match_geopandas_series.py | 10 +++++-
 4 files changed, 74 insertions(+), 11 deletions(-)

diff --git a/python/sedona/spark/geopandas/base.py 
b/python/sedona/spark/geopandas/base.py
index 3bdf70b39e..6e9eb39ee0 100644
--- a/python/sedona/spark/geopandas/base.py
+++ b/python/sedona/spark/geopandas/base.py
@@ -611,9 +611,42 @@ class GeoFrame(metaclass=ABCMeta):
     # def concave_hull(self, ratio=0.0, allow_holes=False):
     #     raise NotImplementedError("This method is not implemented yet.")
 
-    # @property
-    # def convex_hull(self):
-    #     raise NotImplementedError("This method is not implemented yet.")
+    @property
+    def convex_hull(self):
+        """
+        Return the convex hull of each geometry.
+
+        The convex hull is the smallest convex Polygon that contains
+        all the points of the geometry.
+
+        Examples
+        --------
+        >>> from shapely.geometry import Point, Polygon, LineString
+        >>> from sedona.spark.geopandas import GeoSeries
+        >>> s = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
+        ...         LineString([(0, 0), (2, 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)
+        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)
+        dtype: geometry
+
+        See also
+        --------
+        GeoSeries.envelope : axis-aligned bounding rectangle
+        """
+        return _delegate_to_geometry_column("convex_hull", self)
 
     # def delaunay_triangles(self, tolerance=0.0, only_edges=False):
     #     raise NotImplementedError("This method is not implemented yet.")
diff --git a/python/sedona/spark/geopandas/geoseries.py 
b/python/sedona/spark/geopandas/geoseries.py
index ec2cebb075..222419afd0 100644
--- a/python/sedona/spark/geopandas/geoseries.py
+++ b/python/sedona/spark/geopandas/geoseries.py
@@ -973,12 +973,11 @@ class GeoSeries(GeoFrame, pspd.Series):
         raise NotImplementedError("This method is not implemented yet.")
 
     @property
-    def convex_hull(self):
-        # Implementation of the abstract method.
-        raise NotImplementedError(
-            _not_implemented_error(
-                "convex_hull", "Computes the convex hull of each geometry."
-            )
+    def convex_hull(self) -> "GeoSeries":
+        spark_expr = stf.ST_ConvexHull(self.spark.column)
+        return self._query_geometry_column(
+            spark_expr,
+            returns_geom=True,
         )
 
     def delaunay_triangles(self, tolerance=0.0, only_edges=False):
diff --git a/python/tests/geopandas/test_geoseries.py 
b/python/tests/geopandas/test_geoseries.py
index 157b46bb5d..30de7fd356 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -1203,7 +1203,30 @@ e": "Feature", "properties": {}, "geometry": {"type": 
"Point", "coordinates": [3
         pass
 
     def test_convex_hull(self):
-        pass
+        s = GeoSeries(
+            [
+                Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
+                LineString([(0, 0), (2, 1)]),
+                Point(0, 0),
+                None,
+            ]
+        )
+
+        result = s.convex_hull
+
+        expected = gpd.GeoSeries(
+            [
+                Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),  # polygon hull == 
itself
+                LineString([(0, 0), (2, 1)]),  # NOT a polygon
+                Point(0, 0),  # point stays point
+                None,  # None stays None
+            ]
+        )
+        self.check_sgpd_equals_gpd(result, expected)
+
+        # Check if GeoDataFrame works as well
+        df_result = s.to_geoframe().convex_hull
+        self.check_sgpd_equals_gpd(df_result, expected)
 
     def test_delaunay_triangles(self):
         pass
diff --git a/python/tests/geopandas/test_match_geopandas_series.py 
b/python/tests/geopandas/test_match_geopandas_series.py
index 2d7e8848bf..20718baf0c 100644
--- a/python/tests/geopandas/test_match_geopandas_series.py
+++ b/python/tests/geopandas/test_match_geopandas_series.py
@@ -728,7 +728,15 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
         pass
 
     def test_convex_hull(self):
-        pass
+        for geom in self.geoms:
+            sgpd_result = GeoSeries(geom).convex_hull
+            gpd_result = gpd.GeoSeries(geom).convex_hull
+            self.check_sgpd_equals_gpd(sgpd_result, gpd_result)
+
+        mixed = [self.points[1], self.linestrings[1], self.polygons[1], None]
+        sgpd_result = GeoSeries(mixed).convex_hull
+        gpd_result = gpd.GeoSeries(mixed).convex_hull
+        self.check_sgpd_equals_gpd(sgpd_result, gpd_result)
 
     def test_delaunay_triangles(self):
         pass

Reply via email to