Repository: geopandas/geopandas
Pull request #3092 (2023-12-02 21:50:17)

ENH: explose is_closed on a base level
Somehow `is_closed` was already implemented on a `GeometryArray` level, but not exposed to GeoPandasBase. Doing that now as part of #2010.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3473befc..f2fd47bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ New methods:
 - Added `count_coordinates` method from shapely to GeoSeries/GeoDataframe (#3026).
 - Added `minimum_clearance` method from shapely to GeoSeries/GeoDataframe (#2989).
 - Added `is_ccw` method from shapely to GeoSeries/GeoDataframe (#3027).
+- Added `is_closed` attribute from shapely to GeoSeries/GeoDataframe (#3092).
 - Added `force_2d` and `force_3d` methods from shapely to GeoSeries/GeoDataframe (#3090).
 - Added ``transform`` method from shapely to GeoSeries/GeoDataFrame (#3075).
 
diff --git a/doc/source/docs/reference/geoseries.rst b/doc/source/docs/reference/geoseries.rst
index 1c42ac1b..05d9523a 100644
--- a/doc/source/docs/reference/geoseries.rst
+++ b/doc/source/docs/reference/geoseries.rst
@@ -42,6 +42,7 @@ Unary predicates
 .. autosummary::
    :toctree: api/
 
+   GeoSeries.is_closed
    GeoSeries.is_empty
    GeoSeries.is_ring
    GeoSeries.is_simple
diff --git a/geopandas/base.py b/geopandas/base.py
index 828f2369..235c7889 100644
--- a/geopandas/base.py
+++ b/geopandas/base.py
@@ -475,6 +475,40 @@ GeometryCollection
         """
         return _delegate_property("is_ccw", self)
 
+    @property
+    def is_closed(self):
+        """Returns 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 shapely.geometry import LineString, Point, Polygon
+        >>> s = geopandas.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_property("is_closed", self)
+
     @property
     def has_z(self):
         """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for


Test patch:
diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py
index 3bc1f9fb..03fed18c 100644
--- a/geopandas/tests/test_geom_methods.py
+++ b/geopandas/tests/test_geom_methods.py
@@ -783,6 +783,10 @@ class TestGeomMethods:
         expected = Series(np.array([False] * len(self.g1)), self.g1.index)
         self._test_unary_real("is_ccw", expected, self.g1)
 
+    def test_is_closed(self):
+        expected = Series(np.array([False, False]), self.g5.index)
+        self._test_unary_real("is_closed", expected, self.g5)
+
     def test_has_z(self):
         expected = Series([False, True], self.g_3d.index)
         self._test_unary_real("has_z", expected, self.g_3d)