geo-adjacency 1.1.1__py3-none-any.whl → 1.2.0__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.
@@ -1,20 +1,21 @@
1
1
  """
2
2
  The `adjacency` module implements the AdjacencyEngine class,
3
- which allows us to determine 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.
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
- 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.
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
- `AdjacencyEngine` utilizes a Voronoi of all the vertices in all the geometries combined to determine
12
- which geomtries are adjacent to each other. See
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:
@@ -26,7 +27,7 @@ import numpy as np
26
27
  import shapely.ops
27
28
  from scipy.spatial import distance
28
29
  from scipy.spatial import Voronoi
29
- from shapely import LineString, Point, Polygon, MultiPolygon
30
+ from shapely import LineString, Point, Polygon, MultiPolygon, box
30
31
  from shapely.geometry.base import BaseGeometry
31
32
 
32
33
  from geo_adjacency.exception import ImmutablePropertyError
@@ -39,34 +40,54 @@ 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
- logger = logging.getLogger(__name__)
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(
52
+ "%(name)s - %(levelname)s - %(message)s"
53
+ )
54
+ f_format: logging.Formatter = logging.Formatter(
55
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
56
+ )
53
57
  c_handler.setFormatter(c_format)
54
58
 
55
59
  # Add handlers to the logger
56
- logger.addHandler(c_handler)
60
+ log.addHandler(c_handler)
57
61
 
58
62
 
59
63
  class _Feature:
64
+ """
65
+ A _Feature is a wrapper around a Shapely geometry that allows us to easily determine if two
66
+ geometries are adjacent.
67
+ """
68
+
60
69
  __slots__ = ("_geometry", "_coords", "voronoi_points")
61
70
 
62
71
  def __init__(self, geometry: BaseGeometry):
72
+ """
73
+ Create a _Feature from a Shapely geometry.
74
+
75
+ Args:
76
+ geometry (BaseGeometry): A valid Shapely Geometry, either a Point, LineString, Polygon, or
77
+ MultiPolygon.
78
+ """
79
+
63
80
  if not isinstance(geometry, (Point, Polygon, MultiPolygon, LineString)):
64
81
  raise TypeError(
65
- "Cannot create _Feature for geometry type '%s'." % type(self.geometry)
82
+ "Cannot create _Feature for geometry type '%s'." % type(geometry)
66
83
  )
67
84
 
85
+ assert geometry.is_valid, (
86
+ "Could not process invalid geometry: %s" % geometry.wkt
87
+ )
88
+
68
89
  self._geometry: BaseGeometry = geometry
69
- self._coords = None
90
+ self._coords: Union[List[Tuple[float, float]], None] = None
70
91
  self.voronoi_points: set = set()
71
92
 
72
93
  def __str__(self):
@@ -75,32 +96,30 @@ class _Feature:
75
96
  def __repr__(self):
76
97
  return f"<_Feature: {str(self.geometry)}>"
77
98
 
78
- def __eq__(self, other: Self):
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(
99
+ def _is_adjacent(
87
100
  self, other: Self, min_overlapping_voronoi_vertices: int = 2
88
101
  ) -> bool:
89
102
  """
90
103
  Determine if two features are adjacent based on how many Voronoi vertices they share. Note:
91
104
  the Voronoi analysis must have been run, or this will always return False.
92
- :param min_overlapping_voronoi_vertices:
93
- :param other:
94
- :return:
105
+ Args:
106
+ other (_Feature): Another _Feature to compare to.
107
+ min_overlapping_voronoi_vertices (int): The minimum number of Voronoi vertices that
108
+ must be shared to be considered adjacent.
109
+
110
+ Returns:
111
+ bool: True if the two features are adjacent.
112
+
95
113
  """
96
114
  assert isinstance(other, type(self)), "Cannot compare '%s' with '%s'." % (
97
115
  type(self),
98
116
  type(other),
99
117
  )
100
118
  if len(self.voronoi_points) == 0 and len(other.voronoi_points) == 0:
101
- logger.warning(
119
+ log.warning(
102
120
  "No Voronoi vertices found for either feature. Did you run the analysis yet?"
103
121
  )
122
+ return False
104
123
  return (
105
124
  len(self.voronoi_points & other.voronoi_points)
106
125
  >= min_overlapping_voronoi_vertices
@@ -110,7 +129,10 @@ class _Feature:
110
129
  def geometry(self):
111
130
  """
112
131
  Access the Shapely geometry of the feature.
113
- :return: BaseGeometry
132
+
133
+ Returns:
134
+ BaseGeometry: The Shapely geometry of the feature.
135
+
114
136
  """
115
137
  return self._geometry
116
138
 
@@ -124,7 +146,9 @@ class _Feature:
124
146
  """
125
147
  Convenience property for accessing the coordinates of the geometry as a list of 2-tuples.
126
148
 
127
- :return: List[Tuple[float, float]]: A list of coordinate tuples.
149
+ Returns:
150
+ List[Tuple[float, float]]: A list of coordinate tuples.
151
+
128
152
  """
129
153
 
130
154
  if not self._coords:
@@ -140,6 +164,10 @@ class _Feature:
140
164
  raise TypeError(f"Unknown geometry type '{type(self.geometry)}'")
141
165
  return self._coords
142
166
 
167
+ @coords.setter
168
+ def coords(self, coords):
169
+ raise ImmutablePropertyError("Property coords is immutable.")
170
+
143
171
 
144
172
  class AdjacencyEngine:
145
173
  """
@@ -149,9 +177,6 @@ class AdjacencyEngine:
149
177
  First, the Voronoi diagram is generated for each geometry and obstacle. Then, we check which
150
178
  voronoi shapes intersect one another. If they do, then the two underlying geometries are
151
179
  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
180
  """
156
181
 
157
182
  __slots__ = (
@@ -161,8 +186,10 @@ class AdjacencyEngine:
161
186
  "_adjacency_dict",
162
187
  "_feature_indices",
163
188
  "_vor",
164
- "all_features",
189
+ "_all_features",
165
190
  "_all_coordinates",
191
+ "_max_distance",
192
+ "_bounding_rectangle",
166
193
  )
167
194
 
168
195
  def __init__(
@@ -170,30 +197,51 @@ class AdjacencyEngine:
170
197
  source_geoms: List[BaseGeometry],
171
198
  target_geoms: Union[List[BaseGeometry], None] = None,
172
199
  obstacle_geoms: Union[List[BaseGeometry], None] = None,
173
- densify_features: bool = False,
174
- max_segment_length: Union[float, None] = None,
200
+ **kwargs,
175
201
  ):
176
202
  """
177
- Note: only Multipolygons, Polygons, LineStrings and Points are supported. It is assumed all
203
+ Note: only Multipolygons, Polygons, LineStrings and Points are supported. It is assumed all
178
204
  features are in the same projection.
179
205
 
180
- :param source_geoms: List of Shapely geometries. We will which ones are adjacent to
181
- which others.
182
- :param target_geoms: Optional list of Shapley geometries. if not None, We will
206
+ Args:
207
+ source_geoms (List[BaseGeometry]): List of Shapely geometries. We will which ones are adjacent to
208
+ which others, unless target_geoms is specified.
209
+ target_geoms (Union[List[BaseGeometry], None]), optional): list of Shapley geometries. if not None, We will
183
210
  test if these features are adjacent to the source features.
184
- :param obstacle_geoms: List
211
+ obstacle_geoms (Union[List[BaseGeometry], None]), optional): List
185
212
  of Shapely geometries. These features will not be tested for adjacency, but they can
186
213
  prevent a source and target feature from being adjacent.
187
- :param densify_features: If
188
- 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
193
- will be calculated based on the average segment length of all features, divided by 5.
194
- This often works well.
214
+
215
+ Keyword Args:
216
+ densify_features (bool, optional): If True, we will add additional points to the
217
+ features to improve accuracy of the voronoi diagram. If densify_features is True and
218
+ max_segment_length is false, then the max_segment_length will be calculated based on
219
+ the average segment length of all features, divided by 5.
220
+ max_segment_length (Union[float, None], optional): The maximum distance between vertices
221
+ that we want iIn projection units. densify_features must be True, or an error will be thrown.
222
+ max_distance (Union[float, None], optional): The maximum distance between two features
223
+ for them to be candidates for adjacency. Units are same as geometry coordinate system.
224
+ bounding_box (Union[float, float, float, float, None], optional): Set a bounding box
225
+ for the analysis. Only include features that intersect the box in the output.
226
+ This is useful for removing data from the edges from the final analysis, as these
227
+ are often not accurate. This is particularly helpful when analyzing a large data set
228
+ in a windowed fashion. Expected format is (minx, miny, maxx, maxy).
229
+
195
230
  """
196
231
 
232
+ densify_features = kwargs.get("densify_features", False)
233
+ max_segment_length = kwargs.get("max_segment_length", None)
234
+ self._max_distance = kwargs.get("max_distance", None)
235
+
236
+ if kwargs.get("bounding_box", None):
237
+ minx, miny, maxx, maxy = kwargs.get("bounding_box")
238
+ assert (
239
+ minx < maxx and miny < maxy
240
+ ), "Bounding box must have minx < maxx and miny < maxy"
241
+ self._bounding_rectangle: Polygon = box(minx, miny, maxx, maxy)
242
+ else:
243
+ self._bounding_rectangle = None
244
+
197
245
  if max_segment_length and not densify_features:
198
246
  raise ValueError(
199
247
  "interpolate_points must be True if interpolation_distance is not None"
@@ -212,21 +260,21 @@ class AdjacencyEngine:
212
260
  if obstacle_geoms
213
261
  else tuple()
214
262
  )
215
- self._adjacency_dict = None
216
- self._feature_indices = None
263
+ self._adjacency_dict: Union[Dict[int, List[int]], None] = None
264
+ self._feature_indices: Union[Dict[int, int], None] = None
217
265
  self._vor = None
218
266
  self._all_coordinates = None
219
267
 
220
268
  """All source, target, and obstacle features in a single list. The order of this list must
221
269
  not be changed."""
222
- self.all_features: Tuple[_Feature, ...] = tuple(
270
+ self._all_features: Tuple[_Feature, ...] = tuple(
223
271
  [*self.source_features, *self.target_features, *self.obstacle_features]
224
272
  )
225
273
 
226
274
  if densify_features:
227
275
  if max_segment_length is None:
228
- max_segment_length = self.calc_segmentation_dist()
229
- logger.info("Calculated max_segment_length of %s" % max_segment_length)
276
+ max_segment_length = self._calc_segmentation_dist()
277
+ log.info("Calculated max_segment_length of %s" % max_segment_length)
230
278
 
231
279
  for feature in self.all_features:
232
280
  if not isinstance(feature.geometry, Point):
@@ -234,8 +282,32 @@ class AdjacencyEngine:
234
282
  # Reset all coordinates
235
283
  self._all_coordinates = None
236
284
 
285
+ @property
286
+ def all_features(self):
287
+ """
288
+ All source, target, and obstacle features in a single list. The order of this list must
289
+ not be changed. This property cannot be set manually.
290
+
291
+ Returns:
292
+ List[_Feature]: A list of _Features.
293
+
294
+ """
295
+ return self._all_features
296
+
297
+ @all_features.setter
298
+ def all_features(self, value):
299
+ raise ImmutablePropertyError("Property all_features is immutable.")
300
+
237
301
  @property
238
302
  def all_coordinates(self):
303
+ """
304
+ All source, target, and obstacle coordinates in a single list. The order of this list must
305
+ not be changed. This property cannot be set manually.
306
+
307
+ Returns:
308
+ List[tuple[float, float]]: A list of coordinate tuples.
309
+ """
310
+
239
311
  if not self._all_coordinates:
240
312
  self._all_coordinates = []
241
313
  for feature in self.all_features:
@@ -248,7 +320,7 @@ class AdjacencyEngine:
248
320
  def all_coordinates(self, value):
249
321
  raise ImmutablePropertyError("Property all_coordinates is immutable.")
250
322
 
251
- def calc_segmentation_dist(self, divisor=5):
323
+ def _calc_segmentation_dist(self, divisor=5):
252
324
  """
253
325
  Try to create a well-fitting maximum length for all line segments in all features. Take
254
326
  the average distance between all coordinate pairs and divide by 5. This means that the
@@ -258,10 +330,12 @@ class AdjacencyEngine:
258
330
  average segment lengths. In that case, it is advisable to prepare the data appropriately
259
331
  beforehand.
260
332
 
261
- :param divisor: Divide the average segment length by this number to get the new desired
333
+ Args:
334
+ divisor (int, optional): Divide the average segment length by this number to get the new desired
262
335
  segment length.
263
336
 
264
- :return: Average segment length divided by divisor
337
+ Returns:
338
+ float: Average segment length divided by divisor.
265
339
  """
266
340
 
267
341
  return float(
@@ -276,7 +350,10 @@ class AdjacencyEngine:
276
350
  def source_features(self) -> Tuple[_Feature]:
277
351
  """
278
352
  Features which will be the keys in the adjacency_dict.
279
- :return: List of source features.
353
+
354
+ Returns:
355
+ List[_Feature]: A list of _Features.
356
+
280
357
  """
281
358
  return self._source_features
282
359
 
@@ -288,7 +365,8 @@ class AdjacencyEngine:
288
365
  def target_features(self) -> Tuple[_Feature]:
289
366
  """
290
367
  Features which will be the values in the adjacency_dict.
291
- :return: List of target features.
368
+ Returns:
369
+ List[_Feature]: A list of _Features.
292
370
  """
293
371
  return self._target_features
294
372
 
@@ -301,7 +379,9 @@ class AdjacencyEngine:
301
379
  """
302
380
  Features which can prevent source and target features from being adjacent. They
303
381
  Do not participate in the adjacency_dict.
304
- :return: List of obstacle features.
382
+
383
+ Returns:
384
+ List[_Feature]: A list of _Features.
305
385
  """
306
386
  return self._obstacle_features
307
387
 
@@ -316,8 +396,11 @@ class AdjacencyEngine:
316
396
  determine which coordinate belongs to which feature after we calculate the voronoi
317
397
  diagram.
318
398
 
319
- :return: A _Feature.
399
+ Args:
400
+ coord_index (int): The index of the coordinate in self._all_coordinates
320
401
 
402
+ Returns:
403
+ _Feature: A _Feature at the given index.
321
404
  """
322
405
  if not self._feature_indices:
323
406
  self._feature_indices = {}
@@ -335,9 +418,9 @@ class AdjacencyEngine:
335
418
  The Voronoi diagram object returned by Scipy. Useful primarily for debugging an
336
419
  adjacency analysis.
337
420
 
338
- :return: Voronoi object.
421
+ Returns:
422
+ scipy.spatial.Voronoi: The Scipy Voronoi object.
339
423
  """
340
-
341
424
  if not self._vor:
342
425
  self._vor = Voronoi(np.array(self.all_coordinates))
343
426
  return self._vor
@@ -346,12 +429,33 @@ class AdjacencyEngine:
346
429
  def vor(self, _):
347
430
  raise ImmutablePropertyError("Property vor is immutable.")
348
431
 
432
+ def _get_voronoi_vertex_idx_for_coord_idx(
433
+ self, feature_coord_index: int
434
+ ) -> Generator[int, None, None]:
435
+ """
436
+ For a given feature coordinate index, return the indices of the voronoi vertices. Ignore
437
+ any "-1"s, which indicate vertices at infinity; these provide no adjacency information.
438
+
439
+ Args:
440
+ feature_coord_index (int): The index of the coordinate in self.all_coordinates
441
+
442
+ Returns:
443
+ Generator[int, None, None]: A generator of the indices of the voronoi vertices.
444
+ """
445
+ return (
446
+ i
447
+ for i in self.vor.regions[self.vor.point_region[feature_coord_index]]
448
+ if i != -1
449
+ )
450
+
349
451
  def _tag_feature_with_voronoi_vertices(self):
350
452
  """
351
453
  Tag each feature with the vertices of the voronoi region it belongs to. Runs the
352
454
  voronoi analysis if it has not been done already. This is broken out mostly for testing.
353
455
  Do not call this function directly.
354
- :return:
456
+
457
+ Returns:
458
+ None
355
459
  """
356
460
  # We don't need to tag obstacles with their voronoi vertices
357
461
  obstacle_coord_len = sum(len(feat.coords) for feat in self.obstacle_features)
@@ -362,32 +466,45 @@ class AdjacencyEngine:
362
466
  len(self.all_coordinates) - obstacle_coord_len
363
467
  ):
364
468
  feature = self.get_feature_from_coord_index(feature_coord_index)
365
- for voronoi_vertex_index in self.vor.regions[
366
- self.vor.point_region[feature_coord_index]
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)
469
+ for i in self._get_voronoi_vertex_idx_for_coord_idx(feature_coord_index):
470
+ feature.voronoi_points.add(i)
372
471
 
373
472
  def _determine_adjacency(
374
473
  self, source_set: Tuple[_Feature], target_set: Tuple[_Feature]
375
474
  ):
376
475
  """
377
476
  Determines the adjacency relationship between two sets of features.
378
- Parameters:
477
+ Args:
379
478
  source_set (Tuple[_Feature]): The set of source features.
380
479
  target_set (Tuple[_Feature]): The set of target features.
480
+
381
481
  Returns:
382
482
  None
383
483
  """
384
484
  min_overlapping_voronoi_vertices = 2
385
485
  for source_index, source_feature in enumerate(source_set):
486
+ if (
487
+ self._bounding_rectangle is not None
488
+ and not self._bounding_rectangle.intersects(source_feature.geometry)
489
+ ):
490
+ continue
386
491
  for target_index, target_feature in enumerate(target_set):
387
- if source_feature != target_feature and source_feature.is_adjacent(
388
- target_feature, min_overlapping_voronoi_vertices
389
- ):
390
- self._adjacency_dict[source_index].append(target_index)
492
+ if source_feature != target_feature:
493
+ if (
494
+ self._max_distance is not None
495
+ and source_feature.geometry.distance(target_feature.geometry)
496
+ > self._max_distance
497
+ ) or (
498
+ self._bounding_rectangle is not None
499
+ and not self._bounding_rectangle.intersects(
500
+ target_feature.geometry
501
+ )
502
+ ):
503
+ continue
504
+ if source_feature._is_adjacent(
505
+ target_feature, min_overlapping_voronoi_vertices
506
+ ):
507
+ self._adjacency_dict[source_index].append(target_index)
391
508
 
392
509
  def get_adjacency_dict(self) -> Dict[int, List[int]]:
393
510
  """
@@ -397,12 +514,14 @@ class AdjacencyEngine:
397
514
  If no targets were specified, then calculate adjacency between source features and other
398
515
  source features.
399
516
 
400
- :return: dict A dictionary of indices. The keys are the indices of feature_geoms. The
401
- values are the indices of any adjacent features
517
+ Returns:
518
+ dict: A dictionary of indices. The keys are the indices of feature_geoms. The
519
+ values are the indices of any adjacent features.
520
+
402
521
  """
403
522
 
404
- # We want adjacent features to have at least two overlapping vertices, otherwise we might
405
- # call the features adjacent when their voronoi regions don't share any edges.
523
+ """Note: We want adjacent features to have at least two overlapping vertices, otherwise we
524
+ might call the features adjacent when their Voronoi regions don't share any edges."""
406
525
 
407
526
  if self._adjacency_dict is None:
408
527
  self._tag_feature_with_voronoi_vertices()
@@ -424,7 +543,9 @@ class AdjacencyEngine:
424
543
  """
425
544
  Plot the adjacency linkages between the source and target with pyplot. Runs the analysis if
426
545
  it has not already been run.
427
- :return: None
546
+
547
+ Returns:
548
+ None
428
549
  """
429
550
  # Plot the adjacency linkages between the source and target
430
551
  if len(self.target_features) > 0:
@@ -445,7 +566,7 @@ class AdjacencyEngine:
445
566
  )
446
567
  )
447
568
  except ValueError:
448
- logger.error(
569
+ log.error(
449
570
  f"Error creating link between '{target_poly}' and '{source_poly}'"
450
571
  )
451
572
  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
- :param point: A Shapely Point.
17
- :return:
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
- :param ring: A Shapely LinearRing.
27
- :return:
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
- :param polygon: A Shapely Polygon.
40
- :return:
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
- :param multipolygon: A Shapely MultiPolygon.
57
- :return:
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
- :param nested_list: A list of lists.
72
- :return:
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
- :param geoms: A list of Shapely geometries.
92
- :param color: The color we want the geometry to be in the plot.
93
- :return: None
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.1
3
+ Version: 1.2.0
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=pIxXEQjcjFKH95Xk27O8qYrD3ZY88JWOA7qOId82JW4,22941
3
+ geo_adjacency/exception.py,sha256=zZNdBOm5LpuiCpNuqH1FNLhiPnQqyCyuhOTMBDnLSTQ,230
4
+ geo_adjacency/utils.py,sha256=57Q-nRZQlW1QetlLoucbDr1jm3CRHYRCVzrarm7xxZw,4188
5
+ geo_adjacency-1.2.0.dist-info/LICENSE,sha256=p0PMGdB2iuOndKPbBCVhTNe9TMIxZRpJ64bQ_CoUIqY,1065
6
+ geo_adjacency-1.2.0.dist-info/METADATA,sha256=hbT90t7FAtGb2tzr4GgnZrfA_2KESFEW2upzVAxSaYk,3857
7
+ geo_adjacency-1.2.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
8
+ geo_adjacency-1.2.0.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,,