Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add has_z function to Geometries. #103

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 51 additions & 22 deletions geojson_pydantic/geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,32 @@ def _position_wkt_coordinates(position: Position) -> str:
return " ".join(str(number) for number in position)


def _position_has_z(position: Position) -> bool:
return len(position) == 3


def _position_list_wkt_coordinates(positions: List[Position]) -> str:
"""Converts a list of Positions to WKT Coordinates."""
return ", ".join(_position_wkt_coordinates(position) for position in positions)


def _position_list_has_z(positions: List[Position]) -> bool:
"""Checks if any position in a list has a Z."""
return any(_position_has_z(position) for position in positions)


def _lines_wtk_coordinates(lines: List[List[Position]]) -> str:
"""Converts lines to WKT Coordinates."""
return ", ".join(f"({_position_list_wkt_coordinates(line)})" for line in lines)


def _lines_has_z(lines: List[List[Position]]) -> bool:
"""Checks if any position in a list has a Z."""
return any(
_position_has_z(position) for positions in lines for position in positions
)


class _GeometryBase(BaseModel, abc.ABC):
"""Base class for geometry models"""

Expand All @@ -49,13 +65,13 @@ def __geo_interface__(self) -> Dict[str, Any]:

@property
@abc.abstractmethod
def _wkt_coordinates(self) -> str:
def has_z(self) -> bool:
"""Checks if any coordinate has a Z value."""
...

@property
@abc.abstractmethod
def _wkt_inset(self) -> str:
"""Return Z for 3 dimensional geometry or an empty string for 2 dimensions."""
def _wkt_coordinates(self) -> str:
...

@property
Expand All @@ -66,11 +82,18 @@ def _wkt_type(self) -> str:
@property
def wkt(self) -> str:
"""Return the Well Known Text representation."""
return self._wkt_type + (
f"{self._wkt_inset}({self._wkt_coordinates})"
if self.coordinates
else " EMPTY"
)
# Start with the WKT Type
wkt = self._wkt_type
if self.coordinates:
# If any of the coordinates have a Z add a "Z" to the WKT
wkt += " Z " if self.has_z else " "
# Add the rest of the WKT inside parentheses
wkt += f"({self._wkt_coordinates})"
else:
# Otherwise it will be "EMPTY"
wkt += " EMPTY"

return wkt


class Point(_GeometryBase):
Expand All @@ -80,12 +103,13 @@ class Point(_GeometryBase):
coordinates: Position

@property
def _wkt_coordinates(self) -> str:
return _position_wkt_coordinates(self.coordinates)
def has_z(self) -> bool:
"""Checks if any coordinate has a Z value."""
return _position_has_z(self.coordinates)

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates) == 3 else " "
def _wkt_coordinates(self) -> str:
return _position_wkt_coordinates(self.coordinates)


class MultiPoint(_GeometryBase):
Expand All @@ -95,8 +119,9 @@ class MultiPoint(_GeometryBase):
coordinates: MultiPointCoords

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates[0]) == 3 else " "
def has_z(self) -> bool:
"""Checks if any coordinate has a Z value."""
return _position_list_has_z(self.coordinates)

@property
def _wkt_coordinates(self) -> str:
Expand All @@ -110,8 +135,9 @@ class LineString(_GeometryBase):
coordinates: LineStringCoords

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates[0]) == 3 else " "
def has_z(self) -> bool:
"""Checks if any coordinate has a Z value."""
return _position_list_has_z(self.coordinates)

@property
def _wkt_coordinates(self) -> str:
Expand All @@ -125,8 +151,9 @@ class MultiLineString(_GeometryBase):
coordinates: MultiLineStringCoords

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates[0][0]) == 3 else " "
def has_z(self) -> bool:
"""Checks if any coordinate has a Z value."""
return _lines_has_z(self.coordinates)

@property
def _wkt_coordinates(self) -> str:
Expand Down Expand Up @@ -172,8 +199,9 @@ def interiors(self) -> Iterator[LinearRing]:
)

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates[0][0]) == 3 else " "
def has_z(self) -> bool:
"""Checks if any coordinates have a Z value."""
return _lines_has_z(self.coordinates)

@property
def _wkt_coordinates(self) -> str:
Expand All @@ -199,8 +227,9 @@ class MultiPolygon(_GeometryBase):
coordinates: MultiPolygonCoords

@property
def _wkt_inset(self) -> str:
return " Z " if len(self.coordinates[0][0][0]) == 3 else " "
def has_z(self) -> bool:
"""Checks if any coordinates have a Z value."""
return any(_lines_has_z(polygon) for polygon in self.coordinates)

@property
def _wkt_coordinates(self) -> str:
Expand Down
119 changes: 119 additions & 0 deletions tests/test_geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,125 @@ class PointType(Point):
)


@pytest.mark.parametrize(
"coordinates,expected",
[
((0, 0), False),
((0, 0, 0), True),
],
)
def test_point_has_z(coordinates, expected):
assert Point(type="Point", coordinates=coordinates).has_z == expected


@pytest.mark.parametrize(
"coordinates,expected",
[
([(0, 0)], False),
([(0, 0), (1, 1)], False),
([(0, 0), (1, 1, 1)], True),
([(0, 0, 0)], True),
([(0, 0, 0), (1, 1)], True),
],
)
def test_multipoint_has_z(coordinates, expected):
assert MultiPoint(type="MultiPoint", coordinates=coordinates).has_z == expected


@pytest.mark.parametrize(
"coordinates,expected",
[
([(0, 0), (1, 1)], False),
([(0, 0), (1, 1, 1)], True),
([(0, 0, 0), (1, 1, 1)], True),
([(0, 0, 0), (1, 1)], True),
],
)
def test_linestring_has_z(coordinates, expected):
assert LineString(type="LineString", coordinates=coordinates).has_z == expected


@pytest.mark.parametrize(
"coordinates,expected",
[
([[(0, 0), (1, 1)]], False),
([[(0, 0), (1, 1)], [(0, 0), (1, 1)]], False),
([[(0, 0), (1, 1)], [(0, 0, 0), (1, 1)]], True),
([[(0, 0), (1, 1)], [(0, 0), (1, 1, 1)]], True),
([[(0, 0), (1, 1, 1)]], True),
([[(0, 0, 0), (1, 1, 1)]], True),
([[(0, 0, 0), (1, 1)]], True),
([[(0, 0, 0), (1, 1, 1)], [(0, 0, 0), (1, 1, 1)]], True),
],
)
def test_multilinestring_has_z(coordinates, expected):
assert (
MultiLineString(type="MultiLineString", coordinates=coordinates).has_z
== expected
)


@pytest.mark.parametrize(
"coordinates,expected",
[
([[(0, 0), (1, 1), (2, 2), (0, 0)]], False),
([[(0, 0), (1, 1), (2, 2, 2), (0, 0)]], True),
([[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2), (0, 0)]], False),
(
[[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2, 2), (0, 0)]],
True,
),
([[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]], True),
(
[
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
[(0, 0), (1, 1), (2, 2), (0, 0)],
],
True,
),
],
)
def test_polygon_has_z(coordinates, expected):
assert Polygon(type="Polygon", coordinates=coordinates).has_z == expected


@pytest.mark.parametrize(
"coordinates,expected",
[
([[[(0, 0), (1, 1), (2, 2), (0, 0)]]], False),
([[[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]], True),
(
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2), (0, 0)]]],
False,
),
(
[
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
[
[(0, 0), (1, 1), (2, 2), (0, 0)],
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
],
],
True,
),
(
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]],
True,
),
([[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]]], True),
(
[
[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]],
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
],
True,
),
],
)
def test_multipolygon_has_z(coordinates, expected):
assert MultiPolygon(type="MultiPolygon", coordinates=coordinates).has_z == expected


@pytest.mark.parametrize(
"shape",
[
Expand Down