geo-adjacency 1.1.1__py3-none-any.whl → 1.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geo_adjacency/adjacency.py +144 -77
- geo_adjacency/utils.py +38 -14
- {geo_adjacency-1.1.1.dist-info → geo_adjacency-1.1.2.dist-info}/METADATA +1 -1
- geo_adjacency-1.1.2.dist-info/RECORD +8 -0
- geo_adjacency/file.log +0 -10
- geo_adjacency-1.1.1.dist-info/RECORD +0 -9
- {geo_adjacency-1.1.1.dist-info → geo_adjacency-1.1.2.dist-info}/LICENSE +0 -0
- {geo_adjacency-1.1.1.dist-info → geo_adjacency-1.1.2.dist-info}/WHEEL +0 -0
geo_adjacency/adjacency.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The `adjacency` module implements the AdjacencyEngine class,
|
|
3
|
-
which allows us to
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
which allows us to calculate adjacency relationships. Adjacency relationships are between a set of source geometries,
|
|
4
|
+
or between source geometries and a second set of target geometries. Obstacle geometries can be passed in to
|
|
5
|
+
stand between sources or sources and targets, but they are not included in the output.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
For example, if we wanted to know what trees in a forest are adjacent to the shore of a lake, we could
|
|
8
|
+
pass in a set of Point geometries to the trees, a Polygon to represent the lake, and a LineString to represent
|
|
9
|
+
a road passing between some of the trees and the shore.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
`AdjacencyEngine` utilizes a Voronoi diagram of all the vertices in all the geometries combined to determine
|
|
12
|
+
which geometries are adjacent to each other. See
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import math
|
|
16
16
|
from collections import defaultdict
|
|
17
|
-
from typing import List, Union, Dict, Tuple
|
|
17
|
+
from typing import List, Union, Dict, Tuple, Generator
|
|
18
|
+
|
|
18
19
|
try:
|
|
19
20
|
from typing_extensions import Self # Python < 3.11
|
|
20
21
|
except ImportError:
|
|
@@ -39,34 +40,47 @@ from geo_adjacency.utils import (
|
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
# ToDo: Support geometries with Z-coordinates
|
|
42
|
-
|
|
43
43
|
# Create a custom logger
|
|
44
|
-
|
|
44
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
45
45
|
|
|
46
46
|
# Create handlers
|
|
47
|
-
c_handler = logging.StreamHandler()
|
|
47
|
+
c_handler: logging.StreamHandler = logging.StreamHandler()
|
|
48
48
|
c_handler.setLevel(logging.WARNING)
|
|
49
49
|
|
|
50
50
|
# Create formatters and add it to handlers
|
|
51
|
-
c_format = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
|
|
52
|
-
f_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
51
|
+
c_format: logging.Formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
|
|
52
|
+
f_format: logging.Formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
53
53
|
c_handler.setFormatter(c_format)
|
|
54
54
|
|
|
55
55
|
# Add handlers to the logger
|
|
56
|
-
|
|
56
|
+
log.addHandler(c_handler)
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class _Feature:
|
|
60
|
+
"""
|
|
61
|
+
A _Feature is a wrapper around a Shapely geometry that allows us to easily determine if two
|
|
62
|
+
geometries are adjacent.
|
|
63
|
+
"""
|
|
60
64
|
__slots__ = ("_geometry", "_coords", "voronoi_points")
|
|
61
65
|
|
|
62
66
|
def __init__(self, geometry: BaseGeometry):
|
|
67
|
+
"""
|
|
68
|
+
Create a _Feature from a Shapely geometry.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
geometry (BaseGeometry): A valid Shapely Geometry, either a Point, LineString, Polygon, or
|
|
72
|
+
MultiPolygon.
|
|
73
|
+
"""
|
|
74
|
+
|
|
63
75
|
if not isinstance(geometry, (Point, Polygon, MultiPolygon, LineString)):
|
|
64
76
|
raise TypeError(
|
|
65
|
-
"Cannot create _Feature for geometry type '%s'." % type(
|
|
77
|
+
"Cannot create _Feature for geometry type '%s'." % type(geometry)
|
|
66
78
|
)
|
|
67
79
|
|
|
80
|
+
assert geometry.is_valid, "Could not process invalid geometry: %s" % geometry.wkt
|
|
81
|
+
|
|
68
82
|
self._geometry: BaseGeometry = geometry
|
|
69
|
-
self._coords = None
|
|
83
|
+
self._coords: Union[List[Tuple[float, float]], None] = None
|
|
70
84
|
self.voronoi_points: set = set()
|
|
71
85
|
|
|
72
86
|
def __str__(self):
|
|
@@ -75,32 +89,30 @@ class _Feature:
|
|
|
75
89
|
def __repr__(self):
|
|
76
90
|
return f"<_Feature: {str(self.geometry)}>"
|
|
77
91
|
|
|
78
|
-
def
|
|
79
|
-
if not isinstance(other, type(self)):
|
|
80
|
-
return False
|
|
81
|
-
return self.geometry.equals_exact(other.geometry, 1e-8)
|
|
82
|
-
|
|
83
|
-
def __ne__(self, other: Self):
|
|
84
|
-
return not self.__eq__(other)
|
|
85
|
-
|
|
86
|
-
def is_adjacent(
|
|
92
|
+
def _is_adjacent(
|
|
87
93
|
self, other: Self, min_overlapping_voronoi_vertices: int = 2
|
|
88
94
|
) -> bool:
|
|
89
95
|
"""
|
|
90
96
|
Determine if two features are adjacent based on how many Voronoi vertices they share. Note:
|
|
91
97
|
the Voronoi analysis must have been run, or this will always return False.
|
|
92
|
-
:
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
Args:
|
|
99
|
+
other (_Feature): Another _Feature to compare to.
|
|
100
|
+
min_overlapping_voronoi_vertices (int): The minimum number of Voronoi vertices that
|
|
101
|
+
must be shared to be considered adjacent.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
bool: True if the two features are adjacent.
|
|
105
|
+
|
|
95
106
|
"""
|
|
96
107
|
assert isinstance(other, type(self)), "Cannot compare '%s' with '%s'." % (
|
|
97
108
|
type(self),
|
|
98
109
|
type(other),
|
|
99
110
|
)
|
|
100
111
|
if len(self.voronoi_points) == 0 and len(other.voronoi_points) == 0:
|
|
101
|
-
|
|
112
|
+
log.warning(
|
|
102
113
|
"No Voronoi vertices found for either feature. Did you run the analysis yet?"
|
|
103
114
|
)
|
|
115
|
+
return False
|
|
104
116
|
return (
|
|
105
117
|
len(self.voronoi_points & other.voronoi_points)
|
|
106
118
|
>= min_overlapping_voronoi_vertices
|
|
@@ -110,7 +122,10 @@ class _Feature:
|
|
|
110
122
|
def geometry(self):
|
|
111
123
|
"""
|
|
112
124
|
Access the Shapely geometry of the feature.
|
|
113
|
-
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
BaseGeometry: The Shapely geometry of the feature.
|
|
128
|
+
|
|
114
129
|
"""
|
|
115
130
|
return self._geometry
|
|
116
131
|
|
|
@@ -124,7 +139,9 @@ class _Feature:
|
|
|
124
139
|
"""
|
|
125
140
|
Convenience property for accessing the coordinates of the geometry as a list of 2-tuples.
|
|
126
141
|
|
|
127
|
-
:
|
|
142
|
+
Returns:
|
|
143
|
+
List[Tuple[float, float]]: A list of coordinate tuples.
|
|
144
|
+
|
|
128
145
|
"""
|
|
129
146
|
|
|
130
147
|
if not self._coords:
|
|
@@ -140,6 +157,10 @@ class _Feature:
|
|
|
140
157
|
raise TypeError(f"Unknown geometry type '{type(self.geometry)}'")
|
|
141
158
|
return self._coords
|
|
142
159
|
|
|
160
|
+
@coords.setter
|
|
161
|
+
def coords(self, coords):
|
|
162
|
+
raise ImmutablePropertyError("Property coords is immutable.")
|
|
163
|
+
|
|
143
164
|
|
|
144
165
|
class AdjacencyEngine:
|
|
145
166
|
"""
|
|
@@ -149,9 +170,6 @@ class AdjacencyEngine:
|
|
|
149
170
|
First, the Voronoi diagram is generated for each geometry and obstacle. Then, we check which
|
|
150
171
|
voronoi shapes intersect one another. If they do, then the two underlying geometries are
|
|
151
172
|
adjacent.
|
|
152
|
-
|
|
153
|
-
:ivar all_features: List of all features in order of source, target, and obstacle.
|
|
154
|
-
:ivar all_coordinates: List of all coordinates in the same order as all_features.
|
|
155
173
|
"""
|
|
156
174
|
|
|
157
175
|
__slots__ = (
|
|
@@ -161,7 +179,7 @@ class AdjacencyEngine:
|
|
|
161
179
|
"_adjacency_dict",
|
|
162
180
|
"_feature_indices",
|
|
163
181
|
"_vor",
|
|
164
|
-
"
|
|
182
|
+
"_all_features",
|
|
165
183
|
"_all_coordinates",
|
|
166
184
|
)
|
|
167
185
|
|
|
@@ -174,24 +192,23 @@ class AdjacencyEngine:
|
|
|
174
192
|
max_segment_length: Union[float, None] = None,
|
|
175
193
|
):
|
|
176
194
|
"""
|
|
177
|
-
|
|
195
|
+
Note: only Multipolygons, Polygons, LineStrings and Points are supported. It is assumed all
|
|
178
196
|
features are in the same projection.
|
|
179
197
|
|
|
180
|
-
:
|
|
181
|
-
|
|
182
|
-
|
|
198
|
+
Args:
|
|
199
|
+
source_geoms (List[BaseGeometry]): List of Shapely geometries. We will which ones are adjacent to
|
|
200
|
+
which others, unless target_geoms is specified.
|
|
201
|
+
target_geoms (Union[List[BaseGeometry], None]), optional): list of Shapley geometries. if not None, We will
|
|
183
202
|
test if these features are adjacent to the source features.
|
|
184
|
-
|
|
203
|
+
obstacle_geoms (Union[List[BaseGeometry], None]), optional): List
|
|
185
204
|
of Shapely geometries. These features will not be tested for adjacency, but they can
|
|
186
205
|
prevent a source and target feature from being adjacent.
|
|
187
|
-
|
|
206
|
+
densify_features (bool, optional): If
|
|
188
207
|
True, we will add additional points to the features to improve accuracy of the voronoi
|
|
189
|
-
diagram.
|
|
190
|
-
:param max_segment_length: The maximum distance between vertices that we want.
|
|
191
|
-
In projection units. densify_features must be True, or an error will be thrown. If
|
|
192
|
-
densify_features is True and max_segment_length is false, then the max_segment_length
|
|
208
|
+
diagram. If densify_features is True and max_segment_length is false, then the max_segment_length
|
|
193
209
|
will be calculated based on the average segment length of all features, divided by 5.
|
|
194
|
-
|
|
210
|
+
max_segment_length (Union[float, None], optional): The maximum distance between vertices that we want.
|
|
211
|
+
In projection units. densify_features must be True, or an error will be thrown.
|
|
195
212
|
"""
|
|
196
213
|
|
|
197
214
|
if max_segment_length and not densify_features:
|
|
@@ -212,21 +229,21 @@ class AdjacencyEngine:
|
|
|
212
229
|
if obstacle_geoms
|
|
213
230
|
else tuple()
|
|
214
231
|
)
|
|
215
|
-
self._adjacency_dict = None
|
|
216
|
-
self._feature_indices = None
|
|
232
|
+
self._adjacency_dict: Union[Dict[int, List[int]], None] = None
|
|
233
|
+
self._feature_indices: Union[Dict[int, int], None] = None
|
|
217
234
|
self._vor = None
|
|
218
235
|
self._all_coordinates = None
|
|
219
236
|
|
|
220
237
|
"""All source, target, and obstacle features in a single list. The order of this list must
|
|
221
238
|
not be changed."""
|
|
222
|
-
self.
|
|
239
|
+
self._all_features: Tuple[_Feature, ...] = tuple(
|
|
223
240
|
[*self.source_features, *self.target_features, *self.obstacle_features]
|
|
224
241
|
)
|
|
225
242
|
|
|
226
243
|
if densify_features:
|
|
227
244
|
if max_segment_length is None:
|
|
228
|
-
max_segment_length = self.
|
|
229
|
-
|
|
245
|
+
max_segment_length = self._calc_segmentation_dist()
|
|
246
|
+
log.info("Calculated max_segment_length of %s" % max_segment_length)
|
|
230
247
|
|
|
231
248
|
for feature in self.all_features:
|
|
232
249
|
if not isinstance(feature.geometry, Point):
|
|
@@ -234,8 +251,32 @@ class AdjacencyEngine:
|
|
|
234
251
|
# Reset all coordinates
|
|
235
252
|
self._all_coordinates = None
|
|
236
253
|
|
|
254
|
+
@property
|
|
255
|
+
def all_features(self):
|
|
256
|
+
"""
|
|
257
|
+
All source, target, and obstacle features in a single list. The order of this list must
|
|
258
|
+
not be changed. This property cannot be set manually.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List[_Feature]: A list of _Features.
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
return self._all_features
|
|
265
|
+
|
|
266
|
+
@all_features.setter
|
|
267
|
+
def all_features(self, value):
|
|
268
|
+
raise ImmutablePropertyError("Property all_features is immutable.")
|
|
269
|
+
|
|
237
270
|
@property
|
|
238
271
|
def all_coordinates(self):
|
|
272
|
+
"""
|
|
273
|
+
All source, target, and obstacle coordinates in a single list. The order of this list must
|
|
274
|
+
not be changed. This property cannot be set manually.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
List[tuple[float, float]]: A list of coordinate tuples.
|
|
278
|
+
"""
|
|
279
|
+
|
|
239
280
|
if not self._all_coordinates:
|
|
240
281
|
self._all_coordinates = []
|
|
241
282
|
for feature in self.all_features:
|
|
@@ -248,7 +289,7 @@ class AdjacencyEngine:
|
|
|
248
289
|
def all_coordinates(self, value):
|
|
249
290
|
raise ImmutablePropertyError("Property all_coordinates is immutable.")
|
|
250
291
|
|
|
251
|
-
def
|
|
292
|
+
def _calc_segmentation_dist(self, divisor=5):
|
|
252
293
|
"""
|
|
253
294
|
Try to create a well-fitting maximum length for all line segments in all features. Take
|
|
254
295
|
the average distance between all coordinate pairs and divide by 5. This means that the
|
|
@@ -258,10 +299,12 @@ class AdjacencyEngine:
|
|
|
258
299
|
average segment lengths. In that case, it is advisable to prepare the data appropriately
|
|
259
300
|
beforehand.
|
|
260
301
|
|
|
261
|
-
:
|
|
302
|
+
Args:
|
|
303
|
+
divisor (int, optional): Divide the average segment length by this number to get the new desired
|
|
262
304
|
segment length.
|
|
263
305
|
|
|
264
|
-
:
|
|
306
|
+
Returns:
|
|
307
|
+
float: Average segment length divided by divisor.
|
|
265
308
|
"""
|
|
266
309
|
|
|
267
310
|
return float(
|
|
@@ -276,7 +319,10 @@ class AdjacencyEngine:
|
|
|
276
319
|
def source_features(self) -> Tuple[_Feature]:
|
|
277
320
|
"""
|
|
278
321
|
Features which will be the keys in the adjacency_dict.
|
|
279
|
-
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
List[_Feature]: A list of _Features.
|
|
325
|
+
|
|
280
326
|
"""
|
|
281
327
|
return self._source_features
|
|
282
328
|
|
|
@@ -288,7 +334,8 @@ class AdjacencyEngine:
|
|
|
288
334
|
def target_features(self) -> Tuple[_Feature]:
|
|
289
335
|
"""
|
|
290
336
|
Features which will be the values in the adjacency_dict.
|
|
291
|
-
:
|
|
337
|
+
Returns:
|
|
338
|
+
List[_Feature]: A list of _Features.
|
|
292
339
|
"""
|
|
293
340
|
return self._target_features
|
|
294
341
|
|
|
@@ -301,7 +348,9 @@ class AdjacencyEngine:
|
|
|
301
348
|
"""
|
|
302
349
|
Features which can prevent source and target features from being adjacent. They
|
|
303
350
|
Do not participate in the adjacency_dict.
|
|
304
|
-
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
List[_Feature]: A list of _Features.
|
|
305
354
|
"""
|
|
306
355
|
return self._obstacle_features
|
|
307
356
|
|
|
@@ -316,8 +365,11 @@ class AdjacencyEngine:
|
|
|
316
365
|
determine which coordinate belongs to which feature after we calculate the voronoi
|
|
317
366
|
diagram.
|
|
318
367
|
|
|
319
|
-
:
|
|
368
|
+
Args:
|
|
369
|
+
coord_index (int): The index of the coordinate in self._all_coordinates
|
|
320
370
|
|
|
371
|
+
Returns:
|
|
372
|
+
_Feature: A _Feature at the given index.
|
|
321
373
|
"""
|
|
322
374
|
if not self._feature_indices:
|
|
323
375
|
self._feature_indices = {}
|
|
@@ -335,9 +387,9 @@ class AdjacencyEngine:
|
|
|
335
387
|
The Voronoi diagram object returned by Scipy. Useful primarily for debugging an
|
|
336
388
|
adjacency analysis.
|
|
337
389
|
|
|
338
|
-
:
|
|
390
|
+
Returns:
|
|
391
|
+
scipy.spatial.Voronoi: The Scipy Voronoi object.
|
|
339
392
|
"""
|
|
340
|
-
|
|
341
393
|
if not self._vor:
|
|
342
394
|
self._vor = Voronoi(np.array(self.all_coordinates))
|
|
343
395
|
return self._vor
|
|
@@ -346,12 +398,27 @@ class AdjacencyEngine:
|
|
|
346
398
|
def vor(self, _):
|
|
347
399
|
raise ImmutablePropertyError("Property vor is immutable.")
|
|
348
400
|
|
|
401
|
+
def _get_voronoi_vertex_idx_for_coord_idx(self, feature_coord_index: int) -> Generator[int, None, None]:
|
|
402
|
+
"""
|
|
403
|
+
For a given feature coordinate index, return the indices of the voronoi vertices. Ignore
|
|
404
|
+
any "-1"s, which indicate vertices at infinity; these provide no adjacency information.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
feature_coord_index (int): The index of the coordinate in self.all_coordinates
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Generator[int, None, None]: A generator of the indices of the voronoi vertices.
|
|
411
|
+
"""
|
|
412
|
+
return (i for i in self.vor.regions[self.vor.point_region[feature_coord_index]] if i != -1)
|
|
413
|
+
|
|
349
414
|
def _tag_feature_with_voronoi_vertices(self):
|
|
350
415
|
"""
|
|
351
416
|
Tag each feature with the vertices of the voronoi region it belongs to. Runs the
|
|
352
417
|
voronoi analysis if it has not been done already. This is broken out mostly for testing.
|
|
353
418
|
Do not call this function directly.
|
|
354
|
-
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
None
|
|
355
422
|
"""
|
|
356
423
|
# We don't need to tag obstacles with their voronoi vertices
|
|
357
424
|
obstacle_coord_len = sum(len(feat.coords) for feat in self.obstacle_features)
|
|
@@ -362,29 +429,25 @@ class AdjacencyEngine:
|
|
|
362
429
|
len(self.all_coordinates) - obstacle_coord_len
|
|
363
430
|
):
|
|
364
431
|
feature = self.get_feature_from_coord_index(feature_coord_index)
|
|
365
|
-
for
|
|
366
|
-
|
|
367
|
-
]:
|
|
368
|
-
# "-1" indices indicate the vertex goes to infinity. These don't provide us
|
|
369
|
-
# with adjacency information, so we ignore them.
|
|
370
|
-
if voronoi_vertex_index != -1:
|
|
371
|
-
feature.voronoi_points.add(voronoi_vertex_index)
|
|
432
|
+
for i in self._get_voronoi_vertex_idx_for_coord_idx(feature_coord_index):
|
|
433
|
+
feature.voronoi_points.add(i)
|
|
372
434
|
|
|
373
435
|
def _determine_adjacency(
|
|
374
436
|
self, source_set: Tuple[_Feature], target_set: Tuple[_Feature]
|
|
375
437
|
):
|
|
376
438
|
"""
|
|
377
439
|
Determines the adjacency relationship between two sets of features.
|
|
378
|
-
|
|
440
|
+
Args:
|
|
379
441
|
source_set (Tuple[_Feature]): The set of source features.
|
|
380
442
|
target_set (Tuple[_Feature]): The set of target features.
|
|
443
|
+
|
|
381
444
|
Returns:
|
|
382
445
|
None
|
|
383
446
|
"""
|
|
384
447
|
min_overlapping_voronoi_vertices = 2
|
|
385
448
|
for source_index, source_feature in enumerate(source_set):
|
|
386
449
|
for target_index, target_feature in enumerate(target_set):
|
|
387
|
-
if source_feature != target_feature and source_feature.
|
|
450
|
+
if source_feature != target_feature and source_feature._is_adjacent(
|
|
388
451
|
target_feature, min_overlapping_voronoi_vertices
|
|
389
452
|
):
|
|
390
453
|
self._adjacency_dict[source_index].append(target_index)
|
|
@@ -397,12 +460,14 @@ class AdjacencyEngine:
|
|
|
397
460
|
If no targets were specified, then calculate adjacency between source features and other
|
|
398
461
|
source features.
|
|
399
462
|
|
|
400
|
-
:
|
|
401
|
-
|
|
463
|
+
Returns:
|
|
464
|
+
dict: A dictionary of indices. The keys are the indices of feature_geoms. The
|
|
465
|
+
values are the indices of any adjacent features.
|
|
466
|
+
|
|
402
467
|
"""
|
|
403
468
|
|
|
404
|
-
|
|
405
|
-
|
|
469
|
+
"""Note: We want adjacent features to have at least two overlapping vertices, otherwise we
|
|
470
|
+
might call the features adjacent when their Voronoi regions don't share any edges."""
|
|
406
471
|
|
|
407
472
|
if self._adjacency_dict is None:
|
|
408
473
|
self._tag_feature_with_voronoi_vertices()
|
|
@@ -424,7 +489,9 @@ class AdjacencyEngine:
|
|
|
424
489
|
"""
|
|
425
490
|
Plot the adjacency linkages between the source and target with pyplot. Runs the analysis if
|
|
426
491
|
it has not already been run.
|
|
427
|
-
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
None
|
|
428
495
|
"""
|
|
429
496
|
# Plot the adjacency linkages between the source and target
|
|
430
497
|
if len(self.target_features) > 0:
|
|
@@ -445,7 +512,7 @@ class AdjacencyEngine:
|
|
|
445
512
|
)
|
|
446
513
|
)
|
|
447
514
|
except ValueError:
|
|
448
|
-
|
|
515
|
+
log.error(
|
|
449
516
|
f"Error creating link between '{target_poly}' and '{source_poly}'"
|
|
450
517
|
)
|
|
451
518
|
add_geometry_to_plot(links, "green")
|
geo_adjacency/utils.py
CHANGED
|
@@ -13,8 +13,13 @@ def coords_from_point(point: Point) -> List[Tuple[float, float]]:
|
|
|
13
13
|
"""
|
|
14
14
|
Convert a Point into a tuple of (x, y). We put this inside a list for consistency with other
|
|
15
15
|
coordinate methods to allow us to seamlessly merge them later.
|
|
16
|
-
|
|
17
|
-
:
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
point (Point): A Shapely Point.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List[Tuple[float, float]]: A list of coordinate tuples.
|
|
22
|
+
|
|
18
23
|
"""
|
|
19
24
|
assert isinstance(point, Point), "Geometry must be a Point, not '%s'." % type(point)
|
|
20
25
|
return [(float(point.x), float(point.y))]
|
|
@@ -22,9 +27,13 @@ def coords_from_point(point: Point) -> List[Tuple[float, float]]:
|
|
|
22
27
|
|
|
23
28
|
def coords_from_ring(ring: LineString) -> List[Tuple[float, float]]:
|
|
24
29
|
"""
|
|
25
|
-
Convert a LinearRing into a list of (x, y) tuples
|
|
26
|
-
|
|
27
|
-
:
|
|
30
|
+
Convert a LinearRing into a list of (x, y) tuples.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ring (LineString): A Shapely LinearString.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
List[Tuple[float, float]]: A list of coordinate tuples.
|
|
28
37
|
"""
|
|
29
38
|
assert isinstance(
|
|
30
39
|
ring, LineString
|
|
@@ -36,8 +45,12 @@ def coords_from_polygon(polygon: Polygon) -> List[Tuple[float, float]]:
|
|
|
36
45
|
"""
|
|
37
46
|
Convert a Polygon into a list of (x, y) tuples. Does not repeat the first coordinate to close
|
|
38
47
|
the ring.
|
|
39
|
-
|
|
40
|
-
:
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
polygon (Polygon): A Shapely Polygon.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List[Tuple[float, float]]: A list of coordinate tuples.
|
|
41
54
|
"""
|
|
42
55
|
assert isinstance(polygon, Polygon), "Geometry must be a Polygon, not '%s'." % type(
|
|
43
56
|
polygon
|
|
@@ -53,8 +66,12 @@ def coords_from_multipolygon(multipolygon: MultiPolygon) -> List[Tuple[float, fl
|
|
|
53
66
|
"""
|
|
54
67
|
Convert a MultiPolygon into a list of (x, y) tuples. Does not repeat the first coordinate to
|
|
55
68
|
close the ring.
|
|
56
|
-
|
|
57
|
-
:
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
multipolygon (MultiPolygon): A Shapely MultiPolygon.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List[Tuple[float, float]]: A list of coordinate tuples.
|
|
58
75
|
"""
|
|
59
76
|
assert isinstance(
|
|
60
77
|
multipolygon, MultiPolygon
|
|
@@ -68,8 +85,11 @@ def coords_from_multipolygon(multipolygon: MultiPolygon) -> List[Tuple[float, fl
|
|
|
68
85
|
def flatten_list(nested_list) -> List:
|
|
69
86
|
"""
|
|
70
87
|
Flatten a list of lists.
|
|
71
|
-
:
|
|
72
|
-
|
|
88
|
+
Args:
|
|
89
|
+
nested_list (List): A list of lists.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
|
|
73
93
|
"""
|
|
74
94
|
# check if list is empty
|
|
75
95
|
if not bool(nested_list):
|
|
@@ -88,9 +108,13 @@ def add_geometry_to_plot(geoms, color="black"):
|
|
|
88
108
|
"""
|
|
89
109
|
When updating the test data, it may be useful to visualize it. Add a geometry to the global
|
|
90
110
|
maplotlib plt object. The next time we call plt.show(), this geometry will be plotted.
|
|
91
|
-
|
|
92
|
-
:
|
|
93
|
-
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
geoms (List[BaseGeometry]): A list of Shapely geometries.
|
|
114
|
+
color (str): The color we want the geometry to be in the plot.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
None
|
|
94
118
|
"""
|
|
95
119
|
for geom in geoms:
|
|
96
120
|
if isinstance(geom, Point):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: geo-adjacency
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A package to determine which geometries are adjacent to each other, accounting for obstacles and gaps between features.
|
|
5
5
|
Home-page: https://asmyth01.github.io/geo-adjacency/
|
|
6
6
|
License: MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
geo_adjacency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
geo_adjacency/adjacency.py,sha256=bL6ErCwgtpvNu0XfgpQwCJ6RlK2M-vi-qx_veE1_0Rk,20844
|
|
3
|
+
geo_adjacency/exception.py,sha256=zZNdBOm5LpuiCpNuqH1FNLhiPnQqyCyuhOTMBDnLSTQ,230
|
|
4
|
+
geo_adjacency/utils.py,sha256=57Q-nRZQlW1QetlLoucbDr1jm3CRHYRCVzrarm7xxZw,4188
|
|
5
|
+
geo_adjacency-1.1.2.dist-info/LICENSE,sha256=p0PMGdB2iuOndKPbBCVhTNe9TMIxZRpJ64bQ_CoUIqY,1065
|
|
6
|
+
geo_adjacency-1.1.2.dist-info/METADATA,sha256=fpHyeHH726bndsYxfE_CWAtJLmBN9MlN3lFiraRX8PA,3857
|
|
7
|
+
geo_adjacency-1.1.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
8
|
+
geo_adjacency-1.1.2.dist-info/RECORD,,
|
geo_adjacency/file.log
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
2023-11-20 19:19:58,755 - __main__ - ERROR - This is an error
|
|
2
|
-
2023-11-20 19:21:23,871 - __main__ - ERROR - This is an error
|
|
3
|
-
2023-11-20 19:22:38,493 - __main__ - ERROR - This is an error
|
|
4
|
-
2023-11-20 19:22:48,026 - __main__ - ERROR - This is an error
|
|
5
|
-
2023-11-20 19:23:34,369 - __main__ - ERROR - This is an error
|
|
6
|
-
2023-11-20 19:23:44,297 - __main__ - ERROR - This is an error
|
|
7
|
-
2023-11-20 19:26:28,000 - __main__ - ERROR - This is an error
|
|
8
|
-
2023-11-20 19:26:58,737 - __main__ - ERROR - This is an error
|
|
9
|
-
2023-11-20 19:28:41,686 - __main__ - ERROR - This is an error
|
|
10
|
-
2023-11-20 19:29:03,485 - __main__ - ERROR - This is an error
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
geo_adjacency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
geo_adjacency/adjacency.py,sha256=owrsKi-SnCHa14JQGjW_lVXCKVz1yO5-Fr3r1Vp7Wpk,18598
|
|
3
|
-
geo_adjacency/exception.py,sha256=zZNdBOm5LpuiCpNuqH1FNLhiPnQqyCyuhOTMBDnLSTQ,230
|
|
4
|
-
geo_adjacency/file.log,sha256=Zc7rMTU9N_LJKx68zsOwDJcuTnD8Mxll3iHlmZWQUzg,620
|
|
5
|
-
geo_adjacency/utils.py,sha256=YiyNDj-Ye6gK883Mg52lIabb0M5g8W5rj6-3aotvhdg,3789
|
|
6
|
-
geo_adjacency-1.1.1.dist-info/LICENSE,sha256=p0PMGdB2iuOndKPbBCVhTNe9TMIxZRpJ64bQ_CoUIqY,1065
|
|
7
|
-
geo_adjacency-1.1.1.dist-info/METADATA,sha256=QEWLk4kTRQ9NYlYhhG_QPfS_FfjPmbpPIan6zwfXjkM,3857
|
|
8
|
-
geo_adjacency-1.1.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
9
|
-
geo_adjacency-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|