Skip to content

Commit 22c3b2b

Browse files
committed
Address mixed dimensionality in wkt.
1 parent d08b037 commit 22c3b2b

File tree

1 file changed

+54
-34
lines changed

1 file changed

+54
-34
lines changed

geojson_pydantic/geometries.py

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""pydantic models for GeoJSON Geometry objects."""
2+
from __future__ import annotations
23

34
import abc
4-
from typing import Any, Dict, Iterator, List, Literal, Union
5+
from typing import Any, Dict, Iterator, List, Literal, Protocol, Union
56

67
from pydantic import BaseModel, Field, ValidationError, validator
78
from pydantic.error_wrappers import ErrorWrapper
@@ -18,37 +19,61 @@
1819
)
1920

2021

21-
def _position_wkt_coordinates(position: Position) -> str:
22+
def _position_wkt_coordinates(coordinates: Position, force_z: bool = False) -> str:
2223
"""Converts a Position to WKT Coordinates."""
23-
return " ".join(str(number) for number in position)
24+
wkt_coordinates = " ".join(str(number) for number in coordinates)
25+
if force_z and len(coordinates) < 3:
26+
wkt_coordinates += " 0.0"
27+
return wkt_coordinates
2428

2529

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

2933

30-
def _position_list_wkt_coordinates(positions: List[Position]) -> str:
34+
def _position_list_wkt_coordinates(
35+
coordinates: List[Position], force_z: bool = False
36+
) -> str:
3137
"""Converts a list of Positions to WKT Coordinates."""
32-
return ", ".join(_position_wkt_coordinates(position) for position in positions)
38+
return ", ".join(
39+
_position_wkt_coordinates(position, force_z) for position in coordinates
40+
)
3341

3442

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

3947

40-
def _lines_wtk_coordinates(lines: List[List[Position]]) -> str:
48+
def _lines_wtk_coordinates(
49+
coordinates: List[LineStringCoords], force_z: bool = False
50+
) -> str:
4151
"""Converts lines to WKT Coordinates."""
42-
return ", ".join(f"({_position_list_wkt_coordinates(line)})" for line in lines)
52+
return ", ".join(
53+
f"({_position_list_wkt_coordinates(line, force_z)})" for line in coordinates
54+
)
4355

4456

45-
def _lines_has_z(lines: List[List[Position]]) -> bool:
57+
def _lines_has_z(lines: List[LineStringCoords]) -> bool:
4658
"""Checks if any position in a list has a Z."""
4759
return any(
4860
_position_has_z(position) for positions in lines for position in positions
4961
)
5062

5163

64+
def _polygons_wkt_coordinates(
65+
coordinates: List[PolygonCoords], force_z: bool = False
66+
) -> str:
67+
return ",".join(
68+
f"({_lines_wtk_coordinates(polygon, force_z)})" for polygon in coordinates
69+
)
70+
71+
72+
class _WktCallable(Protocol):
73+
def __call__(self, coordinates: Any, force_z: bool) -> str:
74+
...
75+
76+
5277
class _GeometryBase(BaseModel, abc.ABC):
5378
"""Base class for geometry models"""
5479

@@ -71,7 +96,7 @@ def has_z(self) -> bool:
7196

7297
@property
7398
@abc.abstractmethod
74-
def _wkt_coordinates(self) -> str:
99+
def _wkt_coordinates(self) -> _WktCallable:
75100
...
76101

77102
@property
@@ -84,11 +109,12 @@ def wkt(self) -> str:
84109
"""Return the Well Known Text representation."""
85110
# Start with the WKT Type
86111
wkt = self._wkt_type
112+
has_z = self.has_z
87113
if self.coordinates:
88114
# If any of the coordinates have a Z add a "Z" to the WKT
89-
wkt += " Z " if self.has_z else " "
115+
wkt += " Z " if has_z else " "
90116
# Add the rest of the WKT inside parentheses
91-
wkt += f"({self._wkt_coordinates})"
117+
wkt += f"({self._wkt_coordinates(self.coordinates, force_z=has_z)})"
92118
else:
93119
# Otherwise it will be "EMPTY"
94120
wkt += " EMPTY"
@@ -108,8 +134,8 @@ def has_z(self) -> bool:
108134
return _position_has_z(self.coordinates)
109135

110136
@property
111-
def _wkt_coordinates(self) -> str:
112-
return _position_wkt_coordinates(self.coordinates)
137+
def _wkt_coordinates(self) -> _WktCallable:
138+
return _position_wkt_coordinates
113139

114140

115141
class MultiPoint(_GeometryBase):
@@ -124,8 +150,8 @@ def has_z(self) -> bool:
124150
return _position_list_has_z(self.coordinates)
125151

126152
@property
127-
def _wkt_coordinates(self) -> str:
128-
return _position_list_wkt_coordinates(self.coordinates)
153+
def _wkt_coordinates(self) -> _WktCallable:
154+
return _position_list_wkt_coordinates
129155

130156

131157
class LineString(_GeometryBase):
@@ -140,8 +166,8 @@ def has_z(self) -> bool:
140166
return _position_list_has_z(self.coordinates)
141167

142168
@property
143-
def _wkt_coordinates(self) -> str:
144-
return _position_list_wkt_coordinates(self.coordinates)
169+
def _wkt_coordinates(self) -> _WktCallable:
170+
return _position_list_wkt_coordinates
145171

146172

147173
class MultiLineString(_GeometryBase):
@@ -156,8 +182,8 @@ def has_z(self) -> bool:
156182
return _lines_has_z(self.coordinates)
157183

158184
@property
159-
def _wkt_coordinates(self) -> str:
160-
return _lines_wtk_coordinates(self.coordinates)
185+
def _wkt_coordinates(self) -> _WktCallable:
186+
return _lines_wtk_coordinates
161187

162188

163189
class LinearRingGeom(LineString):
@@ -204,8 +230,8 @@ def has_z(self) -> bool:
204230
return _lines_has_z(self.coordinates)
205231

206232
@property
207-
def _wkt_coordinates(self) -> str:
208-
return _lines_wtk_coordinates(self.coordinates)
233+
def _wkt_coordinates(self) -> _WktCallable:
234+
return _lines_wtk_coordinates
209235

210236
@classmethod
211237
def from_bounds(
@@ -232,10 +258,8 @@ def has_z(self) -> bool:
232258
return any(_lines_has_z(polygon) for polygon in self.coordinates)
233259

234260
@property
235-
def _wkt_coordinates(self) -> str:
236-
return ",".join(
237-
f"({_lines_wtk_coordinates(polygon)})" for polygon in self.coordinates
238-
)
261+
def _wkt_coordinates(self) -> _WktCallable:
262+
return _polygons_wkt_coordinates
239263

240264
@validator("coordinates")
241265
def check_closure(cls, coordinates: List) -> List:
@@ -275,19 +299,15 @@ def _wkt_type(self) -> str:
275299
"""Return the WKT name of the geometry."""
276300
return self.type.upper()
277301

278-
@property
279-
def _wkt_coordinates(self) -> str:
280-
"""Encode coordinates as WKT."""
281-
return ", ".join(geom.wkt for geom in self.geometries)
282-
283302
@property
284303
def wkt(self) -> str:
285304
"""Return the Well Known Text representation."""
286-
return (
287-
self._wkt_type
288-
+ " "
289-
+ (f"({self._wkt_coordinates})" if self._wkt_coordinates else "EMPTY")
305+
coordinates = (
306+
f'({", ".join(geom.wkt for geom in self.geometries)})'
307+
if self.geometries
308+
else "EMPTY"
290309
)
310+
return f"{self._wkt_type} {coordinates}"
291311

292312
@property
293313
def __geo_interface__(self) -> Dict[str, Any]:

0 commit comments

Comments
 (0)