Skip to content

Commit 92d2b5d

Browse files
committed
Refactor WKT for reuse and speed.
1 parent 1ff7b66 commit 92d2b5d

File tree

3 files changed

+94
-34
lines changed

3 files changed

+94
-34
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
repos:
22
- repo: https://github.com/psf/black
3-
rev: 22.6.0
3+
rev: 22.12.0
44
hooks:
55
- id: black
66
language_version: python
77

88
- repo: https://github.com/PyCQA/isort
9-
rev: 5.10.1
9+
rev: 5.12.0
1010
hooks:
1111
- id: isort
1212
language_version: python
1313

1414
- repo: https://github.com/PyCQA/flake8
15-
rev: 5.0.4
15+
rev: 6.0.0
1616
hooks:
1717
- id: flake8
1818
language_version: python
@@ -26,7 +26,7 @@ repos:
2626
- toml
2727

2828
- repo: https://github.com/pre-commit/mirrors-mypy
29-
rev: v0.971
29+
rev: v0.991
3030
hooks:
3131
- id: mypy
3232
language_version: python

geojson_pydantic/geometries.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@
1717
)
1818

1919

20+
def _position_wkt_coordinates(position: Position) -> str:
21+
"""Converts a Position to WKT Coordinates."""
22+
return " ".join(str(number) for number in position)
23+
24+
25+
def _position_list_wkt_coordinates(positions: List[Position]) -> str:
26+
"""Converts a list of Positions to WKT Coordinates."""
27+
return ", ".join(_position_wkt_coordinates(position) for position in positions)
28+
29+
30+
def _lines_wtk_coordinates(lines: List[List[Position]]) -> str:
31+
"""Converts lines to WKT Coordinates."""
32+
return ", ".join(f"({_position_list_wkt_coordinates(line)})" for line in lines)
33+
34+
2035
class _GeometryBase(BaseModel, abc.ABC):
2136
"""Base class for geometry models"""
2237

@@ -50,7 +65,11 @@ def _wkt_type(self) -> str:
5065
@property
5166
def wkt(self) -> str:
5267
"""Return the Well Known Text representation."""
53-
return f"{self._wkt_type}{self._wkt_inset}({self._wkt_coordinates})"
68+
return self._wkt_type + (
69+
f"{self._wkt_inset}({self._wkt_coordinates})"
70+
if self.coordinates
71+
else " EMPTY"
72+
)
5473

5574

5675
class Point(_GeometryBase):
@@ -61,7 +80,7 @@ class Point(_GeometryBase):
6180

6281
@property
6382
def _wkt_coordinates(self) -> str:
64-
return " ".join(str(coordinate) for coordinate in self.coordinates)
83+
return _position_wkt_coordinates(self.coordinates)
6584

6685
@property
6786
def _wkt_inset(self) -> str:
@@ -80,8 +99,7 @@ def _wkt_inset(self) -> str:
8099

81100
@property
82101
def _wkt_coordinates(self) -> str:
83-
points = [Point(coordinates=p) for p in self.coordinates]
84-
return ", ".join(point._wkt_coordinates for point in points)
102+
return _position_list_wkt_coordinates(self.coordinates)
85103

86104

87105
class LineString(_GeometryBase):
@@ -96,8 +114,7 @@ def _wkt_inset(self) -> str:
96114

97115
@property
98116
def _wkt_coordinates(self) -> str:
99-
points = [Point(coordinates=p) for p in self.coordinates]
100-
return ", ".join(point._wkt_coordinates for point in points)
117+
return _position_list_wkt_coordinates(self.coordinates)
101118

102119

103120
class MultiLineString(_GeometryBase):
@@ -112,8 +129,7 @@ def _wkt_inset(self) -> str:
112129

113130
@property
114131
def _wkt_coordinates(self) -> str:
115-
lines = [LineString(coordinates=line) for line in self.coordinates]
116-
return ",".join(f"({line._wkt_coordinates})" for line in lines)
132+
return _lines_wtk_coordinates(self.coordinates)
117133

118134

119135
class LinearRingGeom(LineString):
@@ -160,11 +176,7 @@ def _wkt_inset(self) -> str:
160176

161177
@property
162178
def _wkt_coordinates(self) -> str:
163-
ic = "".join(
164-
f", ({LinearRingGeom(coordinates=interior)._wkt_coordinates})"
165-
for interior in self.interiors
166-
)
167-
return f"({LinearRingGeom(coordinates=self.exterior)._wkt_coordinates}){ic}"
179+
return _lines_wtk_coordinates(self.coordinates)
168180

169181
@classmethod
170182
def from_bounds(
@@ -190,8 +202,9 @@ def _wkt_inset(self) -> str:
190202

191203
@property
192204
def _wkt_coordinates(self) -> str:
193-
polygons = [Polygon(coordinates=poly) for poly in self.coordinates]
194-
return ",".join(f"({poly._wkt_coordinates})" for poly in polygons)
205+
return ",".join(
206+
f"({_lines_wtk_coordinates(polygon)})" for polygon in self.coordinates
207+
)
195208

196209

197210
Geometry = Union[Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon]

tests/test_geometries.py

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def assert_wkt_equivalence(geom: Union[Geometry, GeometryCollection]):
2222
"""Assert WKT equivalence with Shapely."""
2323
# Remove any trailing `.0` to match Shapely format
2424
clean_wkt = re.sub(r"\.0(\D)", r"\1", geom.wkt)
25-
assert shape(geom.dict()).wkt == clean_wkt
25+
assert shape(geom).wkt == clean_wkt
2626

2727

2828
@pytest.mark.parametrize("coordinates", [(1.01, 2.01), (1.0, 2.0, 3.0), (1.0, 2.0)])
@@ -51,10 +51,11 @@ def test_point_invalid_coordinates(coordinates):
5151
@pytest.mark.parametrize(
5252
"coordinates",
5353
[
54+
# No Z
5455
[(1.0, 2.0)],
5556
[(1.0, 2.0), (1.0, 2.0)],
57+
# Has Z
5658
[(1.0, 2.0, 3.0), (1.0, 2.0, 3.0)],
57-
[(1.0, 2.0), (1.0, 2.0)],
5859
],
5960
)
6061
def test_multi_point_valid_coordinates(coordinates):
@@ -83,9 +84,12 @@ def test_multi_point_invalid_coordinates(coordinates):
8384
@pytest.mark.parametrize(
8485
"coordinates",
8586
[
87+
# Two Points, no Z
8688
[(1.0, 2.0), (3.0, 4.0)],
87-
[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
89+
# Three Points, no Z
8890
[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)],
91+
# Two Points, has Z
92+
[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
8993
],
9094
)
9195
def test_line_string_valid_coordinates(coordinates):
@@ -111,9 +115,16 @@ def test_line_string_invalid_coordinates(coordinates):
111115
@pytest.mark.parametrize(
112116
"coordinates",
113117
[
118+
# One line, two points, no Z
114119
[[(1.0, 2.0), (3.0, 4.0)]],
120+
# One line, two points, has Z
115121
[[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]],
122+
# One line, three points, no Z
116123
[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)]],
124+
# Two lines, two points each, no Z
125+
[[(1.0, 2.0), (3.0, 4.0)], [(0.0, 0.0), (1.0, 1.0)]],
126+
# Two lines, two points each, has Z
127+
[[(1.0, 2.0, 0.0), (3.0, 4.0, 1.0)], [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]],
117128
],
118129
)
119130
def test_multi_line_string_valid_coordinates(coordinates):
@@ -141,7 +152,9 @@ def test_multi_line_string_invalid_coordinates(coordinates):
141152
@pytest.mark.parametrize(
142153
"coordinates",
143154
[
155+
# Polygon, no Z
144156
[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (1.0, 2.0)]],
157+
# Polygon, has Z
145158
[[(0.0, 0.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0)]],
146159
],
147160
)
@@ -158,14 +171,36 @@ def test_polygon_valid_coordinates(coordinates):
158171
assert_wkt_equivalence(polygon)
159172

160173

161-
def test_polygon_with_holes():
162-
"""Check interior and exterior rings."""
163-
polygon = Polygon(
164-
coordinates=[
174+
@pytest.mark.parametrize(
175+
"coordinates",
176+
[
177+
# Polygon with holes, no Z
178+
[
165179
[(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)],
166180
[(2.0, 2.0), (2.0, 4.0), (4.0, 4.0), (4.0, 2.0), (2.0, 2.0)],
167-
]
168-
)
181+
],
182+
# Polygon with holes, has Z
183+
[
184+
[
185+
(0.0, 0.0, 0.0),
186+
(0.0, 10.0, 0.0),
187+
(10.0, 10.0, 0.0),
188+
(10.0, 0.0, 0.0),
189+
(0.0, 0.0, 0.0),
190+
],
191+
[
192+
(2.0, 2.0, 1.0),
193+
(2.0, 4.0, 1.0),
194+
(4.0, 4.0, 1.0),
195+
(4.0, 2.0, 1.0),
196+
(2.0, 2.0, 1.0),
197+
],
198+
],
199+
],
200+
)
201+
def test_polygon_with_holes(coordinates):
202+
"""Check interior and exterior rings."""
203+
polygon = Polygon(coordinates=coordinates)
169204

170205
assert polygon.type == "Polygon"
171206
assert hasattr(polygon, "__geo_interface__")
@@ -196,10 +231,18 @@ def test_polygon_invalid_coordinates(coordinates):
196231
Polygon(coordinates=coordinates)
197232

198233

199-
def test_multi_polygon():
200-
"""Should accept sequence of polygons."""
201-
multi_polygon = MultiPolygon(
202-
coordinates=[
234+
@pytest.mark.parametrize(
235+
"coordinates",
236+
[
237+
# Multipolygon, no Z
238+
[
239+
[
240+
[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)],
241+
[(2.1, 2.1), (2.2, 2.1), (2.2, 2.2), (2.1, 2.2), (2.1, 2.1)],
242+
]
243+
],
244+
# Multipolygon, has Z
245+
[
203246
[
204247
[
205248
(0.0, 0.0, 4.0),
@@ -216,8 +259,12 @@ def test_multi_polygon():
216259
(2.1, 2.1, 4.0),
217260
],
218261
]
219-
]
220-
)
262+
],
263+
],
264+
)
265+
def test_multi_polygon(coordinates):
266+
"""Should accept sequence of polygons."""
267+
multi_polygon = MultiPolygon(coordinates=coordinates)
221268

222269
assert multi_polygon.type == "MultiPolygon"
223270
assert hasattr(multi_polygon, "__geo_interface__")

0 commit comments

Comments
 (0)