From fbc895abd0dcae23473e57df08709a71f3928626 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Fri, 11 Apr 2025 01:57:32 +0200 Subject: [PATCH 1/4] DOC: document read_dataframe on_invalid=fix parameter value --- pyogrio/geopandas.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyogrio/geopandas.py b/pyogrio/geopandas.py index 45fe3b1a..15876f51 100644 --- a/pyogrio/geopandas.py +++ b/pyogrio/geopandas.py @@ -209,6 +209,9 @@ def read_dataframe( warning will be raised. - **ignore**: invalid WKB geometries will be returned as ``None`` without a warning. + - **fix**: an effort is made to fix invalid input geometries (currently + just unclosed rings). If this is not possible, they are returned as + ``None`` without a warning. Requires GEOS >= 3.11. arrow_to_pandas_kwargs : dict, optional (default: None) When `use_arrow` is True, these kwargs will be passed to the `to_pandas`_ From 7e9fc45b25edc86d78c7dbb3bcfb960b61cfcda1 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Sat, 12 Apr 2025 09:59:54 +0200 Subject: [PATCH 2/4] Update geopandas.py --- pyogrio/geopandas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyogrio/geopandas.py b/pyogrio/geopandas.py index 15876f51..408f6579 100644 --- a/pyogrio/geopandas.py +++ b/pyogrio/geopandas.py @@ -211,7 +211,7 @@ def read_dataframe( without a warning. - **fix**: an effort is made to fix invalid input geometries (currently just unclosed rings). If this is not possible, they are returned as - ``None`` without a warning. Requires GEOS >= 3.11. + ``None`` without a warning. Requires GEOS >= 3.11 and shapely >= 2.1. arrow_to_pandas_kwargs : dict, optional (default: None) When `use_arrow` is True, these kwargs will be passed to the `to_pandas`_ From b376918e04f7c96e67c4edf14ee532e04c55e2e7 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Sat, 12 Apr 2025 10:00:02 +0200 Subject: [PATCH 3/4] Add test --- pyogrio/_compat.py | 1 + pyogrio/tests/test_geopandas_io.py | 33 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/pyogrio/_compat.py b/pyogrio/_compat.py index acfea471..5c89802d 100644 --- a/pyogrio/_compat.py +++ b/pyogrio/_compat.py @@ -46,3 +46,4 @@ HAS_GDAL_GEOS = __gdal_geos_version__ is not None HAS_SHAPELY = shapely is not None and Version(shapely.__version__) >= Version("2.0.0") +SHAPELY_GE_21 = shapely is not None and Version(shapely.__version__) >= Version("2.1.0") diff --git a/pyogrio/tests/test_geopandas_io.py b/pyogrio/tests/test_geopandas_io.py index a65b5baa..beec7663 100644 --- a/pyogrio/tests/test_geopandas_io.py +++ b/pyogrio/tests/test_geopandas_io.py @@ -16,7 +16,13 @@ vsi_listtree, vsi_unlink, ) -from pyogrio._compat import GDAL_GE_352, HAS_ARROW_WRITE_API, HAS_PYPROJ, PANDAS_GE_15 +from pyogrio._compat import ( + GDAL_GE_352, + HAS_ARROW_WRITE_API, + HAS_PYPROJ, + PANDAS_GE_15, + SHAPELY_GE_21, +) from pyogrio.errors import DataLayerError, DataSourceError, FeatureError, GeometryError from pyogrio.geopandas import PANDAS_GE_20, read_dataframe, write_dataframe from pyogrio.raw import ( @@ -1773,23 +1779,29 @@ def test_write_geometry_z_types_auto( @pytest.mark.parametrize( - "on_invalid, message", + "on_invalid, message, expected_wkt", [ ( "warn", "Invalid WKB: geometry is returned as None. IllegalArgumentException: " - "Invalid number of points in LinearRing found 2 - must be 0 or >=", + "Points of LinearRing do not form a closed linestring", + None, ), - ("raise", "Invalid number of points in LinearRing found 2 - must be 0 or >="), - ("ignore", None), + ("raise", "Points of LinearRing do not form a closed linestring", None), + ("ignore", None, None), + ("fix", None, "POLYGON ((0 0, 0 1, 0 0))"), ], ) -def test_read_invalid_poly_ring(tmp_path, use_arrow, on_invalid, message): +@pytest.mark.filterwarnings("ignore:Non closed ring detected:RuntimeWarning") +def test_read_invalid_poly_ring(tmp_path, use_arrow, on_invalid, message, expected_wkt): + # if on_invalid == "fix" and not SHAPELY_GE_21: + # pytest.skip("on_invalid=fix not available for Shapely < 2.1") + if on_invalid == "raise": handler = pytest.raises(shapely.errors.GEOSException, match=message) elif on_invalid == "warn": handler = pytest.warns(match=message) - elif on_invalid == "ignore": + elif on_invalid in ("fix", "ignore"): handler = contextlib.nullcontext() else: raise ValueError(f"unknown value for on_invalid: {on_invalid}") @@ -1803,7 +1815,7 @@ def test_read_invalid_poly_ring(tmp_path, use_arrow, on_invalid, message): "properties": {}, "geometry": { "type": "Polygon", - "coordinates": [ [ [0, 0], [0, 0] ] ] + "coordinates": [ [ [0, 0], [0, 1] ] ] } } ] @@ -1819,7 +1831,10 @@ def test_read_invalid_poly_ring(tmp_path, use_arrow, on_invalid, message): use_arrow=use_arrow, on_invalid=on_invalid, ) - df.geometry.isnull().all() + if expected_wkt is None: + assert df.geometry.iloc[0] is None + else: + assert df.geometry.iloc[0].wkt == expected_wkt def test_read_multisurface(multisurface_file, use_arrow): From d28c039d3f0288479cac44aa8de6b1c708f67829 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Sat, 12 Apr 2025 10:07:27 +0200 Subject: [PATCH 4/4] skip for shapely < 2.1 --- pyogrio/tests/test_geopandas_io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyogrio/tests/test_geopandas_io.py b/pyogrio/tests/test_geopandas_io.py index beec7663..fc4e5c43 100644 --- a/pyogrio/tests/test_geopandas_io.py +++ b/pyogrio/tests/test_geopandas_io.py @@ -1794,8 +1794,8 @@ def test_write_geometry_z_types_auto( ) @pytest.mark.filterwarnings("ignore:Non closed ring detected:RuntimeWarning") def test_read_invalid_poly_ring(tmp_path, use_arrow, on_invalid, message, expected_wkt): - # if on_invalid == "fix" and not SHAPELY_GE_21: - # pytest.skip("on_invalid=fix not available for Shapely < 2.1") + if on_invalid == "fix" and not SHAPELY_GE_21: + pytest.skip("on_invalid=fix not available for Shapely < 2.1") if on_invalid == "raise": handler = pytest.raises(shapely.errors.GEOSException, match=message)