Repository: geopandas/geopandas

Issue:
Remove usage of buffer(0) as trick to make geometries valid
In our overlay code we have some places where we ensure geometries are valid. In the first place the input geometries, but for difference and intersection also the resulting geometries.

Historically we have been doing that with the `buffer(0)` trick, but in https://github.com/geopandas/geopandas/pull/2939 we already replaced one such example with `make_valid()` instead, which is faster and more robust, and just meant for making geometries valid, instead of being a trick.

But we have some remaining cases:

Making the input valid:

https://github.com/geopandas/geopandas/blob/321df2b588a41e8e87f740e828b65c2a87cd9542/geopandas/tools/overlay.py#L293-L307

The intersection result:

https://github.com/geopandas/geopandas/blob/321df2b588a41e8e87f740e828b65c2a87cd9542/geopandas/tools/overlay.py#L37-L39




Discussion:

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -2327,7 +2327,7 @@ def overlay(self, right, how="intersection", keep_geom_type=None, make_valid=Tru
             geometries.
         make_valid : bool, default True
             If True, any invalid input geometries are corrected with a call to
-            `buffer(0)`, if False, a `ValueError` is raised if any input geometries
+            make_valid(), if False, a `ValueError` is raised if any input geometries
             are invalid.
 
         Returns
diff --git a/geopandas/tools/overlay.py b/geopandas/tools/overlay.py
--- a/geopandas/tools/overlay.py
+++ b/geopandas/tools/overlay.py
@@ -36,7 +36,7 @@ def _overlay_intersection(df1, df2):
         right.reset_index(drop=True, inplace=True)
         intersections = left.intersection(right)
         poly_ix = intersections.geom_type.isin(["Polygon", "MultiPolygon"])
-        intersections.loc[poly_ix] = intersections[poly_ix].buffer(0)
+        intersections.loc[poly_ix] = intersections[poly_ix].make_valid()
 
         # only keep actual intersecting geometries
         pairs_intersect = pd.DataFrame({"__idx1": idx1, "__idx2": idx2})
@@ -166,7 +166,7 @@ def overlay(df1, df2, how="intersection", keep_geom_type=None, make_valid=True):
         which will set keep_geom_type to True but warn upon dropping
         geometries.
     make_valid : bool, default True
-        If True, any invalid input geometries are corrected with a call to `buffer(0)`,
+        If True, any invalid input geometries are corrected with a call to make_valid(),
         if False, a `ValueError` is raised if any input geometries are invalid.
 
     Returns
@@ -296,7 +296,7 @@ def _make_valid(df):
             mask = ~df.geometry.is_valid
             col = df._geometry_column_name
             if make_valid:
-                df.loc[mask, col] = df.loc[mask, col].buffer(0)
+                df.loc[mask, col] = df.loc[mask, col].make_valid()
             elif mask.any():
                 raise ValueError(
                     "You have passed make_valid=False along with "


Test patch:
diff --git a/geopandas/tests/test_overlay.py b/geopandas/tests/test_overlay.py
--- a/geopandas/tests/test_overlay.py
+++ b/geopandas/tests/test_overlay.py
@@ -3,6 +3,7 @@
 import numpy as np
 import pandas as pd
 
+from shapely import make_valid
 from shapely.geometry import Point, Polygon, LineString, GeometryCollection, box
 
 import geopandas
@@ -690,11 +691,11 @@ def test_keep_geom_type_geometry_collection_difference():
     assert_geodataframe_equal(result1, expected1)
 
 
-@pytest.mark.parametrize("make_valid", [True, False])
-def test_overlap_make_valid(make_valid):
+@pytest.mark.parametrize("should_make_valid", [True, False])
+def test_overlap_make_valid(should_make_valid):
     bowtie = Polygon([(1, 1), (9, 9), (9, 1), (1, 9), (1, 1)])
     assert not bowtie.is_valid
-    fixed_bowtie = bowtie.buffer(0)
+    fixed_bowtie = make_valid(bowtie)
     assert fixed_bowtie.is_valid
 
     df1 = GeoDataFrame({"col1": ["region"], "geometry": GeoSeries([box(0, 0, 10, 10)])})
@@ -702,13 +703,13 @@ def test_overlap_make_valid(make_valid):
         {"col1": ["invalid", "valid"], "geometry": GeoSeries([bowtie, fixed_bowtie])}
     )
 
-    if make_valid:
-        df_overlay_bowtie = overlay(df1, df_bowtie, make_valid=make_valid)
+    if should_make_valid:
+        df_overlay_bowtie = overlay(df1, df_bowtie, make_valid=should_make_valid)
         assert df_overlay_bowtie.at[0, "geometry"].equals(fixed_bowtie)
         assert df_overlay_bowtie.at[1, "geometry"].equals(fixed_bowtie)
     else:
         with pytest.raises(ValueError, match="1 invalid input geometries"):
-            overlay(df1, df_bowtie, make_valid=make_valid)
+            overlay(df1, df_bowtie, make_valid=should_make_valid)
 
 
 def test_empty_overlay_return_non_duplicated_columns():