Skip to content

Commit

Permalink
refactor handling of empty points
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Apr 27, 2024
1 parent 2d359a0 commit b8d6596
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 24 deletions.
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
4 changes: 2 additions & 2 deletions pygeoif/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 9 additions & 11 deletions pygeoif/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,31 +248,27 @@ 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
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:
Expand Down Expand Up @@ -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]:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -35,6 +36,7 @@ dynamic = [
]
keywords = [
"GIS",
"Hypothesis",
"Spatial",
"WKT",
]
Expand Down
14 changes: 8 additions & 6 deletions tests/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
)
Expand All @@ -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)),
)
Expand Down Expand Up @@ -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)))""",
Expand Down Expand Up @@ -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, "
Expand Down
7 changes: 7 additions & 0 deletions tests/test_line.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test LineString."""

import math
from unittest import mock

import pytest
Expand All @@ -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)])

Expand Down
6 changes: 4 additions & 2 deletions tests/test_multipoint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test MultiPoint."""

import math

import pytest

from pygeoif import geometry
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit b8d6596

Please sign in to comment.