diff --git a/README.rst b/README.rst index c83934b..ec556d8 100644 --- a/README.rst +++ b/README.rst @@ -50,9 +50,9 @@ It was written to provide clean and python only geometries for fastkml_ :target: https://codecov.io/gh/cleder/pygeoif :alt: Codecov -.. image:: https://img.shields.io/badge/property_based_tests-hypothesis-green - :target: https://hypothesis.works - :alt: Hypothesis +.. image:: https://img.shields.io/badge/hypothesis-tested-brightgreen.svg + :alt: Tested with Hypothesis + :target: https://hypothesis.readthedocs.io .. image:: https://img.shields.io/badge/code_style-black-000000.svg :target: https://github.com/psf/ diff --git a/pygeoif/functions.py b/pygeoif/functions.py index 61a9a1c..276dd6a 100644 --- a/pygeoif/functions.py +++ b/pygeoif/functions.py @@ -68,8 +68,8 @@ def centroid(coords: LineType) -> Tuple[Point2D, float]: ans[0] += (coord[0] + next_coord[0]) * area ans[1] += (coord[1] + next_coord[1]) * area - ans[0] = (ans[0]) / (3 * signed_area) - ans[1] = (ans[1]) / (3 * signed_area) + ans[0] = ans[0] / (3 * signed_area) + ans[1] = ans[1] / (3 * signed_area) return cast(Point2D, tuple(ans)), signed_area / 2.0 diff --git a/pygeoif/geometry.py b/pygeoif/geometry.py index c500ebb..06222f6 100644 --- a/pygeoif/geometry.py +++ b/pygeoif/geometry.py @@ -248,21 +248,17 @@ def __init__(self, x: float, y: float, z: Optional[float] = None) -> None: Easting, northing, and elevation. """ + geoms = (x, y, z) if z is not None else (x, y) object.__setattr__( self, "_geoms", - cast( - PointType, - tuple( - coordinate - for coordinate in (x, y, z) - if coordinate is not None and not math.isnan(coordinate) - ), - ), + geoms, ) def __repr__(self) -> str: """Return the representation.""" + if self.is_empty: + return f"{self.geom_type}()" return f"{self.geom_type}{self._geoms}" @property @@ -270,9 +266,9 @@ def is_empty(self) -> bool: """ Return if this geometry is empty. - A Point is considered empty when it has fewer than 2 coordinates. + A Point is considered empty when it has no valid coordinates. """ - return len(self._geoms) < 2 # noqa: PLR2004 + return any(coord is None or math.isnan(coord) for coord in self._geoms) @property def x(self) -> float: @@ -733,7 +729,9 @@ def __len__(self) -> int: def __repr__(self) -> str: """Return the representation.""" - return f"{self.geom_type}({tuple(geom.coords[0] for geom in self._geoms)})" + return ( + f"{self.geom_type}({tuple(geom.coords[0] for geom in self._geoms if geom)})" + ) @property def geoms(self) -> Iterator[Point]: diff --git a/pyproject.toml b/pyproject.toml index 5ada748..17a3994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ authors = [ ] classifiers = [ "Development Status :: 5 - Production/Stable", + "Framework :: Hypothesis", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", @@ -35,6 +36,7 @@ dynamic = [ ] keywords = [ "GIS", + "Hypothesis", "Spatial", "WKT", ] diff --git a/tests/test_factories.py b/tests/test_factories.py index cf2a437..1dbafe9 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -89,7 +89,7 @@ def test_force_2d_polygon() -> None: internal = [(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)] p = geometry.Polygon(external, [internal]) p2d = factories.force_2d(p) - assert p2d.coords[0] == (((0, 0), (0, 2), (2, 2), (2, 0), (0, 0))) + assert p2d.coords[0] == ((0, 0), (0, 2), (2, 2), (2, 0), (0, 0)) assert p2d.coords[1] == ( ((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)), ) @@ -107,7 +107,7 @@ def test_force_2d_polygon() -> None: p = geometry.Polygon(external, [internal]) p2d = factories.force_2d(p) - assert p2d.coords[0] == (((0, 0), (0, 2), (2, 2), (2, 0), (0, 0))) + assert p2d.coords[0] == ((0, 0), (0, 2), (2, 2), (2, 0), (0, 0)) assert p2d.coords[1] == ( ((0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)), ) @@ -261,8 +261,10 @@ class TestWKT: "POINT M (1 1 80)", "LINESTRING(3 4,10 50,20 25)", "LINESTRING (30 10, 10 30, 40 40)", - "MULTIPOLYGON (((10 10, 10 20, 20 20, 20 15, 10 10))," - "((60 60, 70 70, 80 60, 60 60 )))", + ( + "MULTIPOLYGON (((10 10, 10 20, 20 20, 20 15, 10 10))," + "((60 60, 70 70, 80 60, 60 60 )))" + ), """MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))""", @@ -385,8 +387,8 @@ def test_multilinestring(self) -> None: ) assert isinstance(p, geometry.MultiLineString) - assert next(iter(p.geoms)).coords == (((3, 4), (10, 50), (20, 25))) - assert list(p.geoms)[1].coords == (((-5, -8), (-10, -8), (-15, -4))) + assert next(iter(p.geoms)).coords == ((3, 4), (10, 50), (20, 25)) + assert list(p.geoms)[1].coords == ((-5, -8), (-10, -8), (-15, -4)) assert ( p.wkt == "MULTILINESTRING ((3 4, 10 50, " "20 25),(-5 -8, " diff --git a/tests/test_line.py b/tests/test_line.py index 0665a43..fff6b6b 100644 --- a/tests/test_line.py +++ b/tests/test_line.py @@ -1,5 +1,6 @@ """Test LineString.""" +import math from unittest import mock import pytest @@ -20,6 +21,12 @@ def test_coords_get_3d() -> None: assert line.coords == ((0.0, 0.0, 0), (1.0, 1.0, 1)) +def test_coords_get_nan() -> None: + line = geometry.LineString([(0, math.nan, 0), (1, 1, math.nan), (2, 2, 2)]) + + assert line.coords == ((2, 2, 2),) + + def test_empty_points_omitted() -> None: line = geometry.LineString([(0, 0, 0), (None, None, None), (2, 2, 2)]) diff --git a/tests/test_multipoint.py b/tests/test_multipoint.py index 04c3705..7590775 100644 --- a/tests/test_multipoint.py +++ b/tests/test_multipoint.py @@ -1,5 +1,7 @@ """Test MultiPoint.""" +import math + import pytest from pygeoif import geometry @@ -156,9 +158,9 @@ def test_empty() -> None: def test_repr_empty() -> None: - multipoint = geometry.MultiPoint([(None, None)]) + multipoint = geometry.MultiPoint([(math.nan, math.nan)]) - assert repr(multipoint) == "MultiPoint(((),))" + assert repr(multipoint) == "MultiPoint(())" def test_empty_bounds() -> None: