1
1
"""pydantic models for GeoJSON Geometry objects."""
2
+ from __future__ import annotations
2
3
3
4
import abc
4
- from typing import Any , Dict , Iterator , List , Literal , Union
5
+ from typing import Any , Dict , Iterator , List , Literal , Protocol , Union
5
6
6
7
from pydantic import BaseModel , Field , ValidationError , validator
7
8
from pydantic .error_wrappers import ErrorWrapper
18
19
)
19
20
20
21
21
- def _position_wkt_coordinates (position : Position ) -> str :
22
+ def _position_wkt_coordinates (coordinates : Position , force_z : bool = False ) -> str :
22
23
"""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
24
28
25
29
26
30
def _position_has_z (position : Position ) -> bool :
27
31
return len (position ) == 3
28
32
29
33
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 :
31
37
"""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
+ )
33
41
34
42
35
43
def _position_list_has_z (positions : List [Position ]) -> bool :
36
44
"""Checks if any position in a list has a Z."""
37
45
return any (_position_has_z (position ) for position in positions )
38
46
39
47
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 :
41
51
"""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
+ )
43
55
44
56
45
- def _lines_has_z (lines : List [List [ Position ] ]) -> bool :
57
+ def _lines_has_z (lines : List [LineStringCoords ]) -> bool :
46
58
"""Checks if any position in a list has a Z."""
47
59
return any (
48
60
_position_has_z (position ) for positions in lines for position in positions
49
61
)
50
62
51
63
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
+
52
77
class _GeometryBase (BaseModel , abc .ABC ):
53
78
"""Base class for geometry models"""
54
79
@@ -71,7 +96,7 @@ def has_z(self) -> bool:
71
96
72
97
@property
73
98
@abc .abstractmethod
74
- def _wkt_coordinates (self ) -> str :
99
+ def _wkt_coordinates (self ) -> _WktCallable :
75
100
...
76
101
77
102
@property
@@ -84,11 +109,12 @@ def wkt(self) -> str:
84
109
"""Return the Well Known Text representation."""
85
110
# Start with the WKT Type
86
111
wkt = self ._wkt_type
112
+ has_z = self .has_z
87
113
if self .coordinates :
88
114
# 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 " "
90
116
# 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 ) } )"
92
118
else :
93
119
# Otherwise it will be "EMPTY"
94
120
wkt += " EMPTY"
@@ -108,8 +134,8 @@ def has_z(self) -> bool:
108
134
return _position_has_z (self .coordinates )
109
135
110
136
@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
113
139
114
140
115
141
class MultiPoint (_GeometryBase ):
@@ -124,8 +150,8 @@ def has_z(self) -> bool:
124
150
return _position_list_has_z (self .coordinates )
125
151
126
152
@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
129
155
130
156
131
157
class LineString (_GeometryBase ):
@@ -140,8 +166,8 @@ def has_z(self) -> bool:
140
166
return _position_list_has_z (self .coordinates )
141
167
142
168
@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
145
171
146
172
147
173
class MultiLineString (_GeometryBase ):
@@ -156,8 +182,8 @@ def has_z(self) -> bool:
156
182
return _lines_has_z (self .coordinates )
157
183
158
184
@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
161
187
162
188
163
189
class LinearRingGeom (LineString ):
@@ -204,8 +230,8 @@ def has_z(self) -> bool:
204
230
return _lines_has_z (self .coordinates )
205
231
206
232
@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
209
235
210
236
@classmethod
211
237
def from_bounds (
@@ -232,10 +258,8 @@ def has_z(self) -> bool:
232
258
return any (_lines_has_z (polygon ) for polygon in self .coordinates )
233
259
234
260
@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
239
263
240
264
@validator ("coordinates" )
241
265
def check_closure (cls , coordinates : List ) -> List :
@@ -275,19 +299,15 @@ def _wkt_type(self) -> str:
275
299
"""Return the WKT name of the geometry."""
276
300
return self .type .upper ()
277
301
278
- @property
279
- def _wkt_coordinates (self ) -> str :
280
- """Encode coordinates as WKT."""
281
- return ", " .join (geom .wkt for geom in self .geometries )
282
-
283
302
@property
284
303
def wkt (self ) -> str :
285
304
"""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"
290
309
)
310
+ return f"{ self ._wkt_type } { coordinates } "
291
311
292
312
@property
293
313
def __geo_interface__ (self ) -> Dict [str , Any ]:
0 commit comments