This is an automated email from the ASF dual-hosted git repository.

jiayu 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 56f196988f [GH-2377] Implement `is_closed` (#2378)
56f196988f is described below

commit 56f196988fffb0f3fed0bf1c0f4153d41b976a6c
Author: Yunchi Pang <[email protected]>
AuthorDate: Thu Oct 9 20:32:10 2025 -0700

    [GH-2377] Implement `is_closed` (#2378)
    
    Co-authored-by: Peter Nguyen <[email protected]>
---
 python/sedona/spark/geopandas/base.py              | 37 ++++++++++++++++++++--
 python/sedona/spark/geopandas/geoseries.py         | 11 +++----
 python/tests/geopandas/test_geoseries.py           | 15 ++++++++-
 .../tests/geopandas/test_match_geopandas_series.py |  8 ++++-
 4 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/python/sedona/spark/geopandas/base.py 
b/python/sedona/spark/geopandas/base.py
index 471a245eac..912b3c35f4 100644
--- a/python/sedona/spark/geopandas/base.py
+++ b/python/sedona/spark/geopandas/base.py
@@ -390,9 +390,40 @@ class GeoFrame(metaclass=ABCMeta):
     # def is_ccw(self):
     #     raise NotImplementedError("This method is not implemented yet.")
 
-    # @property
-    # def is_closed(self):
-    #     raise NotImplementedError("This method is not implemented yet.")
+    @property
+    def is_closed(self):
+        """Return a ``Series`` of ``dtype('bool')`` with value ``True`` if a
+        LineString's or LinearRing's first and last points are equal.
+
+        Returns False for any other geometry type.
+
+        Examples
+        --------
+        >>> from sedona.spark.geopandas import GeoSeries
+        >>> from shapely.geometry import LineString, Point, Polygon
+        >>> s = GeoSeries(
+        ...     [
+        ...         LineString([(0, 0), (1, 1), (0, 1), (0, 0)]),
+        ...         LineString([(0, 0), (1, 1), (0, 1)]),
+        ...         Polygon([(0, 0), (0, 1), (1, 1), (0, 0)]),
+                    Point(3, 3)
+        ...     ]
+        ... )
+        >>> s
+        0    LINESTRING (0 0, 1 1, 0 1, 0 0)
+        1         LINESTRING (0 0, 1 1, 0 1)
+        2     POLYGON ((0 0, 0 1, 1 1, 0 0))
+        3                        POINT (3 3)
+        dtype: geometry
+
+        >>> s.is_closed
+        0     True
+        1    False
+        2    False
+        3    False
+        dtype: bool
+        """
+        return _delegate_to_geometry_column("is_closed", self)
 
     @property
     def has_z(self):
diff --git a/python/sedona/spark/geopandas/geoseries.py 
b/python/sedona/spark/geopandas/geoseries.py
index 21bb6e4b1d..55af19673f 100644
--- a/python/sedona/spark/geopandas/geoseries.py
+++ b/python/sedona/spark/geopandas/geoseries.py
@@ -976,13 +976,12 @@ class GeoSeries(GeoFrame, pspd.Series):
 
     @property
     def is_closed(self):
-        # Implementation of the abstract method.
-        raise NotImplementedError(
-            _not_implemented_error(
-                "is_closed",
-                "Tests if LineString geometries are closed (start equals end 
point).",
-            )
+        spark_expr = stf.ST_IsClosed(self.spark.column)
+        result = self._query_geometry_column(
+            spark_expr,
+            returns_geom=False,
         )
+        return _to_bool(result)
 
     @property
     def has_z(self) -> pspd.Series:
diff --git a/python/tests/geopandas/test_geoseries.py 
b/python/tests/geopandas/test_geoseries.py
index 614e4dc112..d9c5fd7db3 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -885,7 +885,20 @@ e": "Feature", "properties": {}, "geometry": {"type": 
"Point", "coordinates": [3
         pass
 
     def test_is_closed(self):
-        pass
+        s = GeoSeries(
+            [
+                LineString([(0, 0), (1, 1), (1, -1)]),
+                LineString([(0, 0), (1, 1), (1, -1), (0, 0)]),
+                LinearRing([(0, 0), (1, 1), (1, -1)]),
+            ]
+        )
+        result = s.is_closed
+        expected = pd.Series([False, True, True])
+        self.check_pd_series_equal(result, expected)
+
+        # Check that GeoDataFrame works too
+        result = s.to_geoframe().is_closed
+        self.check_pd_series_equal(result, expected)
 
     def test_has_z(self):
         s = sgpd.GeoSeries(
diff --git a/python/tests/geopandas/test_match_geopandas_series.py 
b/python/tests/geopandas/test_match_geopandas_series.py
index 17cae5b95e..250c133eef 100644
--- a/python/tests/geopandas/test_match_geopandas_series.py
+++ b/python/tests/geopandas/test_match_geopandas_series.py
@@ -588,7 +588,13 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
         pass
 
     def test_is_closed(self):
-        pass
+        if parse_version(gpd.__version__) < parse_version("1.0.0"):
+            pytest.skip("geopandas is_closed requires version 1.0.0 or higher")
+        # is_closed is only meaningful for linestrings so we use 
self.linestrings instead of self.geoms
+        for geom in self.linestrings:
+            sgpd_result = GeoSeries(geom).is_closed
+            gpd_result = gpd.GeoSeries(geom).is_closed
+            self.check_pd_series_equal(sgpd_result, gpd_result)
 
     def test_has_z(self):
         for geom in self.geoms:

Reply via email to