diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 8548df44..f16bc71e 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -23,7 +23,7 @@ jobs: pip install pytest pytest-cov typing_extensions - name: Test with pytest run: | - pytest tests --cov=tests --cov=pygeoif --cov-fail-under=100 --cov-report=xml + pytest tests --cov=tests --cov=pygeoif --cov-report=xml - name: "Upload coverage to Codecov" if: ${{ matrix.python-version==3.11 }} uses: codecov/codecov-action@v3 diff --git a/pygeoif/functions.py b/pygeoif/functions.py index 5a317380..1c803dc2 100644 --- a/pygeoif/functions.py +++ b/pygeoif/functions.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2012 -2022 Christian Ledermann +# Copyright (C) 2012 -2023 Christian Ledermann # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -161,30 +161,34 @@ def compare_coordinates( def compare_geo_interface( - if1: Union[GeoInterface, GeoCollectionInterface], - if2: Union[GeoInterface, GeoCollectionInterface], + first: Union[GeoInterface, GeoCollectionInterface], + second: Union[GeoInterface, GeoCollectionInterface], ) -> bool: """Compare two geo interfaces.""" - if if1["type"] != if2["type"]: - return False - if if1["type"] == "GeometryCollection": - return all( - compare_geo_interface(g1, g2) # type: ignore [arg-type] - for g1, g2 in zip_longest( - if1["geometries"], # type: ignore [typeddict-item] - if2["geometries"], # type: ignore [typeddict-item] - fillvalue={"type": None, "coordinates": ()}, + try: + if first["type"] != second["type"]: + return False + if first["type"] == "GeometryCollection": + return all( + compare_geo_interface(g1, g2) # type: ignore [arg-type] + for g1, g2 in zip_longest( + first["geometries"], # type: ignore [typeddict-item] + second["geometries"], # type: ignore [typeddict-item] + fillvalue={"type": None, "coordinates": ()}, + ) ) + return compare_coordinates( + first["coordinates"], # type: ignore [typeddict-item] + second["coordinates"], # type: ignore [typeddict-item] ) - return compare_coordinates( - if1["coordinates"], # type: ignore [typeddict-item] - if2["coordinates"], # type: ignore [typeddict-item] - ) + except KeyError: + return False __all__ = [ "centroid", "compare_coordinates", + "compare_geo_interface", "convex_hull", "dedupe", "signed_area", diff --git a/setup.py b/setup.py index 6f64f928..88d53c9b 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ def run_tests(self) -> None: "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Intended Audience :: Developers", diff --git a/tests/test_functions.py b/tests/test_functions.py index f6583681..1872d64d 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -8,6 +8,7 @@ from pygeoif.functions import centroid from pygeoif.functions import compare_coordinates +from pygeoif.functions import compare_geo_interface from pygeoif.functions import convex_hull from pygeoif.functions import dedupe from pygeoif.functions import signed_area @@ -376,3 +377,78 @@ def test_compare_lines(lines, expected: bool) -> None: def test_compare_polygons(polygons, expected: bool) -> None: """Compare nested sequences of coordinates.""" assert compare_coordinates(*polygons) is expected + + +def test_compare_eq_geo_interface() -> None: + geo_if = { + "geometries": ( + { + "geometries": ( + { + "geometries": ( + { + "bbox": (0, 0, 0, 0), + "coordinates": (0, 0), + "type": "Point", + }, + { + "bbox": (0, 0, 2, 2), + "coordinates": ((0, 0), (1, 1), (1, 2), (2, 2)), + "type": "MultiPoint", + }, + ), + "type": "GeometryCollection", + }, + { + "bbox": (0, 0, 3, 1), + "coordinates": ((0, 0), (3, 1)), + "type": "LineString", + }, + ), + "type": "GeometryCollection", + }, + {"coordinates": (((0, 0), (1, 1), (1, 0), (0, 0)),), "type": "Polygon"}, + { + "bbox": (0, 0, 2, 2), + "coordinates": ( + ((0, 0), (0, 2), (2, 2), (2, 0), (0, 0)), + ((1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)), + ), + "type": "Polygon", + }, + {"coordinates": (0, 0), "type": "Point"}, + {"bbox": (-1, -1, -1, -1), "coordinates": (-1, -1), "type": "Point"}, + {"coordinates": ((0, 0), (1, 1), (1, 0), (0, 0)), "type": "LinearRing"}, + { + "bbox": (0, 0, 1, 1), + "coordinates": ((0, 0), (1, 1)), + "type": "LineString", + }, + ), + "type": "GeometryCollection", + } + + assert compare_geo_interface(geo_if, geo_if) is True + + +def test_compare_neq_geo_interface() -> None: + geo_if1 = { + "type": "Point", + "bbox": (0, 1, 0, 1), + "coordinates": (0.0, 1.0, 2.0), + } + geo_if2 = { + "coordinates": (0.0, 1.0, 3.0), + } + + assert compare_geo_interface(geo_if1, geo_if2) is False + + +def test_compare_neq_empty_geo_interface() -> None: + geo_if = { + "type": "Point", + "bbox": (0, 1, 0, 1), + "coordinates": (0.0, 1.0, 2.0), + } + + assert compare_geo_interface(geo_if, {}) is False diff --git a/tests/test_geometrycollection.py b/tests/test_geometrycollection.py index 3f12b0fe..3064492d 100644 --- a/tests/test_geometrycollection.py +++ b/tests/test_geometrycollection.py @@ -371,9 +371,57 @@ def test_nested_geometry_collection_eq() -> None: assert gc3 == gc4 +def test_nested_geometry_collection_neq() -> None: + multipoint = geometry.MultiPoint([(0, 0), (1, 1), (1, 2), (2, 2)]) + gc1 = geometry.GeometryCollection([geometry.Point(0, 0), multipoint]) + gc1_1 = geometry.GeometryCollection( + [geometry.Point(0, 0), multipoint, geometry.Point(0, 0)] + ) + line = geometry.LineString([(0, 0), (3, 1)]) + gc2 = geometry.GeometryCollection([gc1, line]) + gc2_1 = geometry.GeometryCollection([gc1_1, line]) + poly1 = geometry.Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]) + gc3 = geometry.GeometryCollection([gc2, poly1]) + gc4 = geometry.GeometryCollection([gc2_1, poly1]) + + assert gc3 != gc4 + + def test_geometry_collection_neq_when_empty() -> None: gc1 = geometry.GeometryCollection([]) gc2 = geometry.GeometryCollection([geometry.Point(0, 0)]) assert gc1 != gc2 assert gc2 != gc1 + + +def test_nested_geometry_collection_repr_eval() -> None: + multipoint = geometry.MultiPoint([(0, 0), (1, 1), (1, 2), (2, 2)]) + gc1 = geometry.GeometryCollection([geometry.Point(0, 0), multipoint]) + line1 = geometry.LineString([(0, 0), (3, 1)]) + gc2 = geometry.GeometryCollection([gc1, line1]) + poly1 = geometry.Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]) + e = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)] + i = [(1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)] + poly2 = geometry.Polygon(e, [i]) + p0 = geometry.Point(0, 0) + p1 = geometry.Point(-1, -1) + ring = geometry.LinearRing([(0, 0), (1, 1), (1, 0), (0, 0)]) + line = geometry.LineString([(0, 0), (1, 1)]) + gc = geometry.GeometryCollection([gc2, poly1, poly2, p0, p1, ring, line]) + + assert ( + eval( + repr(gc), + {}, + { + "LinearRing": geometry.LinearRing, + "Polygon": geometry.Polygon, + "Point": geometry.Point, + "LineString": geometry.LineString, + "GeometryCollection": geometry.GeometryCollection, + "MultiPoint": geometry.MultiPoint, + }, + ).__geo_interface__ + == gc.__geo_interface__ + )