ssb-sgis 1.0.1__py3-none-any.whl → 1.0.3__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.
Files changed (60) hide show
  1. sgis/__init__.py +107 -121
  2. sgis/exceptions.py +5 -3
  3. sgis/geopandas_tools/__init__.py +1 -0
  4. sgis/geopandas_tools/bounds.py +86 -47
  5. sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
  6. sgis/geopandas_tools/centerlines.py +53 -44
  7. sgis/geopandas_tools/cleaning.py +87 -104
  8. sgis/geopandas_tools/conversion.py +164 -107
  9. sgis/geopandas_tools/duplicates.py +33 -19
  10. sgis/geopandas_tools/general.py +84 -52
  11. sgis/geopandas_tools/geometry_types.py +24 -10
  12. sgis/geopandas_tools/neighbors.py +23 -11
  13. sgis/geopandas_tools/overlay.py +136 -53
  14. sgis/geopandas_tools/point_operations.py +11 -10
  15. sgis/geopandas_tools/polygon_operations.py +53 -61
  16. sgis/geopandas_tools/polygons_as_rings.py +121 -78
  17. sgis/geopandas_tools/sfilter.py +17 -17
  18. sgis/helpers.py +116 -58
  19. sgis/io/dapla_functions.py +32 -23
  20. sgis/io/opener.py +13 -6
  21. sgis/io/read_parquet.py +2 -2
  22. sgis/maps/examine.py +55 -28
  23. sgis/maps/explore.py +471 -112
  24. sgis/maps/httpserver.py +12 -12
  25. sgis/maps/legend.py +285 -134
  26. sgis/maps/map.py +248 -129
  27. sgis/maps/maps.py +123 -119
  28. sgis/maps/thematicmap.py +260 -94
  29. sgis/maps/tilesources.py +3 -8
  30. sgis/networkanalysis/_get_route.py +5 -4
  31. sgis/networkanalysis/_od_cost_matrix.py +44 -1
  32. sgis/networkanalysis/_points.py +10 -4
  33. sgis/networkanalysis/_service_area.py +5 -2
  34. sgis/networkanalysis/closing_network_holes.py +22 -64
  35. sgis/networkanalysis/cutting_lines.py +58 -46
  36. sgis/networkanalysis/directednetwork.py +16 -8
  37. sgis/networkanalysis/finding_isolated_networks.py +6 -5
  38. sgis/networkanalysis/network.py +15 -13
  39. sgis/networkanalysis/networkanalysis.py +79 -61
  40. sgis/networkanalysis/networkanalysisrules.py +21 -17
  41. sgis/networkanalysis/nodes.py +2 -3
  42. sgis/networkanalysis/traveling_salesman.py +6 -3
  43. sgis/parallel/parallel.py +372 -142
  44. sgis/raster/base.py +9 -3
  45. sgis/raster/cube.py +331 -213
  46. sgis/raster/cubebase.py +15 -29
  47. sgis/raster/image_collection.py +2560 -0
  48. sgis/raster/indices.py +17 -12
  49. sgis/raster/raster.py +356 -275
  50. sgis/raster/sentinel_config.py +104 -0
  51. sgis/raster/zonal.py +38 -14
  52. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
  53. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
  54. ssb_sgis-1.0.3.dist-info/RECORD +61 -0
  55. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
  56. sgis/raster/bands.py +0 -48
  57. sgis/raster/gradient.py +0 -78
  58. sgis/raster/methods_as_functions.py +0 -124
  59. sgis/raster/torchgeo.py +0 -150
  60. ssb_sgis-1.0.1.dist-info/RECORD +0 -63
@@ -1,30 +1,39 @@
1
1
  import numbers
2
2
  import warnings
3
- from collections.abc import Hashable, Iterable
3
+ from collections.abc import Hashable
4
+ from collections.abc import Iterable
4
5
  from typing import Any
5
6
 
6
7
  import joblib
7
8
  import numpy as np
8
9
  import pandas as pd
9
10
  import pyproj
10
- from geopandas import GeoDataFrame, GeoSeries
11
- from geopandas.array import GeometryArray, GeometryDtype
11
+ from geopandas import GeoDataFrame
12
+ from geopandas import GeoSeries
13
+ from geopandas.array import GeometryArray
14
+ from geopandas.array import GeometryDtype
12
15
  from numpy.typing import NDArray
13
- from shapely import (
14
- Geometry,
15
- get_coordinates,
16
- get_exterior_ring,
17
- get_interior_ring,
18
- get_num_interior_rings,
19
- get_parts,
20
- linestrings,
21
- make_valid,
22
- )
16
+ from shapely import Geometry
17
+ from shapely import get_coordinates
18
+ from shapely import get_exterior_ring
19
+ from shapely import get_interior_ring
20
+ from shapely import get_num_interior_rings
21
+ from shapely import get_parts
22
+ from shapely import linestrings
23
+ from shapely import make_valid
23
24
  from shapely import points as shapely_points
24
25
  from shapely import unary_union
25
- from shapely.geometry import LineString, Point
26
+ from shapely.geometry import LineString
27
+ from shapely.geometry import Point
26
28
 
27
- from .geometry_types import get_geom_type, make_all_singlepart, to_single_geom_type
29
+ try:
30
+ import dask_geopandas
31
+ except ImportError:
32
+ pass
33
+
34
+ from .geometry_types import get_geom_type
35
+ from .geometry_types import make_all_singlepart
36
+ from .geometry_types import to_single_geom_type
28
37
 
29
38
 
30
39
  def split_geom_types(gdf: GeoDataFrame | GeoSeries) -> tuple[GeoDataFrame | GeoSeries]:
@@ -79,13 +88,13 @@ def get_common_crs(
79
88
  actually_different.add(x)
80
89
 
81
90
  if len(actually_different) == 1:
82
- return list(actually_different)[0]
91
+ return next(iter(actually_different))
83
92
  raise ValueError("'crs' mismatch.", truthy_crs)
84
93
 
85
94
  return pyproj.CRS(truthy_crs[0])
86
95
 
87
96
 
88
- def is_bbox_like(obj) -> bool:
97
+ def is_bbox_like(obj: Any) -> bool:
89
98
  if (
90
99
  hasattr(obj, "__iter__")
91
100
  and len(obj) == 4
@@ -114,6 +123,7 @@ def _push_geom_col(gdf: GeoDataFrame) -> GeoDataFrame:
114
123
 
115
124
 
116
125
  def drop_inactive_geometry_columns(gdf: GeoDataFrame) -> GeoDataFrame:
126
+ """Removes geometry columns in a GeoDataFrame if they are not active."""
117
127
  for col in gdf.columns:
118
128
  if (
119
129
  isinstance(gdf[col].dtype, GeometryDtype)
@@ -123,7 +133,7 @@ def drop_inactive_geometry_columns(gdf: GeoDataFrame) -> GeoDataFrame:
123
133
  return gdf
124
134
 
125
135
 
126
- def rename_geometry_if(gdf: GeoDataFrame) -> GeoDataFrame:
136
+ def _rename_geometry_if(gdf: GeoDataFrame) -> GeoDataFrame:
127
137
  geom_col = gdf._geometry_column_name
128
138
  if geom_col == "geometry" and geom_col in gdf.columns:
129
139
  return gdf
@@ -157,8 +167,8 @@ def clean_geoms(
157
167
  GeoDataFrame or GeoSeries with fixed geometries and only the rows with valid,
158
168
  non-empty and not-NaN/-None geometries.
159
169
 
160
- Examples
161
- --------
170
+ Examples:
171
+ ---------
162
172
  >>> import sgis as sg
163
173
  >>> import pandas as pd
164
174
  >>> from shapely import wkt
@@ -231,8 +241,20 @@ def clean_geoms(
231
241
 
232
242
 
233
243
  def get_grouped_centroids(
234
- gdf: GeoDataFrame, groupby: str, as_string: bool = True
244
+ gdf: GeoDataFrame, groupby: str | list[str], as_string: bool = True
235
245
  ) -> pd.Series:
246
+ """Get the centerpoint of the geometries within a group.
247
+
248
+ Args:
249
+ gdf: GeoDataFrame.
250
+ groupby: column to group by.
251
+ as_string: If True (default), coordinates are returned in
252
+ the format "{x}_{y}". If False, coordinates are returned
253
+ as Points.
254
+
255
+ Returns:
256
+ A pandas.Series of grouped centroids with the index of 'gdf'.
257
+ """
236
258
  centerpoints = gdf.assign(geometry=lambda x: x.centroid)
237
259
 
238
260
  grouped_centerpoints = centerpoints.dissolve(by=groupby).assign(
@@ -242,9 +264,13 @@ def get_grouped_centroids(
242
264
  ys = grouped_centerpoints.geometry.y
243
265
 
244
266
  if as_string:
245
- grouped_centerpoints["wkt"] = [f"{int(x)}_{int(y)}" for x, y in zip(xs, ys)]
267
+ grouped_centerpoints["wkt"] = [
268
+ f"{int(x)}_{int(y)}" for x, y in zip(xs, ys, strict=False)
269
+ ]
246
270
  else:
247
- grouped_centerpoints["wkt"] = [Point(x, y) for x, y in zip(xs, ys)]
271
+ grouped_centerpoints["wkt"] = [
272
+ Point(x, y) for x, y in zip(xs, ys, strict=False)
273
+ ]
248
274
 
249
275
  return gdf[groupby].map(grouped_centerpoints["wkt"])
250
276
 
@@ -258,12 +284,20 @@ def sort_large_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
258
284
  Returns:
259
285
  A GeoDataFrame or GeoSeries sorted from large to small in area.
260
286
 
261
- Examples
262
- --------
287
+ Examples:
288
+ ---------
263
289
  Create GeoDataFrame with NaN values.
264
290
 
265
291
  >>> import sgis as sg
266
- >>> df = sg.random_points(5)
292
+ >>> df = sg.to_gdf(
293
+ ... [
294
+ ... (0, 1),
295
+ ... (1, 0),
296
+ ... (1, 1),
297
+ ... (0, 0),
298
+ ... (0.5, 0.5),
299
+ ... ]
300
+ ... )
267
301
  >>> df.geometry = df.buffer([4, 1, 2, 3, 5])
268
302
  >>> df["col"] = [None, 1, 2, None, 1]
269
303
  >>> df["col2"] = [None, 1, 2, 3, None]
@@ -299,16 +333,6 @@ def sort_large_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
299
333
  return gdf.iloc[list(sorted_areas)]
300
334
 
301
335
 
302
- def sort_df(
303
- df: pd.DataFrame | GeoDataFrame, sort_col: pd.Series
304
- ) -> pd.DataFrame | GeoDataFrame:
305
- value_mapper: dict[int, Any] = dict(enumerate(sort_col.values))
306
- sorted_indices = dict(
307
- reversed(sorted(value_mapper.items(), key=lambda item: item[1]))
308
- )
309
- return df.iloc[list(sorted_indices)]
310
-
311
-
312
336
  def sort_long_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
313
337
  """Sort GeoDataFrame by length in decending order.
314
338
 
@@ -402,8 +426,8 @@ def random_points(n: int, loc: float | int = 0.5) -> GeoDataFrame:
402
426
  Returns:
403
427
  A GeoDataFrame of points with n rows.
404
428
 
405
- Examples
406
- --------
429
+ Examples:
430
+ ---------
407
431
  >>> import sgis as sg
408
432
  >>> points = sg.random_points(10_000)
409
433
  >>> points
@@ -451,6 +475,16 @@ def random_points(n: int, loc: float | int = 0.5) -> GeoDataFrame:
451
475
 
452
476
 
453
477
  def random_points_in_polygons(gdf: GeoDataFrame, n: int, seed=None) -> GeoDataFrame:
478
+ """Creates a GeoDataFrame with n random points within the geometries of 'gdf'.
479
+
480
+ Args:
481
+ gdf: A GeoDataFrame.
482
+ n: Number of points/rows to create.
483
+ seed: Optional random seet.
484
+
485
+ Returns:
486
+ A GeoDataFrame of points with n rows.
487
+ """
454
488
  all_points = []
455
489
 
456
490
  rng = np.random.default_rng(seed)
@@ -492,8 +526,8 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
492
526
  ignored. This is because the union overlay used if multiple GeoDataFrames
493
527
  always ignores the index.
494
528
 
495
- Examples
496
- --------
529
+ Examples:
530
+ ---------
497
531
  Convert single polygon to linestring.
498
532
 
499
533
  >>> import sgis as sg
@@ -525,7 +559,6 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
525
559
  >>> lines["l"] = lines.length
526
560
  >>> sg.qtm(lines, "l")
527
561
  """
528
-
529
562
  if not all(isinstance(gdf, (GeoSeries, GeoDataFrame)) for gdf in gdfs):
530
563
  raise TypeError("gdf must be GeoDataFrame or GeoSeries")
531
564
 
@@ -534,7 +567,6 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
534
567
 
535
568
  def _shapely_geometry_to_lines(geom):
536
569
  """Get all lines from the exterior and interiors of a Polygon."""
537
-
538
570
  # if lines (points are not allowed in this function)
539
571
  if geom.area == 0:
540
572
  return geom
@@ -664,11 +696,11 @@ def _determine_geom_type_args(
664
696
  return gdf, geom_type, keep_geom_type
665
697
 
666
698
 
667
- def merge_geometries(geoms: GeoSeries, grid_size=None) -> Geometry:
699
+ def _merge_geometries(geoms: GeoSeries, grid_size=None) -> Geometry:
668
700
  return make_valid(unary_union(geoms, grid_size=grid_size))
669
701
 
670
702
 
671
- def parallel_unary_union(
703
+ def _parallel_unary_union(
672
704
  gdf: GeoDataFrame, n_jobs: int = 1, by=None, grid_size=None, **kwargs
673
705
  ) -> list[Geometry]:
674
706
  try:
@@ -702,7 +734,7 @@ def parallel_unary_union(
702
734
  return dissolved.geometry
703
735
 
704
736
 
705
- def parallel_unary_union_geoseries(
737
+ def _parallel_unary_union_geoseries(
706
738
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
707
739
  ) -> list[Geometry]:
708
740
  if ser.crs is None:
@@ -725,7 +757,7 @@ def parallel_unary_union_geoseries(
725
757
  return dissolved.geometry
726
758
 
727
759
 
728
- def parallel_unary_union(
760
+ def _parallel_unary_union(
729
761
  gdf: GeoDataFrame, n_jobs: int = 1, by=None, grid_size=None, **kwargs
730
762
  ) -> list[Geometry]:
731
763
  try:
@@ -737,13 +769,13 @@ def parallel_unary_union(
737
769
  delayed_operations = []
738
770
  for _, geoms in gdf.groupby(by, **kwargs)[geom_col]:
739
771
  delayed_operations.append(
740
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
772
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
741
773
  )
742
774
 
743
775
  return parallel(delayed_operations)
744
776
 
745
777
 
746
- def parallel_unary_union_geoseries(
778
+ def _parallel_unary_union_geoseries(
747
779
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
748
780
  ) -> list[Geometry]:
749
781
 
@@ -756,18 +788,18 @@ def parallel_unary_union_geoseries(
756
788
  delayed_operations = []
757
789
  for _, geoms in many_hits.groupby(**kwargs):
758
790
  delayed_operations.append(
759
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
791
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
760
792
  )
761
793
 
762
794
  dissolved = pd.Series(
763
795
  parallel(delayed_operations),
764
- index=is_one_hit[lambda x: x == False].index.unique(),
796
+ index=is_one_hit[lambda x: x is False].index.unique(),
765
797
  )
766
798
 
767
799
  return pd.concat([dissolved, one_hit]).sort_index().values
768
800
 
769
801
 
770
- def parallel_unary_union_geoseries(
802
+ def _parallel_unary_union_geoseries(
771
803
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
772
804
  ) -> list[Geometry]:
773
805
 
@@ -775,7 +807,7 @@ def parallel_unary_union_geoseries(
775
807
  delayed_operations = []
776
808
  for _, geoms in ser.groupby(**kwargs):
777
809
  delayed_operations.append(
778
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
810
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
779
811
  )
780
812
 
781
813
  return parallel(delayed_operations)
@@ -1,8 +1,10 @@
1
1
  """Check and set geometry type."""
2
+
2
3
  import numpy as np
3
4
  import pandas as pd
4
5
  import shapely
5
- from geopandas import GeoDataFrame, GeoSeries
6
+ from geopandas import GeoDataFrame
7
+ from geopandas import GeoSeries
6
8
  from geopandas.array import GeometryArray
7
9
  from shapely import Geometry
8
10
 
@@ -10,6 +12,18 @@ from shapely import Geometry
10
12
  def make_all_singlepart(
11
13
  gdf: GeoDataFrame | GeoSeries, index_parts: bool = False, ignore_index: bool = False
12
14
  ) -> GeoDataFrame | GeoSeries:
15
+ """Make all geometries single part.
16
+
17
+ This means doing either 0, 1 or 2 calls to the explode method.
18
+
19
+ Args:
20
+ gdf: GeoDataFrame
21
+ index_parts: Defaults to False.
22
+ ignore_index: Defaults to False.
23
+
24
+ Returns:
25
+ A GeoDataFrame of singlepart geometries.
26
+ """
13
27
  # only explode if nessecary
14
28
  if (
15
29
  index_parts or ignore_index
@@ -25,7 +39,7 @@ def make_all_singlepart(
25
39
 
26
40
 
27
41
  def to_single_geom_type(
28
- gdf: GeoDataFrame | GeoSeries,
42
+ gdf: GeoDataFrame | GeoSeries | Geometry | GeometryArray | np.ndarray,
29
43
  geom_type: str,
30
44
  ignore_index: bool = False,
31
45
  ) -> GeoDataFrame | GeoSeries:
@@ -49,8 +63,8 @@ def to_single_geom_type(
49
63
  TypeError: If incorrect gdf type.
50
64
  ValueError: If 'geom_type' is neither 'polygon', 'line' or 'point'.
51
65
 
52
- Examples
53
- --------
66
+ Examples:
67
+ ---------
54
68
  First create a GeoDataFrame of mixed geometries.
55
69
 
56
70
  >>> from sgis import to_gdf, to_single_geom_type
@@ -96,7 +110,7 @@ def to_single_geom_type(
96
110
  arr = np.vectorize(_shapely_to_single_geom_type)(gdf, geom_type)
97
111
  return arr[~shapely.is_empty(arr)]
98
112
 
99
- if not isinstance(gdf, (GeoDataFrame, GeoSeries)):
113
+ if not isinstance(gdf, (GeoDataFrame, GeoSeries)): # type: ignore [unreachable]
100
114
  raise TypeError(f"'gdf' should be GeoDataFrame or GeoSeries, got {type(gdf)}")
101
115
 
102
116
  # explode collections to single-typed geometries
@@ -122,7 +136,7 @@ def to_single_geom_type(
122
136
  return gdf.reset_index(drop=True) if ignore_index else gdf
123
137
 
124
138
 
125
- def _shapely_to_single_geom_type(geom, geom_type):
139
+ def _shapely_to_single_geom_type(geom: Geometry, geom_type: str) -> Geometry:
126
140
  parts = shapely.get_parts(geom)
127
141
  return shapely.unary_union(
128
142
  [part for part in parts if geom_type.lower() in part.geom_type.lower()]
@@ -141,8 +155,8 @@ def get_geom_type(gdf: GeoDataFrame | GeoSeries) -> str:
141
155
  Raises:
142
156
  TypeError: If 'gdf' is not of type GeoDataFrame or GeoSeries.
143
157
 
144
- Examples
145
- --------
158
+ Examples:
159
+ ---------
146
160
  >>> from sgis import to_gdf, get_geom_type
147
161
  >>> gdf = to_gdf([0, 0])
148
162
  >>> gdf
@@ -189,8 +203,8 @@ def is_single_geom_type(gdf: GeoDataFrame | GeoSeries) -> bool:
189
203
  Raises:
190
204
  TypeError: If 'gdf' is not of type GeoDataFrame or GeoSeries.
191
205
 
192
- Examples
193
- --------
206
+ Examples:
207
+ ---------
194
208
  >>> from sgis import to_gdf, get_geom_type
195
209
  >>> gdf = to_gdf([0, 0])
196
210
  >>> gdf
@@ -10,9 +10,10 @@ types.
10
10
 
11
11
  import numpy as np
12
12
  import shapely
13
- from geopandas import GeoDataFrame, GeoSeries
14
- from pandas import DataFrame, Series, concat
15
- from shapely import STRtree
13
+ from geopandas import GeoDataFrame
14
+ from geopandas import GeoSeries
15
+ from pandas import DataFrame
16
+ from pandas import Series
16
17
  from sklearn.neighbors import NearestNeighbors
17
18
 
18
19
  from .conversion import coordinate_array
@@ -48,8 +49,8 @@ def get_neighbor_indices(
48
49
  ValueError: If gdf and neighbors do not have the same coordinate reference
49
50
  system.
50
51
 
51
- Examples
52
- --------
52
+ Examples:
53
+ ---------
53
54
  >>> from sgis import get_neighbor_indices, to_gdf
54
55
  >>> points = to_gdf([(0, 0), (0.5, 0.5)])
55
56
  >>> points
@@ -96,7 +97,6 @@ def get_neighbor_indices(
96
97
  ['a' 'a' 'b' 'b']
97
98
 
98
99
  """
99
-
100
100
  if gdf.crs != neighbors.crs:
101
101
  raise ValueError(f"'crs' mismatch. Got {gdf.crs} and {neighbors.crs}")
102
102
 
@@ -152,8 +152,8 @@ def get_all_distances(
152
152
  ValueError: If the coordinate reference system of 'gdf' and 'neighbors' are
153
153
  not the same.
154
154
 
155
- Examples
156
- --------
155
+ Examples:
156
+ ---------
157
157
  >>> from sgis import get_all_distances, random_points
158
158
  >>> points = random_points(100)
159
159
  >>> neighbors = random_points(100)
@@ -248,7 +248,6 @@ def sjoin_within_distance(
248
248
  **kwargs,
249
249
  ) -> GeoDataFrame:
250
250
  """Sjoin with a buffer on the right GeoDataFrame and adds a distance column."""
251
-
252
251
  new_neighbor_cols = {"__left_range_idx": range(len(neighbors))}
253
252
  if distance:
254
253
  new_neighbor_cols[neighbors._geometry_column_name] = lambda x: x.buffer(
@@ -297,8 +296,8 @@ def get_k_nearest_neighbors(
297
296
  ValueError: If the coordinate reference system of 'gdf' and 'neighbors' are
298
297
  not the same.
299
298
 
300
- Examples
301
- --------
299
+ Examples:
300
+ ---------
302
301
  Make some random points.
303
302
 
304
303
  >>> from sgis import get_k_nearest_neighbors, random_points
@@ -430,6 +429,19 @@ def k_nearest_neighbors(
430
429
  k: int | None = None,
431
430
  strict: bool = False,
432
431
  ) -> tuple[np.ndarray[float], np.ndarray[int]]:
432
+ """Finds nearest neighbors for an array of coordinates to another array of coordinates.
433
+
434
+ Args:
435
+ from_array: Numpy array of coordinates.
436
+ to_array: Numpy array of coordinates.
437
+ k: Number of neighbors to find.
438
+ strict: If True (not default), an exception is raised
439
+ if k is larger than the length of 'to_array'.
440
+
441
+ Returns a tuple of arrays, one with distances and one with indices
442
+ of the neighbors.
443
+
444
+ """
433
445
  if not len(to_array) or not len(from_array):
434
446
  return np.array([]), np.array([])
435
447