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 807137225b [GH-2491] Implement force_2d (#2493)
807137225b is described below

commit 807137225b56731655e0f595ed21013fdfd969ff
Author: Krishna C Vemulakonda <[email protected]>
AuthorDate: Wed Nov 12 21:13:13 2025 -0700

    [GH-2491] Implement force_2d (#2493)
---
 python/sedona/spark/geopandas/base.py              | 33 ++++++++++++++++++++--
 python/sedona/spark/geopandas/geoseries.py         |  6 ++--
 python/tests/geopandas/test_geoseries.py           | 33 +++++++++++++++++++++-
 .../tests/geopandas/test_match_geopandas_series.py | 23 ++++++++++++++-
 4 files changed, 88 insertions(+), 7 deletions(-)

diff --git a/python/sedona/spark/geopandas/base.py 
b/python/sedona/spark/geopandas/base.py
index 82dd7dd53b..639785c2b6 100644
--- a/python/sedona/spark/geopandas/base.py
+++ b/python/sedona/spark/geopandas/base.py
@@ -861,8 +861,37 @@ class GeoFrame(metaclass=ABCMeta):
     # def transform(self, transformation, include_z=False):
     #     raise NotImplementedError("This method is not implemented yet.")
 
-    # def force_2d(self):
-    #     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.
+        Returns
+        -------
+        GeoSeries
+        Examples
+        --------
+        >>> from shapely import Polygon, LineString, Point
+        >>> from sedona.spark.geopandas import GeoSeries
+        >>> 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)
+        2    POLYGON ((0 0, 0 10, 10 10, 0 0))
+        dtype: geometry
+        """
+        return _delegate_to_geometry_column("force_2d", self)
 
     # def force_3d(self, z=0):
     #     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 0530b62e3d..0e60256aa7 100644
--- a/python/sedona/spark/geopandas/geoseries.py
+++ b/python/sedona/spark/geopandas/geoseries.py
@@ -1081,9 +1081,9 @@ class GeoSeries(GeoFrame, pspd.Series):
         # Implementation of the abstract method.
         raise NotImplementedError("This method is not implemented yet.")
 
-    def force_2d(self):
-        # Implementation of the abstract method.
-        raise NotImplementedError("This method is not implemented yet.")
+    def force_2d(self) -> "GeoSeries":
+        spark_expr = stf.ST_Force_2D(self.spark.column)
+        return self._query_geometry_column(spark_expr, returns_geom=True)
 
     def force_3d(self, z=0):
         # Implementation of the abstract method.
diff --git a/python/tests/geopandas/test_geoseries.py 
b/python/tests/geopandas/test_geoseries.py
index c1c373a2ff..efadad1794 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -1409,7 +1409,38 @@ e": "Feature", "properties": {}, "geometry": {"type": 
"Point", "coordinates": [3
         pass
 
     def test_force_2d(self):
-        pass
+        s = sgpd.GeoSeries(
+            [
+                Point(0, -1, 2.5),  # 3D point
+                LineString([(0, 0, 1), (1, 1, 2)]),  # 3D line
+                Polygon([(0, 0, 1), (1, 0, 2), (1, 1, 3), (0, 0, 1)]),  # 3D 
polygon
+                Point(5, 5),  # already 2D
+                Polygon(),  # empty geometry
+                None,  # None preserved
+                shapely.wkt.loads("POINT M (1 2 3)"),
+                shapely.wkt.loads("LINESTRING ZM (1 2 3 4, 5 6 7 8)"),
+            ]
+        )
+
+        result = s.force_2d()
+
+        expected = gpd.GeoSeries(
+            [
+                Point(0, -1),
+                LineString([(0, 0), (1, 1)]),
+                Polygon([(0, 0), (1, 0), (1, 1), (0, 0)]),
+                Point(5, 5),
+                Polygon(),
+                None,
+                shapely.wkt.loads("POINT (1 2)"),
+                shapely.wkt.loads("LINESTRING (1 2, 5 6)"),
+            ]
+        )
+
+        self.check_sgpd_equals_gpd(result, expected)
+
+        df_result = s.to_geoframe().force_2d()
+        self.check_sgpd_equals_gpd(df_result, expected)
 
     def test_force_3d(self):
         pass
diff --git a/python/tests/geopandas/test_match_geopandas_series.py 
b/python/tests/geopandas/test_match_geopandas_series.py
index 1a9f7ad4ef..16a9fee9de 100644
--- a/python/tests/geopandas/test_match_geopandas_series.py
+++ b/python/tests/geopandas/test_match_geopandas_series.py
@@ -833,7 +833,28 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
         pass
 
     def test_force_2d(self):
-        pass
+        # force_2d was added from geopandas 1.0.0
+        if parse_version(gpd.__version__) < parse_version("1.0.0"):
+            pytest.skip("geopandas force_2d requires version 1.0.0 or higher")
+        # 1) No-op on existing 2D fixtures
+        for geom in self.geoms:
+            sgpd_result = GeoSeries(geom).force_2d()
+            gpd_result = gpd.GeoSeries(geom).force_2d()
+            self.check_sgpd_equals_gpd(sgpd_result, gpd_result)
+
+        # 2) Minimal 3D sample to verify Z is actually stripped
+        data = [
+            Point(0, -1, 2.5),
+            LineString([(0, 0, 1), (1, 1, 2)]),
+            Polygon([(0, 0, 1), (1, 0, 2), (1, 1, 3), (0, 0, 1)]),
+            Point(5, 5),  # already 2D
+            Polygon(),  # empty geometry
+            shapely.wkt.loads("POINT M (1 2 3)"),
+            shapely.wkt.loads("LINESTRING ZM (1 2 3 4, 5 6 7 8)"),
+        ]
+        sgpd_3d = GeoSeries(data).force_2d()
+        gpd_3d = gpd.GeoSeries(data).force_2d()
+        self.check_sgpd_equals_gpd(sgpd_3d, gpd_3d)
 
     def test_force_3d(self):
         pass

Reply via email to