Skip to content

Commit d6c291e

Browse files
Merge pull request #103 from eseglem/feature/add-has-z-function
Add has_z function to Geometries.
2 parents b93b2b6 + 6c40701 commit d6c291e

File tree

2 files changed

+170
-22
lines changed

2 files changed

+170
-22
lines changed

geojson_pydantic/geometries.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,32 @@ def _position_wkt_coordinates(position: Position) -> str:
2323
return " ".join(str(number) for number in position)
2424

2525

26+
def _position_has_z(position: Position) -> bool:
27+
return len(position) == 3
28+
29+
2630
def _position_list_wkt_coordinates(positions: List[Position]) -> str:
2731
"""Converts a list of Positions to WKT Coordinates."""
2832
return ", ".join(_position_wkt_coordinates(position) for position in positions)
2933

3034

35+
def _position_list_has_z(positions: List[Position]) -> bool:
36+
"""Checks if any position in a list has a Z."""
37+
return any(_position_has_z(position) for position in positions)
38+
39+
3140
def _lines_wtk_coordinates(lines: List[List[Position]]) -> str:
3241
"""Converts lines to WKT Coordinates."""
3342
return ", ".join(f"({_position_list_wkt_coordinates(line)})" for line in lines)
3443

3544

45+
def _lines_has_z(lines: List[List[Position]]) -> bool:
46+
"""Checks if any position in a list has a Z."""
47+
return any(
48+
_position_has_z(position) for positions in lines for position in positions
49+
)
50+
51+
3652
class _GeometryBase(BaseModel, abc.ABC):
3753
"""Base class for geometry models"""
3854

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

5066
@property
5167
@abc.abstractmethod
52-
def _wkt_coordinates(self) -> str:
68+
def has_z(self) -> bool:
69+
"""Checks if any coordinate has a Z value."""
5370
...
5471

5572
@property
5673
@abc.abstractmethod
57-
def _wkt_inset(self) -> str:
58-
"""Return Z for 3 dimensional geometry or an empty string for 2 dimensions."""
74+
def _wkt_coordinates(self) -> str:
5975
...
6076

6177
@property
@@ -66,11 +82,18 @@ def _wkt_type(self) -> str:
6682
@property
6783
def wkt(self) -> str:
6884
"""Return the Well Known Text representation."""
69-
return self._wkt_type + (
70-
f"{self._wkt_inset}({self._wkt_coordinates})"
71-
if self.coordinates
72-
else " EMPTY"
73-
)
85+
# Start with the WKT Type
86+
wkt = self._wkt_type
87+
if self.coordinates:
88+
# If any of the coordinates have a Z add a "Z" to the WKT
89+
wkt += " Z " if self.has_z else " "
90+
# Add the rest of the WKT inside parentheses
91+
wkt += f"({self._wkt_coordinates})"
92+
else:
93+
# Otherwise it will be "EMPTY"
94+
wkt += " EMPTY"
95+
96+
return wkt
7497

7598

7699
class Point(_GeometryBase):
@@ -80,12 +103,13 @@ class Point(_GeometryBase):
80103
coordinates: Position
81104

82105
@property
83-
def _wkt_coordinates(self) -> str:
84-
return _position_wkt_coordinates(self.coordinates)
106+
def has_z(self) -> bool:
107+
"""Checks if any coordinate has a Z value."""
108+
return _position_has_z(self.coordinates)
85109

86110
@property
87-
def _wkt_inset(self) -> str:
88-
return " Z " if len(self.coordinates) == 3 else " "
111+
def _wkt_coordinates(self) -> str:
112+
return _position_wkt_coordinates(self.coordinates)
89113

90114

91115
class MultiPoint(_GeometryBase):
@@ -95,8 +119,9 @@ class MultiPoint(_GeometryBase):
95119
coordinates: MultiPointCoords
96120

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

101126
@property
102127
def _wkt_coordinates(self) -> str:
@@ -110,8 +135,9 @@ class LineString(_GeometryBase):
110135
coordinates: LineStringCoords
111136

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

116142
@property
117143
def _wkt_coordinates(self) -> str:
@@ -125,8 +151,9 @@ class MultiLineString(_GeometryBase):
125151
coordinates: MultiLineStringCoords
126152

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

131158
@property
132159
def _wkt_coordinates(self) -> str:
@@ -172,8 +199,9 @@ def interiors(self) -> Iterator[LinearRing]:
172199
)
173200

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

178206
@property
179207
def _wkt_coordinates(self) -> str:
@@ -199,8 +227,9 @@ class MultiPolygon(_GeometryBase):
199227
coordinates: MultiPolygonCoords
200228

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

205234
@property
206235
def _wkt_coordinates(self) -> str:

tests/test_geometries.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,125 @@ class PointType(Point):
472472
)
473473

474474

475+
@pytest.mark.parametrize(
476+
"coordinates,expected",
477+
[
478+
((0, 0), False),
479+
((0, 0, 0), True),
480+
],
481+
)
482+
def test_point_has_z(coordinates, expected):
483+
assert Point(type="Point", coordinates=coordinates).has_z == expected
484+
485+
486+
@pytest.mark.parametrize(
487+
"coordinates,expected",
488+
[
489+
([(0, 0)], False),
490+
([(0, 0), (1, 1)], False),
491+
([(0, 0), (1, 1, 1)], True),
492+
([(0, 0, 0)], True),
493+
([(0, 0, 0), (1, 1)], True),
494+
],
495+
)
496+
def test_multipoint_has_z(coordinates, expected):
497+
assert MultiPoint(type="MultiPoint", coordinates=coordinates).has_z == expected
498+
499+
500+
@pytest.mark.parametrize(
501+
"coordinates,expected",
502+
[
503+
([(0, 0), (1, 1)], False),
504+
([(0, 0), (1, 1, 1)], True),
505+
([(0, 0, 0), (1, 1, 1)], True),
506+
([(0, 0, 0), (1, 1)], True),
507+
],
508+
)
509+
def test_linestring_has_z(coordinates, expected):
510+
assert LineString(type="LineString", coordinates=coordinates).has_z == expected
511+
512+
513+
@pytest.mark.parametrize(
514+
"coordinates,expected",
515+
[
516+
([[(0, 0), (1, 1)]], False),
517+
([[(0, 0), (1, 1)], [(0, 0), (1, 1)]], False),
518+
([[(0, 0), (1, 1)], [(0, 0, 0), (1, 1)]], True),
519+
([[(0, 0), (1, 1)], [(0, 0), (1, 1, 1)]], True),
520+
([[(0, 0), (1, 1, 1)]], True),
521+
([[(0, 0, 0), (1, 1, 1)]], True),
522+
([[(0, 0, 0), (1, 1)]], True),
523+
([[(0, 0, 0), (1, 1, 1)], [(0, 0, 0), (1, 1, 1)]], True),
524+
],
525+
)
526+
def test_multilinestring_has_z(coordinates, expected):
527+
assert (
528+
MultiLineString(type="MultiLineString", coordinates=coordinates).has_z
529+
== expected
530+
)
531+
532+
533+
@pytest.mark.parametrize(
534+
"coordinates,expected",
535+
[
536+
([[(0, 0), (1, 1), (2, 2), (0, 0)]], False),
537+
([[(0, 0), (1, 1), (2, 2, 2), (0, 0)]], True),
538+
([[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2), (0, 0)]], False),
539+
(
540+
[[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2, 2), (0, 0)]],
541+
True,
542+
),
543+
([[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]], True),
544+
(
545+
[
546+
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
547+
[(0, 0), (1, 1), (2, 2), (0, 0)],
548+
],
549+
True,
550+
),
551+
],
552+
)
553+
def test_polygon_has_z(coordinates, expected):
554+
assert Polygon(type="Polygon", coordinates=coordinates).has_z == expected
555+
556+
557+
@pytest.mark.parametrize(
558+
"coordinates,expected",
559+
[
560+
([[[(0, 0), (1, 1), (2, 2), (0, 0)]]], False),
561+
([[[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]], True),
562+
(
563+
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2), (0, 0)]]],
564+
False,
565+
),
566+
(
567+
[
568+
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
569+
[
570+
[(0, 0), (1, 1), (2, 2), (0, 0)],
571+
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
572+
],
573+
],
574+
True,
575+
),
576+
(
577+
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]],
578+
True,
579+
),
580+
([[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]]], True),
581+
(
582+
[
583+
[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]],
584+
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
585+
],
586+
True,
587+
),
588+
],
589+
)
590+
def test_multipolygon_has_z(coordinates, expected):
591+
assert MultiPolygon(type="MultiPolygon", coordinates=coordinates).has_z == expected
592+
593+
475594
@pytest.mark.parametrize(
476595
"shape",
477596
[

0 commit comments

Comments
 (0)