ENH: Add back make_valid to overlay and use to skip the buffer(0) calls (#1802)

This commit is contained in:
bretttully
2021-02-28 10:29:03 +01:00
committed by GitHub
parent 919a8f26b5
commit 9ae42ff0da
2 changed files with 51 additions and 15 deletions
+22 -1
View File
@@ -2,7 +2,7 @@ import os
import pandas as pd
from shapely.geometry import Point, Polygon, LineString, GeometryCollection
from shapely.geometry import Point, Polygon, LineString, GeometryCollection, box
from fiona.errors import DriverError
import geopandas
@@ -571,3 +571,24 @@ def test_keep_geom_type_geometry_collection():
intersection = overlay(df1, df2, keep_geom_type=False)
assert len(intersection) == 1
assert (intersection.geom_type == "GeometryCollection").all()
@pytest.mark.parametrize("make_valid", [True, False])
def test_overlap_make_valid(make_valid):
bowtie = Polygon([(1, 1), (9, 9), (9, 1), (1, 9), (1, 1)])
assert not bowtie.is_valid
fixed_bowtie = bowtie.buffer(0)
assert fixed_bowtie.is_valid
df1 = GeoDataFrame({"col1": ["region"], "geometry": GeoSeries([box(0, 0, 10, 10)])})
df_bowtie = GeoDataFrame(
{"col1": ["invalid", "valid"], "geometry": GeoSeries([bowtie, fixed_bowtie])}
)
if make_valid:
df_overlay_bowtie = overlay(df1, df_bowtie, make_valid=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)
+29 -14
View File
@@ -137,7 +137,7 @@ def _overlay_union(df1, df2):
return dfunion.reindex(columns=columns)
def overlay(df1, df2, how="intersection", keep_geom_type=None):
def overlay(df1, df2, how="intersection", keep_geom_type=None, make_valid=True):
"""Perform spatial overlay between two GeoDataFrames.
Currently only supports data GeoDataFrames with uniform geometry types,
@@ -159,6 +159,9 @@ def overlay(df1, df2, how="intersection", keep_geom_type=None):
if False, return all resulting geometries. Default is None,
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 False, a `ValueError` is raised if any input geometries are invalid.
Returns
-------
@@ -179,9 +182,9 @@ def overlay(df1, df2, how="intersection", keep_geom_type=None):
>>> geopandas.overlay(df1, df2, how='union')
df1_data df2_data geometry
0 1.0 1.0 POLYGON ((1.00000 2.00000, 2.00000 2.00000, 2....
1 2.0 1.0 POLYGON ((2.00000 2.00000, 2.00000 3.00000, 3....
1 2.0 1.0 POLYGON ((3.00000 2.00000, 2.00000 2.00000, 2....
2 2.0 2.0 POLYGON ((3.00000 4.00000, 4.00000 4.00000, 4....
3 1.0 NaN POLYGON ((0.00000 0.00000, 0.00000 2.00000, 1....
3 1.0 NaN POLYGON ((2.00000 1.00000, 2.00000 0.00000, 0....
4 2.0 NaN MULTIPOLYGON (((3.00000 3.00000, 4.00000 3.000...
5 NaN 1.0 MULTIPOLYGON (((2.00000 2.00000, 3.00000 2.000...
6 NaN 2.0 POLYGON ((3.00000 4.00000, 3.00000 5.00000, 5....
@@ -189,27 +192,27 @@ def overlay(df1, df2, how="intersection", keep_geom_type=None):
>>> geopandas.overlay(df1, df2, how='intersection')
df1_data df2_data geometry
0 1 1 POLYGON ((1.00000 2.00000, 2.00000 2.00000, 2....
1 2 1 POLYGON ((2.00000 2.00000, 2.00000 3.00000, 3....
1 2 1 POLYGON ((3.00000 2.00000, 2.00000 2.00000, 2....
2 2 2 POLYGON ((3.00000 4.00000, 4.00000 4.00000, 4....
>>> geopandas.overlay(df1, df2, how='symmetric_difference')
df1_data df2_data geometry
0 1.0 NaN POLYGON ((0.00000 0.00000, 0.00000 2.00000, 1....
0 1.0 NaN POLYGON ((2.00000 1.00000, 2.00000 0.00000, 0....
1 2.0 NaN MULTIPOLYGON (((3.00000 3.00000, 4.00000 3.000...
2 NaN 1.0 MULTIPOLYGON (((2.00000 2.00000, 3.00000 2.000...
3 NaN 2.0 POLYGON ((3.00000 4.00000, 3.00000 5.00000, 5....
>>> geopandas.overlay(df1, df2, how='difference')
geometry df1_data
0 POLYGON ((0.00000 0.00000, 0.00000 2.00000, 1.... 1
0 POLYGON ((2.00000 1.00000, 2.00000 0.00000, 0.... 1
1 MULTIPOLYGON (((2.00000 3.00000, 2.00000 4.000... 2
>>> geopandas.overlay(df1, df2, how='identity')
df1_data df2_data geometry
0 1.0 1.0 POLYGON ((1.00000 2.00000, 2.00000 2.00000, 2....
1 2.0 1.0 POLYGON ((2.00000 2.00000, 2.00000 3.00000, 3....
1 2.0 1.0 POLYGON ((3.00000 2.00000, 2.00000 2.00000, 2....
2 2.0 2.0 POLYGON ((3.00000 4.00000, 4.00000 4.00000, 4....
3 1.0 NaN POLYGON ((0.00000 0.00000, 0.00000 2.00000, 1....
3 1.0 NaN POLYGON ((2.00000 1.00000, 2.00000 0.00000, 0....
4 2.0 NaN MULTIPOLYGON (((3.00000 3.00000, 4.00000 3.000...
See also
@@ -262,12 +265,24 @@ def overlay(df1, df2, how="intersection", keep_geom_type=None):
)
# Computations
df1 = df1.copy()
df2 = df2.copy()
if df1.geom_type.isin(polys).all():
df1[df1._geometry_column_name] = df1.geometry.buffer(0)
if df2.geom_type.isin(polys).all():
df2[df2._geometry_column_name] = df2.geometry.buffer(0)
def _make_valid(df):
df = df.copy()
if df.geom_type.isin(polys).all():
mask = ~df.geometry.is_valid
col = df._geometry_column_name
if make_valid:
df.loc[mask, col] = df.loc[mask, col].buffer(0)
elif mask.any():
raise ValueError(
"You have passed make_valid=False along with "
f"{mask.sum()} invalid input geometries. "
"Use make_valid=True or make sure that all geometries "
"are valid before using overlay."
)
return df
df1 = _make_valid(df1)
df2 = _make_valid(df2)
with warnings.catch_warnings(): # CRS checked above, supress array-level warning
warnings.filterwarnings("ignore", message="CRS mismatch between the CRS")