Skip to content

Commit ffe27f5

Browse files
committed
Add function to Geometries.
1 parent 49af995 commit ffe27f5

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
@@ -22,16 +22,32 @@ def _position_wkt_coordinates(position: Position) -> str:
2222
return " ".join(str(number) for number in position)
2323

2424

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

2933

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

3443

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

@@ -48,13 +64,13 @@ def __geo_interface__(self) -> Dict[str, Any]:
4864

4965
@property
5066
@abc.abstractmethod
51-
def _wkt_coordinates(self) -> str:
67+
def has_z(self) -> bool:
68+
"""Checks if any coordinate has a Z value."""
5269
...
5370

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

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

7497

7598
class Point(_GeometryBase):
@@ -79,12 +102,13 @@ class Point(_GeometryBase):
79102
coordinates: Position
80103

81104
@property
82-
def _wkt_coordinates(self) -> str:
83-
return _position_wkt_coordinates(self.coordinates)
105+
def has_z(self) -> bool:
106+
"""Checks if any coordinate has a Z value."""
107+
return _position_has_z(self.coordinates)
84108

85109
@property
86-
def _wkt_inset(self) -> str:
87-
return " Z " if len(self.coordinates) == 3 else " "
110+
def _wkt_coordinates(self) -> str:
111+
return _position_wkt_coordinates(self.coordinates)
88112

89113

90114
class MultiPoint(_GeometryBase):
@@ -94,8 +118,9 @@ class MultiPoint(_GeometryBase):
94118
coordinates: MultiPointCoords
95119

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

100125
@property
101126
def _wkt_coordinates(self) -> str:
@@ -109,8 +134,9 @@ class LineString(_GeometryBase):
109134
coordinates: LineStringCoords
110135

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

115141
@property
116142
def _wkt_coordinates(self) -> str:
@@ -124,8 +150,9 @@ class MultiLineString(_GeometryBase):
124150
coordinates: MultiLineStringCoords
125151

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

130157
@property
131158
def _wkt_coordinates(self) -> str:
@@ -171,8 +198,9 @@ def interiors(self) -> Iterator[LinearRing]:
171198
)
172199

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

177205
@property
178206
def _wkt_coordinates(self) -> str:
@@ -198,8 +226,9 @@ class MultiPolygon(_GeometryBase):
198226
coordinates: MultiPolygonCoords
199227

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

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

tests/test_geometries.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,122 @@ class PointType(Point):
438438
PointType(type="Point", coordinates=(1.01, 2.01)).wkt
439439
== Point(type="Point", coordinates=(1.01, 2.01)).wkt
440440
)
441+
442+
443+
@pytest.mark.parametrize(
444+
"coordinates,expected",
445+
[
446+
((0, 0), False),
447+
((0, 0, 0), True),
448+
],
449+
)
450+
def test_point_has_z(coordinates, expected):
451+
assert Point(type="Point", coordinates=coordinates).has_z == expected
452+
453+
454+
@pytest.mark.parametrize(
455+
"coordinates,expected",
456+
[
457+
([(0, 0)], False),
458+
([(0, 0), (1, 1)], False),
459+
([(0, 0), (1, 1, 1)], True),
460+
([(0, 0, 0)], True),
461+
([(0, 0, 0), (1, 1)], True),
462+
],
463+
)
464+
def test_multipoint_has_z(coordinates, expected):
465+
assert MultiPoint(type="MultiPoint", coordinates=coordinates).has_z == expected
466+
467+
468+
@pytest.mark.parametrize(
469+
"coordinates,expected",
470+
[
471+
([(0, 0), (1, 1)], False),
472+
([(0, 0), (1, 1, 1)], True),
473+
([(0, 0, 0), (1, 1, 1)], True),
474+
([(0, 0, 0), (1, 1)], True),
475+
],
476+
)
477+
def test_linestring_has_z(coordinates, expected):
478+
assert LineString(type="LineString", coordinates=coordinates).has_z == expected
479+
480+
481+
@pytest.mark.parametrize(
482+
"coordinates,expected",
483+
[
484+
([[(0, 0), (1, 1)]], False),
485+
([[(0, 0), (1, 1)], [(0, 0), (1, 1)]], False),
486+
([[(0, 0), (1, 1)], [(0, 0, 0), (1, 1)]], True),
487+
([[(0, 0), (1, 1)], [(0, 0), (1, 1, 1)]], True),
488+
([[(0, 0), (1, 1, 1)]], True),
489+
([[(0, 0, 0), (1, 1, 1)]], True),
490+
([[(0, 0, 0), (1, 1)]], True),
491+
([[(0, 0, 0), (1, 1, 1)], [(0, 0, 0), (1, 1, 1)]], True),
492+
],
493+
)
494+
def test_multilinestring_has_z(coordinates, expected):
495+
assert (
496+
MultiLineString(type="MultiLineString", coordinates=coordinates).has_z
497+
== expected
498+
)
499+
500+
501+
@pytest.mark.parametrize(
502+
"coordinates,expected",
503+
[
504+
([[(0, 0), (1, 1), (2, 2), (0, 0)]], False),
505+
([[(0, 0), (1, 1), (2, 2, 2), (0, 0)]], True),
506+
([[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2), (0, 0)]], False),
507+
(
508+
[[(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2, 2), (0, 0)]],
509+
True,
510+
),
511+
([[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]], True),
512+
(
513+
[
514+
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
515+
[(0, 0), (1, 1), (2, 2), (0, 0)],
516+
],
517+
True,
518+
),
519+
],
520+
)
521+
def test_polygon_has_z(coordinates, expected):
522+
assert Polygon(type="Polygon", coordinates=coordinates).has_z == expected
523+
524+
525+
@pytest.mark.parametrize(
526+
"coordinates,expected",
527+
[
528+
([[[(0, 0), (1, 1), (2, 2), (0, 0)]]], False),
529+
([[[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]], True),
530+
(
531+
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2), (0, 0)]]],
532+
False,
533+
),
534+
(
535+
[
536+
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
537+
[
538+
[(0, 0), (1, 1), (2, 2), (0, 0)],
539+
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)],
540+
],
541+
],
542+
True,
543+
),
544+
(
545+
[[[(0, 0), (1, 1), (2, 2), (0, 0)]], [[(0, 0), (1, 1), (2, 2, 2), (0, 0)]]],
546+
True,
547+
),
548+
([[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]]], True),
549+
(
550+
[
551+
[[(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 0)]],
552+
[[(0, 0), (1, 1), (2, 2), (0, 0)]],
553+
],
554+
True,
555+
),
556+
],
557+
)
558+
def test_multipolygon_has_z(coordinates, expected):
559+
assert MultiPolygon(type="MultiPolygon", coordinates=coordinates).has_z == expected

0 commit comments

Comments
 (0)