Skip to content

Commit b45b771

Browse files
Merge pull request #97 from eseglem/feature/wkt-refactor
Refactor WKT for reuse and speed.
2 parents d65cdc0 + cecd827 commit b45b771

File tree

3 files changed

+92
-39
lines changed

3 files changed

+92
-39
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 & 19 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(type="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(type="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,10 +129,7 @@ def _wkt_inset(self) -> str:
112129

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

120134

121135
class LinearRingGeom(LineString):
@@ -162,11 +176,7 @@ def _wkt_inset(self) -> str:
162176

163177
@property
164178
def _wkt_coordinates(self) -> str:
165-
ic = "".join(
166-
f", ({LinearRingGeom(type='LineString', coordinates=interior)._wkt_coordinates})"
167-
for interior in self.interiors
168-
)
169-
return f"({LinearRingGeom(type='LineString', coordinates=self.exterior)._wkt_coordinates}){ic}"
179+
return _lines_wtk_coordinates(self.coordinates)
170180

171181
@classmethod
172182
def from_bounds(
@@ -193,10 +203,9 @@ def _wkt_inset(self) -> str:
193203

194204
@property
195205
def _wkt_coordinates(self) -> str:
196-
polygons = [
197-
Polygon(type="Polygon", coordinates=poly) for poly in self.coordinates
198-
]
199-
return ",".join(f"({poly._wkt_coordinates})" for poly in polygons)
206+
return ",".join(
207+
f"({_lines_wtk_coordinates(polygon)})" for polygon in self.coordinates
208+
)
200209

201210

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

tests/test_geometries.py

Lines changed: 60 additions & 16 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,16 +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-
type="Polygon",
165-
coordinates=[
174+
@pytest.mark.parametrize(
175+
"coordinates",
176+
[
177+
# Polygon with holes, no Z
178+
[
166179
[(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)],
167180
[(2.0, 2.0), (2.0, 4.0), (4.0, 4.0), (4.0, 2.0), (2.0, 2.0)],
168181
],
169-
)
170-
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(type="Polygon", coordinates=coordinates)
171204
assert polygon.type == "Polygon"
172205
assert hasattr(polygon, "__geo_interface__")
173206
assert polygon.exterior == polygon.coordinates[0]
@@ -197,11 +230,18 @@ def test_polygon_invalid_coordinates(coordinates):
197230
Polygon(type="Polygon", coordinates=coordinates)
198231

199232

200-
def test_multi_polygon():
201-
"""Should accept sequence of polygons."""
202-
multi_polygon = MultiPolygon(
203-
type="MultiPolygon",
204-
coordinates=[
233+
@pytest.mark.parametrize(
234+
"coordinates",
235+
[
236+
# Multipolygon, no Z
237+
[
238+
[
239+
[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)],
240+
[(2.1, 2.1), (2.2, 2.1), (2.2, 2.2), (2.1, 2.2), (2.1, 2.1)],
241+
]
242+
],
243+
# Multipolygon, has Z
244+
[
205245
[
206246
[
207247
(0.0, 0.0, 4.0),
@@ -219,7 +259,11 @@ def test_multi_polygon():
219259
],
220260
]
221261
],
222-
)
262+
],
263+
)
264+
def test_multi_polygon(coordinates):
265+
"""Should accept sequence of polygons."""
266+
multi_polygon = MultiPolygon(type="MultiPolygon", coordinates=coordinates)
223267

224268
assert multi_polygon.type == "MultiPolygon"
225269
assert hasattr(multi_polygon, "__geo_interface__")

0 commit comments

Comments
 (0)