From 0165e891f70db531a8c019b969bb2b80716d0641 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Apr 2026 14:53:23 +0200 Subject: [PATCH 1/2] autotest: update expected results for GEOS 3.15 --- autotest/ogr/ogr_geojson.py | 11 ++++- autotest/ogr/ogr_geom.py | 34 +++++++++++--- autotest/ogr/ogr_mvt.py | 44 ++++++++++--------- autotest/ogr/ogr_xodr.py | 6 +-- .../utilities/test_gdalalg_vector_clip.py | 43 +++++++++++++++--- .../test_gdalalg_vector_layer_algebra.py | 38 +++++++++++----- .../test_gdalalg_vector_reproject.py | 26 ++++++++--- 7 files changed, 149 insertions(+), 53 deletions(-) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 0a96776ec30a..42ce4f888bca 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -2757,6 +2757,13 @@ def test_ogr_geojson_57(tmp_vsimem): { "type": "Feature", "properties": { }, "bbox": [ 135.0, 88.6984598, -135.0, 90.0 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 135.0, 88.6984598 ], [ 180.0, 89.0796531 ], [ 180.0, 90.0 ], [ 135.0, 88.6984598 ] ] ], [ [ [ -135.0, 88.6984598 ], [ -180.0, 90.0 ], [ -180.0, 89.0796531 ], [ -135.0, 88.6984598 ] ] ] ] } } ] }""" + expected_geos_3_15 = """{ + "type": "FeatureCollection", + "bbox": [ 135.0000000, 88.6984598, -135.0000000, 90.0000000 ], + "features": [ + {"type":"Feature","properties":{},"bbox":[135.0,88.6984598,-135.0,90.0],"geometry":{"type":"MultiPolygon","coordinates":[[[[180.0,89.0796531],[180.0,90.0],[135.0,88.6984598],[180.0,89.0796531]]],[[[-135.0,88.6984598],[-180.0,90.0],[-180.0,89.0796531],[-135.0,88.6984598]]]]}} + ] + }""" if ( ogr.GetGEOSVersionMajor() * 10000 + ogr.GetGEOSVersionMinor() * 100 @@ -2767,6 +2774,7 @@ def test_ogr_geojson_57(tmp_vsimem): json.loads(got) == json.loads(expected) or json.loads(got) == json.loads(expected_geos_overlay_ng) or json.loads(got) == json.loads(expected_geos_3_9_1) + or json.loads(got) == json.loads(expected_geos_3_15) ), got # Polar case: EPSG:3031: WGS 84 / Antarctic Polar Stereographic @@ -4696,7 +4704,8 @@ def test_ogr_geojson_write_geometry_validity_fixing_rfc7946(tmp_vsimem): lyr = ds.GetLayer(0) f = lyr.GetNextFeature() assert f.GetGeometryRef().IsValid() - assert "((6.3889058 51.3181847," in f.GetGeometryRef().ExportToWkt() + wkt = f.GetGeometryRef().ExportToWkt() + assert "((6.3889058 51.3181847," in wkt or "((6.3889005 51.3181831," in wkt ############################################################################### diff --git a/autotest/ogr/ogr_geom.py b/autotest/ogr/ogr_geom.py index 808c0a6f10a5..10446cb13872 100755 --- a/autotest/ogr/ogr_geom.py +++ b/autotest/ogr/ogr_geom.py @@ -749,19 +749,25 @@ def test_ogr_geom_transform_geogcrs_to_wgs84(): @pytest.mark.require_geos @pytest.mark.parametrize( - "input_wkt,output_wkt", + "input_wkt,expected_wkt", [ ( "POLYGON((0 100000,100000 0,0 -100000,-100000 0,0 100000),(0 50000,50000 0,0 -50000,-50000 0,0 50000))", - "POLYGON ((90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069,90.0 89.089200825091))", + ( + "POLYGON ((90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069,90.0 89.089200825091))", + "POLYGON ((180.0 89.0892008251069,90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069))", + ), ), ( "POLYGON((50000 -100000,100000 -100000,100000 100000,-100000 100000,-100000 50000,50000 50000,50000 -100000))", - "MULTIPOLYGON (((135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091,135.0 88.7119614804959)),((-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803,-116.565051177078 88.9817007095479)))", + ( + "MULTIPOLYGON (((135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091,135.0 88.7119614804959)),((-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803,-116.565051177078 88.9817007095479)))", + "MULTIPOLYGON (((180.0 89.089200825091,135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091)),((-180 89.5445935108803,-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803)))", + ), ), ], ) -def test_ogr_geom_transform_polar_projected_to_geographic(input_wkt, output_wkt): +def test_ogr_geom_transform_polar_projected_to_geographic(input_wkt, expected_wkt): srs_3996 = osr.SpatialReference() srs_3996.ImportFromEPSG(3996) @@ -777,13 +783,29 @@ def test_ogr_geom_transform_polar_projected_to_geographic(input_wkt, output_wkt) g = ogr.CreateGeometryFromWkt(input_wkt) g = tr.Transform(g) # print(g.ExportToWkt()) - ogrtest.check_feature_geometry(g, output_wkt) + ok = False + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(g, wkt) + ok = True + break + except Exception: + pass + assert ok, f"Got {g.ExportToIsoWkt()}, expected {expected_wkt}" tr = ogr.GeomTransformer(ct, ["WRAPDATELINE=YES"]) g = ogr.CreateGeometryFromWkt(input_wkt) g = tr.Transform(g) # print(g.ExportToWkt()) - ogrtest.check_feature_geometry(g, output_wkt) + ok = False + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(g, wkt) + ok = True + break + except Exception: + pass + assert ok, f"Got {g.ExportToIsoWkt()}, expected {expected_wkt}" ############################################################################### diff --git a/autotest/ogr/ogr_mvt.py b/autotest/ogr/ogr_mvt.py index 12b0a61886ee..58435cc8a359 100755 --- a/autotest/ogr/ogr_mvt.py +++ b/autotest/ogr/ogr_mvt.py @@ -956,18 +956,22 @@ def test_ogr_mvt_write_one_layer(): ) out_f = out_lyr.GetNextFeature() - try: - # GEOS > 3.8 (not sure which minimum version) - ogrtest.check_feature_geometry( - out_f, - "MULTIPOLYGON (((-508764.860266134 1007745.78091176,-498980.920645632 997961.84129126,-508764.860266134 997961.84129126,-508764.860266134 1007745.78091176)),((508764.860266134 1007745.78091176,508764.860266134 997961.84129126,498980.920645632 997961.84129126,508764.860266134 1007745.78091176)))", - ) - except AssertionError: - # Below result with GEOS 3.8 - ogrtest.check_feature_geometry( - out_f, - "MULTIPOLYGON (((498980.920645632 997961.84129126,508764.860266134 1007745.78091176,508764.860266134 997961.84129126,498980.920645632 997961.84129126)),((-508764.860266134 997961.84129126,-508764.860266134 1007745.78091176,-498980.920645632 997961.84129126,-508764.860266134 997961.84129126)))", - ) + out_g = out_f.GetGeometryRef() + print(out_g) + + ok = False + expected_wkt = ( + "MULTIPOLYGON (((498980.920645632 997961.84129126,508764.860266134 1007745.78091176,508764.860266134 997961.84129126,498980.920645632 997961.84129126)),((-508764.860266134 997961.84129126,-508764.860266134 1007745.78091176,-498980.920645632 997961.84129126,-508764.860266134 997961.84129126)))", # GEOS <= 3.8 + "MULTIPOLYGON (((-508764.860266134 1007745.78091176,-498980.920645632 997961.84129126,-508764.860266134 997961.84129126,-508764.860266134 1007745.78091176)),((508764.860266134 1007745.78091176,508764.860266134 997961.84129126,498980.920645632 997961.84129126,508764.860266134 1007745.78091176)))", # GEOS 3.8 to 3.14 + "MULTIPOLYGON (((-508764.860266134 997961.84129126,-508764.860266134 1007745.78091176,-498980.920645632 997961.84129126,-508764.860266134 997961.84129126)),((498980.920645632 997961.84129126,508764.860266134 1007745.78091176,508764.860266134 997961.84129126,498980.920645632 997961.84129126))),", # GEOS 3.15 + ) + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(out_f, wkt) + ok = True + except Exception: + pass + assert ok, f"Got {out_g.ExportToIsoWkt()}, expected {expected_wkt}" for _ in range(2): out_f = out_lyr.GetNextFeature() @@ -1097,7 +1101,7 @@ def test_ogr_mvt_write_one_layer(): @pytest.mark.require_driver("SQLite") @pytest.mark.require_geos -def test_ogr_mvt_write_conf(): +def test_ogr_mvt_write_conf(tmp_vsimem): src_ds = gdal.GetDriverByName("MEM").Create("", 0, 0, 0, gdal.GDT_Unknown) lyr = src_ds.CreateLayer("mylayer") @@ -1116,7 +1120,7 @@ def test_ogr_mvt_write_conf(): } with gdaltest.tempfile("/vsimem/conf.json", json.dumps(conf)): out_ds = gdal.VectorTranslate( - "/vsimem/outmvt", + tmp_vsimem / "outmvt", src_ds, format="MVT", datasetCreationOptions=["CONF=/vsimem/conf.json"], @@ -1124,16 +1128,16 @@ def test_ogr_mvt_write_conf(): assert out_ds is not None out_ds = None - out_ds = ogr.Open("/vsimem/outmvt/1") + out_ds = ogr.Open(tmp_vsimem / "outmvt/1") assert out_ds is not None out_lyr = out_ds.GetLayerByName("TheLayer") assert out_lyr out_ds = None - gdal.RmdirRecursive("/vsimem/outmvt") + gdal.RmdirRecursive(tmp_vsimem / "outmvt") out_ds = gdal.VectorTranslate( - "/vsimem/outmvt", + tmp_vsimem / "outmvt", src_ds, format="MVT", datasetCreationOptions=["CONF=%s" % json.dumps(conf)], @@ -1141,7 +1145,7 @@ def test_ogr_mvt_write_conf(): assert out_ds is not None out_ds = None - out_ds = ogr.Open("/vsimem/outmvt/1") + out_ds = ogr.Open(tmp_vsimem / "outmvt/1") assert out_ds is not None out_lyr = out_ds.GetLayerByName("TheLayer") assert out_lyr @@ -1150,7 +1154,7 @@ def test_ogr_mvt_write_conf(): out_f, "MULTIPOINT (498980.920645632 997961.84129126)" ) - f = gdal.VSIFOpenL("/vsimem/outmvt/metadata.json", "rb") + f = gdal.VSIFOpenL(tmp_vsimem / "outmvt/metadata.json", "rb") assert f is not None data = gdal.VSIFReadL(1, 100000, f).decode("ASCII") gdal.VSIFCloseL(f) @@ -1182,8 +1186,6 @@ def test_ogr_mvt_write_conf(): assert json_json == expected_json_json, data_json["json"] - gdal.RmdirRecursive("/vsimem/outmvt") - ############################################################################### diff --git a/autotest/ogr/ogr_xodr.py b/autotest/ogr/ogr_xodr.py index fc823cd6f9cb..a1b438360cea 100644 --- a/autotest/ogr/ogr_xodr.py +++ b/autotest/ogr/ogr_xodr.py @@ -284,9 +284,9 @@ def ogr_xodr_check_road_mark_geometry_dissolve(lyr, dissolve_tin: bool): == "TIN Z (((618251.72468874 5809502.85743767 102.185583413892,618252.578130818 5809502.64753279 102.169882217474,618252.576002918 5809502.76737822 102.175586986359,618251.72468874 5809502.85743767 102.185583413892)),((618251.72468874 5809502.85743767 102.185583413892,618251.72911469 5809502.73765153 102.179953929071,618252.578130818 5809502.64753279 102.169882217474,618251.72468874 5809502.85743767 102.185583413892)),((618252.576002918 5809502.76737822 102.175586986359,618253.405793556 5809502.53390956 102.159384806253,618253.406427815 5809502.6537686 102.165164856953,618252.576002918 5809502.76737822 102.175586986359)),((618252.576002918 5809502.76737822 102.175586986359,618252.578130818 5809502.64753279 102.169882217474,618253.405793556 5809502.53390956 102.159384806253,618252.576002918 5809502.76737822 102.175586986359)),((618253.406427815 5809502.6537686 102.165164856953,618253.747583384 5809502.4836466 102.15508610511,618253.749521849 5809502.6034901 102.160897877637,618253.406427815 5809502.6537686 102.165164856953)),((618253.406427815 5809502.6537686 102.165164856953,618253.405793556 5809502.53390956 102.159384806253,618253.747583384 5809502.4836466 102.15508610511,618253.406427815 5809502.6537686 102.165164856953)),((618253.749521849 5809502.6034901 102.160897877637,618254.085085834 5809502.43409623 102.150979368988,618254.088411764 5809502.55390772 102.156822862935,618253.749521849 5809502.6034901 102.160897877637)),((618253.749521849 5809502.6034901 102.160897877637,618253.747583384 5809502.4836466 102.15508610511,618254.085085834 5809502.43409623 102.150979368988,618253.749521849 5809502.6034901 102.160897877637)),((618254.088411764 5809502.55390772 102.156822862935,618254.707033446 5809502.33995247 102.143681017939,618254.713189111 5809502.45964901 102.149584000393,618254.088411764 5809502.55390772 102.156822862935)),((618254.088411764 5809502.55390772 102.156822862935,618254.085085834 5809502.43409623 102.150979368988,618254.707033446 5809502.33995247 102.143681017939,618254.088411764 5809502.55390772 102.156822862935)),((618254.713189111 5809502.45964901 102.149584000393,618255.243094449 5809502.25186128 102.137539289407,618255.251990439 5809502.3713828 102.143494733244,618254.713189111 5809502.45964901 102.149584000393)),((618254.713189111 5809502.45964901 102.149584000393,618254.707033446 5809502.33995247 102.143681017939,618255.243094449 5809502.25186128 102.137539289407,618254.713189111 5809502.45964901 102.149584000393)),((618255.251990439 5809502.3713828 102.143494733244,618256.346892323 5809502.04568328 102.125419284058,618256.362382638 5809502.16452451 102.131486672596,618255.251990439 5809502.3713828 102.143494733244)),((618255.251990439 5809502.3713828 102.143494733244,618255.243094449 5809502.25186128 102.137539289407,618256.346892323 5809502.04568328 102.125419284058,618255.251990439 5809502.3713828 102.143494733244)),((618256.362382638 5809502.16452451 102.131486672596,618256.86502563 5809501.93528991 102.120031826125,618256.884079624 5809502.05360925 102.126153745722,618256.362382638 5809502.16452451 102.131486672596)),((618256.362382638 5809502.16452451 102.131486672596,618256.346892323 5809502.04568328 102.125419284058,618256.86502563 5809501.93528991 102.120031826125,618256.362382638 5809502.16452451 102.131486672596)),((618256.884079624 5809502.05360925 102.126153745722,618257.370482622 5809501.81785335 102.11500305465,618257.393309764 5809501.93550017 102.12117950404,618256.884079624 5809502.05360925 102.126153745722)),((618256.884079624 5809502.05360925 102.126153745722,618256.86502563 5809501.93528991 102.120031826125,618257.370482622 5809501.81785335 102.11500305465,618256.884079624 5809502.05360925 102.126153745722)))" ), "wrong geometry created for RoadMark" else: - assert ( - wkt - == "POLYGON ((618253.747583384 5809502.4836466 102.15508610511,618253.405793556 5809502.53390956 102.159384806253,618252.578130818 5809502.64753279 102.169882217474,618251.72911469 5809502.73765153 102.179953929071,618251.72468874 5809502.85743767 102.185583413892,618252.576002918 5809502.76737822 102.175586986359,618253.406427815 5809502.6537686 102.165164856953,618253.749521849 5809502.6034901 102.160897877637,618254.088411764 5809502.55390772 102.156822862935,618254.713189111 5809502.45964901 102.149584000393,618255.251990439 5809502.3713828 102.143494733244,618256.362382638 5809502.16452451 102.131486672596,618256.884079624 5809502.05360925 102.126153745722,618257.393309764 5809501.93550017 102.12117950404,618257.370482622 5809501.81785335 102.11500305465,618256.86502563 5809501.93528991 102.120031826125,618256.346892323 5809502.04568328 102.125419284058,618255.243094449 5809502.25186128 102.137539289407,618254.707033446 5809502.33995247 102.143681017939,618254.085085834 5809502.43409623 102.150979368988,618253.747583384 5809502.4836466 102.15508610511))" + assert wkt in ( + "POLYGON ((618253.747583384 5809502.4836466 102.15508610511,618253.405793556 5809502.53390956 102.159384806253,618252.578130818 5809502.64753279 102.169882217474,618251.72911469 5809502.73765153 102.179953929071,618251.72468874 5809502.85743767 102.185583413892,618252.576002918 5809502.76737822 102.175586986359,618253.406427815 5809502.6537686 102.165164856953,618253.749521849 5809502.6034901 102.160897877637,618254.088411764 5809502.55390772 102.156822862935,618254.713189111 5809502.45964901 102.149584000393,618255.251990439 5809502.3713828 102.143494733244,618256.362382638 5809502.16452451 102.131486672596,618256.884079624 5809502.05360925 102.126153745722,618257.393309764 5809501.93550017 102.12117950404,618257.370482622 5809501.81785335 102.11500305465,618256.86502563 5809501.93528991 102.120031826125,618256.346892323 5809502.04568328 102.125419284058,618255.243094449 5809502.25186128 102.137539289407,618254.707033446 5809502.33995247 102.143681017939,618254.085085834 5809502.43409623 102.150979368988,618253.747583384 5809502.4836466 102.15508610511))", + "POLYGON ((618254.707033446 5809502.33995247 102.143681017939,618254.085085834 5809502.43409623 102.150979368988,618253.747583384 5809502.4836466 102.15508610511,618253.405793556 5809502.53390956 102.159384806253,618252.578130818 5809502.64753279 102.169882217474,618251.72911469 5809502.73765153 102.179953929071,618251.72468874 5809502.85743767 102.185583413892,618252.576002918 5809502.76737822 102.175586986359,618253.406427815 5809502.6537686 102.165164856953,618253.749521849 5809502.6034901 102.160897877637,618254.088411764 5809502.55390772 102.156822862935,618254.713189111 5809502.45964901 102.149584000393,618255.251990439 5809502.3713828 102.143494733244,618256.362382638 5809502.16452451 102.131486672596,618256.884079624 5809502.05360925 102.126153745722,618257.393309764 5809501.93550017 102.12117950404,618257.370482622 5809501.81785335 102.11500305465,618256.86502563 5809501.93528991 102.120031826125,618256.346892323 5809502.04568328 102.125419284058,618255.243094449 5809502.25186128 102.137539289407,618254.707033446 5809502.33995247 102.143681017939))", # GEOS 3.15 ), "wrong geometry created for dissolved RoadMark" diff --git a/autotest/utilities/test_gdalalg_vector_clip.py b/autotest/utilities/test_gdalalg_vector_clip.py index 90d2273e708b..d34f1a64f062 100755 --- a/autotest/utilities/test_gdalalg_vector_clip.py +++ b/autotest/utilities/test_gdalalg_vector_clip.py @@ -187,9 +187,20 @@ def test_gdalalg_vector_clip_bbox_srs(): assert out_lyr.GetSpatialRef().GetAuthorityCode(None) == "4326" out_f = out_lyr.GetNextFeature() assert out_f["foo"] == "bar" - ogrtest.check_feature_geometry( - out_f, "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))" + + out_g = out_f.GetGeometryRef() + ok = False + expected_wkt = ( + "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))", + "POLYGON ((0.2 0.3,0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3))", # GEOS 3.15 ) + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(out_g, wkt) + ok = True + except Exception: + pass + assert ok, f"Got {out_g.ExportToIsoWkt()}, expected {expected_wkt}" assert out_lyr.GetNextFeature() is None @@ -838,9 +849,19 @@ def test_gdalalg_vector_clip_like_raster(): out_lyr = out_ds.GetLayer(0) out_f = out_lyr.GetNextFeature() assert out_f["foo"] == "bar" - ogrtest.check_feature_geometry( - out_f, "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))" + out_g = out_f.GetGeometryRef() + ok = False + expected_wkt = ( + "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))", + "POLYGON ((0.2 0.3,0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3))", # GEOS 3.15 ) + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(out_g, wkt) + ok = True + except Exception: + pass + assert ok, f"Got {out_g.ExportToIsoWkt()}, expected {expected_wkt}" assert out_lyr.GetNextFeature() is None @@ -879,9 +900,19 @@ def test_gdalalg_vector_clip_like_raster_srs(): out_lyr = out_ds.GetLayer(0) out_f = out_lyr.GetNextFeature() assert out_f["foo"] == "bar" - ogrtest.check_feature_geometry( - out_f, "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))" + out_g = out_f.GetGeometryRef() + ok = False + expected_wkt = ( + "POLYGON ((0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3,0.2 0.8))", + "POLYGON ((0.2 0.3,0.2 0.8,0.7 0.8,0.7 0.3,0.2 0.3))", # GEOS 3.15 ) + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(out_g, wkt) + ok = True + except Exception: + pass + assert ok, f"Got {out_g.ExportToIsoWkt()}, expected {expected_wkt}" assert out_lyr.GetNextFeature() is None diff --git a/autotest/utilities/test_gdalalg_vector_layer_algebra.py b/autotest/utilities/test_gdalalg_vector_layer_algebra.py index db81f70ef82d..f2e18ed86c94 100755 --- a/autotest/utilities/test_gdalalg_vector_layer_algebra.py +++ b/autotest/utilities/test_gdalalg_vector_layer_algebra.py @@ -11,6 +11,7 @@ # SPDX-License-Identifier: MIT ############################################################################### +import ogrtest import pytest from osgeo import gdal, ogr @@ -589,7 +590,9 @@ def test_gdal_vector_layer_algebra_intersection(): f = out_lyr.GetNextFeature() assert f["input_a"] == "foo" assert f["method_b"] == "bar" - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ) f = out_lyr.GetNextFeature() assert f["input_a"] == "foo2" @@ -619,14 +622,15 @@ def test_gdal_vector_layer_algebra_sym_difference(): f = out_lyr.GetNextFeature() assert f["input_a"] == "foo" assert f["method_b"] is None - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ) f = out_lyr.GetNextFeature() assert f["input_a"] is None assert f["method_b"] == "bar" - assert ( - f.GetGeometryRef().ExportToWkt() - == "POLYGON ((15 10,15 0,10 0,10 10,15 10))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((15 10,15 0,10 0,10 10,15 10))" ) f = out_lyr.GetNextFeature() @@ -657,12 +661,16 @@ def test_gdal_vector_layer_algebra_identity(): f = out_lyr.GetNextFeature() assert f["input_a"] == "foo" assert f["method_b"] == "bar" - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ) f = out_lyr.GetNextFeature() assert f["input_a"] == "foo" assert f["method_b"] is None - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ) f = out_lyr.GetNextFeature() assert f["input_a"] == "foo2" @@ -690,11 +698,15 @@ def test_gdal_vector_layer_algebra_update(): f = out_lyr.GetNextFeature() assert f["a"] == "foo" - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ) f = out_lyr.GetNextFeature() assert f["a"] is None - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((5 0,15 0,15 10,5 10,5 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((5 0,15 0,15 10,5 10,5 0))" + ) f = out_lyr.GetNextFeature() assert f["a"] is None @@ -725,7 +737,9 @@ def test_gdal_vector_layer_algebra_clip(): f = out_lyr.GetNextFeature() assert f["a"] == "foo" - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((5 0,5 10,10 10,10 0,5 0))" + ) f = out_lyr.GetNextFeature() assert f["a"] == "foo2" @@ -752,4 +766,6 @@ def test_gdal_vector_layer_algebra_erase(): f = out_lyr.GetNextFeature() assert f["a"] == "foo" - assert f.GetGeometryRef().ExportToWkt() == "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ogrtest.check_feature_geometry( + f.GetGeometryRef(), "POLYGON ((0 0,0 10,5 10,5 0,0 0))" + ) diff --git a/autotest/utilities/test_gdalalg_vector_reproject.py b/autotest/utilities/test_gdalalg_vector_reproject.py index cee018dfb23b..3ab04cc0c5f2 100755 --- a/autotest/utilities/test_gdalalg_vector_reproject.py +++ b/autotest/utilities/test_gdalalg_vector_reproject.py @@ -118,19 +118,29 @@ def test_gdalalg_vector_reproject_complete_dst_crs(): @pytest.mark.require_geos @pytest.mark.parametrize( - "input_wkt,output_wkt", + "input_wkt,expected_wkt", [ ( "POLYGON((0 100000,100000 0,0 -100000,-100000 0,0 100000),(0 50000,50000 0,0 -50000,-50000 0,0 50000))", - "POLYGON ((90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069,90.0 89.089200825091))", + ( + "POLYGON ((90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069,90.0 89.089200825091))", + # Below is with GEOS 3.15 + "POLYGON ((180.0 89.0892008251069,90.0 89.089200825091,0.0 89.089200825091,-90 89.089200825091,-180 89.0892008251069,-180 89.5445935108883,-90 89.5445935108803,0.0 89.5445935108803,90.0 89.5445935108803,180.0 89.5445935108883,180.0 89.0892008251069))", + ), ), ( "POLYGON((50000 -100000,100000 -100000,100000 100000,-100000 100000,-100000 50000,50000 50000,50000 -100000))", - "MULTIPOLYGON (((135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091,135.0 88.7119614804959)),((-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803,-116.565051177078 88.9817007095479)))", + ( + "MULTIPOLYGON (((135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091,135.0 88.7119614804959)),((-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803,-116.565051177078 88.9817007095479)))", + # Below is with GEOS 3.15 + "MULTIPOLYGON (((180.0 89.089200825091,135.0 88.7119614804959,45.0 88.7119614804959,26.565051177078 88.9817007095479,135.0 89.3559612202261,180.0 89.5445935108803,180.0 89.089200825091)),((-180 89.5445935108803,-116.565051177078 88.9817007095479,-135 88.7119614804959,-180 89.089200825091,-180 89.5445935108803)))", + ), ), ], ) -def test_gdalalg_vector_reproject_polar_projected_to_geographic(input_wkt, output_wkt): +def test_gdalalg_vector_reproject_polar_projected_to_geographic( + input_wkt, expected_wkt +): srs_3996 = osr.SpatialReference() srs_3996.ImportFromEPSG(3996) @@ -154,4 +164,10 @@ def test_gdalalg_vector_reproject_polar_projected_to_geographic(input_wkt, outpu out_lyr = out_ds.GetLayer(0) out_f = out_lyr.GetNextFeature() out_g = out_f.GetGeometryRef() - ogrtest.check_feature_geometry(out_g, output_wkt) + for wkt in expected_wkt: + try: + ogrtest.check_feature_geometry(out_g, wkt) + return + except Exception: + pass + assert False, f"Got {out_g.ExportToIsoWkt()}, expected {expected_wkt}" From 4ad0daf6169e839c15b9554848617c887fa8322f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Apr 2026 09:28:27 +0200 Subject: [PATCH 2/2] OGRGeometryFactory::transformWithOptions(): make sure polygons are closed in polar reprojection code Exposed by a recent change in GEOS (https://github.com/libgeos/geos/pull/1412) Co-authored-by: Dan Baston --- ogr/ogrgeometryfactory.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/ogr/ogrgeometryfactory.cpp b/ogr/ogrgeometryfactory.cpp index 7b877e477412..013ea478e2ed 100644 --- a/ogr/ogrgeometryfactory.cpp +++ b/ogr/ogrgeometryfactory.cpp @@ -3238,7 +3238,7 @@ static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti, /* RemovePoint() */ /************************************************************************/ -static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint) +static void RemovePoint(OGRGeometry *poGeom, const OGRPoint *poPoint) { const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType()); switch (eType) @@ -3275,14 +3275,11 @@ static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint) case wkbPolygon: { OGRPolygon *poPoly = poGeom->toPolygon(); - if (poPoly->getExteriorRing() != nullptr) + for (auto *poRing : *poPoly) { - RemovePoint(poPoly->getExteriorRing(), poPoint); - for (int i = 0; i < poPoly->getNumInteriorRings(); ++i) - { - RemovePoint(poPoly->getInteriorRing(i), poPoint); - } + RemovePoint(poRing, poPoint); } + poPoly->closeRings(); break; } @@ -3291,9 +3288,9 @@ static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint) case wkbGeometryCollection: { OGRGeometryCollection *poGC = poGeom->toGeometryCollection(); - for (int i = 0; i < poGC->getNumGeometries(); ++i) + for (auto *poPart : *poGC) { - RemovePoint(poGC->getGeometryRef(i), poPoint); + RemovePoint(poPart, poPoint); } break; }