ssb-sgis 1.0.0__py3-none-any.whl → 1.0.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.
Files changed (59) hide show
  1. sgis/__init__.py +97 -115
  2. sgis/exceptions.py +3 -1
  3. sgis/geopandas_tools/__init__.py +1 -0
  4. sgis/geopandas_tools/bounds.py +75 -38
  5. sgis/geopandas_tools/buffer_dissolve_explode.py +38 -34
  6. sgis/geopandas_tools/centerlines.py +53 -44
  7. sgis/geopandas_tools/cleaning.py +87 -104
  8. sgis/geopandas_tools/conversion.py +149 -101
  9. sgis/geopandas_tools/duplicates.py +31 -17
  10. sgis/geopandas_tools/general.py +76 -48
  11. sgis/geopandas_tools/geometry_types.py +21 -7
  12. sgis/geopandas_tools/neighbors.py +20 -8
  13. sgis/geopandas_tools/overlay.py +136 -53
  14. sgis/geopandas_tools/point_operations.py +9 -8
  15. sgis/geopandas_tools/polygon_operations.py +48 -56
  16. sgis/geopandas_tools/polygons_as_rings.py +121 -78
  17. sgis/geopandas_tools/sfilter.py +14 -14
  18. sgis/helpers.py +114 -56
  19. sgis/io/dapla_functions.py +32 -23
  20. sgis/io/opener.py +13 -6
  21. sgis/io/read_parquet.py +1 -1
  22. sgis/maps/examine.py +39 -26
  23. sgis/maps/explore.py +112 -66
  24. sgis/maps/httpserver.py +12 -12
  25. sgis/maps/legend.py +124 -65
  26. sgis/maps/map.py +66 -41
  27. sgis/maps/maps.py +31 -29
  28. sgis/maps/thematicmap.py +46 -33
  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 +20 -62
  35. sgis/networkanalysis/cutting_lines.py +55 -43
  36. sgis/networkanalysis/directednetwork.py +15 -7
  37. sgis/networkanalysis/finding_isolated_networks.py +4 -3
  38. sgis/networkanalysis/network.py +15 -13
  39. sgis/networkanalysis/networkanalysis.py +72 -54
  40. sgis/networkanalysis/networkanalysisrules.py +20 -16
  41. sgis/networkanalysis/nodes.py +2 -3
  42. sgis/networkanalysis/traveling_salesman.py +5 -2
  43. sgis/parallel/parallel.py +337 -127
  44. sgis/raster/__init__.py +6 -0
  45. sgis/raster/base.py +9 -3
  46. sgis/raster/cube.py +280 -208
  47. sgis/raster/cubebase.py +15 -29
  48. sgis/raster/indices.py +3 -7
  49. sgis/raster/methods_as_functions.py +0 -124
  50. sgis/raster/raster.py +313 -127
  51. sgis/raster/torchgeo.py +58 -37
  52. sgis/raster/zonal.py +38 -13
  53. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
  54. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +89 -18
  55. ssb_sgis-1.0.2.dist-info/RECORD +61 -0
  56. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/WHEEL +1 -1
  57. sgis/raster/bands.py +0 -48
  58. sgis/raster/gradient.py +0 -78
  59. ssb_sgis-1.0.0.dist-info/RECORD +0 -63
sgis/maps/maps.py CHANGED
@@ -11,25 +11,30 @@ import inspect
11
11
  from numbers import Number
12
12
  from typing import Any
13
13
 
14
- from geopandas import GeoDataFrame, GeoSeries
14
+ from geopandas import GeoDataFrame
15
+ from geopandas import GeoSeries
15
16
  from pyproj import CRS
16
17
  from shapely import Geometry
18
+ from shapely import box
19
+ from shapely.geometry import Polygon
17
20
 
21
+ from ..geopandas_tools.bounds import get_total_bounds
18
22
  from ..geopandas_tools.conversion import to_gdf as to_gdf_func
19
- from ..geopandas_tools.general import clean_geoms, get_common_crs, is_wkt
23
+ from ..geopandas_tools.general import clean_geoms
24
+ from ..geopandas_tools.general import get_common_crs
25
+ from ..geopandas_tools.general import is_wkt
20
26
  from ..geopandas_tools.geocoding import address_to_gdf
21
27
  from ..geopandas_tools.geometry_types import get_geom_type
22
28
  from .explore import Explore
23
29
  from .map import Map
24
30
  from .thematicmap import ThematicMap
25
31
 
26
-
27
32
  try:
28
33
  from torchgeo.datasets.geo import RasterDataset
29
34
  except ImportError:
30
35
 
31
36
  class RasterDataset:
32
- """Placeholder"""
37
+ """Placeholder."""
33
38
 
34
39
 
35
40
  def _get_location_mask(kwargs: dict, gdfs) -> tuple[GeoDataFrame | None, dict]:
@@ -72,7 +77,6 @@ def explore(
72
77
  *gdfs: GeoDataFrame | dict[str, GeoDataFrame],
73
78
  column: str | None = None,
74
79
  center: Any | None = None,
75
- center_4326: Any | None = None,
76
80
  labels: tuple[str] | None = None,
77
81
  max_zoom: int = 40,
78
82
  browser: bool = False,
@@ -94,10 +98,9 @@ def explore(
94
98
  *gdfs: one or more GeoDataFrames.
95
99
  column: The column to color the geometries by. Defaults to None, which means
96
100
  each GeoDataFrame will get a unique color.
97
- center: Either an address string to be geocoded or a geometry like object
98
- (coordinate pair (x, y), GeoDataFrame, bbox, etc.). If the geometry is a
99
- point (or line), it will be buffered by 1000 (can be changed with the)
100
- size parameter. If a polygon is given, it will not be buffered.
101
+ center: Geometry-like object to center the map on. If a three-length tuple
102
+ is given, the first two should be x and y coordinates and the third
103
+ should be a number of meters to buffer the centerpoint by.
101
104
  labels: By default, the GeoDataFrames will be labeled by their object names.
102
105
  Alternatively, labels can be specified as a tuple of strings with the same
103
106
  length as the number of gdfs.
@@ -113,12 +116,12 @@ def explore(
113
116
  instance 'cmap' to change the colors, 'scheme' to change how the data
114
117
  is grouped. This defaults to 'fisherjenkssampled' for numeric data.
115
118
 
116
- See also
119
+ See Also:
117
120
  --------
118
121
  samplemap: same functionality, but shows only a random area of a given size.
119
122
  clipmap: same functionality, but shows only the areas clipped by a given mask.
120
123
 
121
- Examples
124
+ Examples:
122
125
  --------
123
126
  >>> import sgis as sg
124
127
  >>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
@@ -138,7 +141,6 @@ def explore(
138
141
  >>> points["meters"] = points.length
139
142
  >>> sg.explore(roads, points, column="meters", cmap="plasma", max_zoom=60, center_4326=(10.7463, 59.92, 500))
140
143
  """
141
-
142
144
  gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
143
145
 
144
146
  loc_mask, kwargs = _get_location_mask(kwargs | {"size": size}, gdfs)
@@ -162,14 +164,11 @@ def explore(
162
164
  to_crs = gdfs[0].crs
163
165
  except IndexError:
164
166
  try:
165
- to_crs = [x for x in kwargs.values() if hasattr(x, "crs")][0].crs
167
+ to_crs = next(x for x in kwargs.values() if hasattr(x, "crs")).crs
166
168
  except IndexError:
167
169
  to_crs = None
168
170
 
169
- if center_4326 is not None:
170
- from_crs = 4326
171
- center = center_4326
172
- elif "crs" in kwargs:
171
+ if "crs" in kwargs:
173
172
  from_crs = kwargs.pop("crs")
174
173
  else:
175
174
  from_crs = to_crs
@@ -185,6 +184,10 @@ def explore(
185
184
  *center, size = center
186
185
  mask = to_gdf_func(center, crs=from_crs)
187
186
 
187
+ bounds: Polygon = box(*get_total_bounds(*gdfs, *list(kwargs.values())))
188
+ if not mask.intersects(bounds).any():
189
+ mask = mask.set_crs(4326, allow_override=True)
190
+
188
191
  try:
189
192
  mask = mask.to_crs(to_crs)
190
193
  except ValueError:
@@ -270,12 +273,12 @@ def samplemap(
270
273
  instance 'cmap' to change the colors, 'scheme' to change how the data
271
274
  is grouped. This defaults to 'fisherjenkssampled' for numeric data.
272
275
 
273
- See also
276
+ See Also:
274
277
  --------
275
278
  explore: Same functionality, but shows the entire area of the geometries.
276
279
  clipmap: Same functionality, but shows only the areas clipped by a given mask.
277
280
 
278
- Examples
281
+ Examples:
279
282
  --------
280
283
  >>> from sgis import read_parquet_url, samplemap
281
284
  >>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_eidskog_2022.parquet")
@@ -290,7 +293,6 @@ def samplemap(
290
293
  >>> samplemap(roads, points, size=5_000, column="meters")
291
294
 
292
295
  """
293
-
294
296
  if gdfs and isinstance(gdfs[-1], (float, int)):
295
297
  *gdfs, size = gdfs
296
298
 
@@ -393,12 +395,11 @@ def clipmap(
393
395
  instance 'cmap' to change the colors, 'scheme' to change how the data
394
396
  is grouped. This defaults to 'fisherjenkssampled' for numeric data.
395
397
 
396
- See also
398
+ See Also:
397
399
  --------
398
400
  explore: same functionality, but shows the entire area of the geometries.
399
401
  samplemap: same functionality, but shows only a random area of a given size.
400
402
  """
401
-
402
403
  gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
403
404
 
404
405
  if mask is None and len(gdfs) > 1:
@@ -444,7 +445,7 @@ def clipmap(
444
445
  qtm(m._gdf, column=m.column, cmap=m._cmap, k=m.k)
445
446
 
446
447
 
447
- def explore_locals(*gdfs, convert: bool = True, **kwargs):
448
+ def explore_locals(*gdfs: GeoDataFrame, convert: bool = True, **kwargs) -> None:
448
449
  """Displays all local variables with geometries (GeoDataFrame etc.).
449
450
 
450
451
  Local means inside a function or file/notebook.
@@ -478,15 +479,15 @@ def explore_locals(*gdfs, convert: bool = True, **kwargs):
478
479
 
479
480
  if isinstance(value, dict) or hasattr(value, "__dict__"):
480
481
  # add dicts or classes with GeoDataFrames to kwargs
481
- for key, value in as_dict(value).items():
482
- if isinstance(value, allowed_types):
483
- gdf = clean_geoms(to_gdf_func(value))
482
+ for key, val in as_dict(value).items():
483
+ if isinstance(val, allowed_types):
484
+ gdf = clean_geoms(to_gdf_func(val))
484
485
  if len(gdf):
485
486
  local_gdfs[key] = gdf
486
487
 
487
- elif isinstance(value, dict) or hasattr(value, "__dict__"):
488
+ elif isinstance(val, dict) or hasattr(val, "__dict__"):
488
489
  try:
489
- for k, v in value.items():
490
+ for k, v in val.items():
490
491
  if isinstance(v, allowed_types):
491
492
  gdf = clean_geoms(to_gdf_func(v))
492
493
  if len(gdf):
@@ -543,12 +544,13 @@ def qtm(
543
544
  'viridis' when black, and 'RdPu' when white.
544
545
  size: Size of the plot. Defaults to 10.
545
546
  title_fontsize: Size of the title.
547
+ legend: Whether to add legend. Defaults to True.
546
548
  cmap: Color palette of the map. See:
547
549
  https://matplotlib.org/stable/tutorials/colors/colormaps.html
548
550
  k: Number of color groups.
549
551
  **kwargs: Additional keyword arguments taken by the geopandas plot method.
550
552
 
551
- See also:
553
+ See Also:
552
554
  ThematicMap: Class with more options for customising the plot.
553
555
  """
554
556
  gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
sgis/maps/thematicmap.py CHANGED
@@ -1,16 +1,19 @@
1
1
  """Make static maps with geopandas and matplotlib."""
2
+
2
3
  import warnings
4
+ from typing import Any
3
5
 
4
6
  import matplotlib
7
+ import matplotlib.figure
5
8
  import matplotlib.pyplot as plt
6
9
  import numpy as np
7
10
  import pandas as pd
8
11
  from geopandas import GeoDataFrame
9
12
 
10
- from .legend import ContinousLegend, Legend
13
+ from .legend import ContinousLegend
14
+ from .legend import Legend
11
15
  from .map import Map
12
16
 
13
-
14
17
  # the geopandas._explore raises a deprication warning. Ignoring for now.
15
18
  warnings.filterwarnings(
16
19
  action="ignore", category=matplotlib.MatplotlibDeprecationWarning
@@ -24,16 +27,6 @@ class ThematicMap(Map):
24
27
  The class takes one or more GeoDataFrames and a column name. The class attributes
25
28
  can then be set to customise the map before plotting.
26
29
 
27
- Args:
28
- *gdfs: One or more GeoDataFrames.
29
- column: The name of the column to plot.
30
- size: Width and height of the plot in inches. Fontsize of title and legend is
31
- adjusted accordingly. Defaults to 25.
32
- black: If False (default), the background will be white and the text black. If
33
- True, the background will be black and the text white. When True, the
34
- default cmap is "viridis", and when False, the default is red to purple
35
- (RdPu).
36
-
37
30
  Attributes:
38
31
  size (int): Width and height of the plot in inches.
39
32
  k (int): Number of color groups.
@@ -50,7 +43,7 @@ class ThematicMap(Map):
50
43
  cmap_stop (int): End position for the color palette.
51
44
  facecolor (str): Background color.
52
45
 
53
- Examples
46
+ Examples:
54
47
  --------
55
48
  >>> import sgis as sg
56
49
  >>> points = sg.random_points(100).pipe(sg.buff, np.random.rand(100))
@@ -90,7 +83,20 @@ class ThematicMap(Map):
90
83
  column: str | None = None,
91
84
  size: int = 25,
92
85
  black: bool = False,
93
- ):
86
+ ) -> None:
87
+ """Initialiser.
88
+
89
+ Args:
90
+ *gdfs: One or more GeoDataFrames.
91
+ column: The name of the column to plot.
92
+ size: Width and height of the plot in inches. Fontsize of title and legend is
93
+ adjusted accordingly. Defaults to 25.
94
+ black: If False (default), the background will be white and the text black. If
95
+ True, the background will be black and the text white. When True, the
96
+ default cmap is "viridis", and when False, the default is red to purple
97
+ (RdPu).
98
+
99
+ """
94
100
  super().__init__(*gdfs, column=column)
95
101
 
96
102
  self._size = size
@@ -106,7 +112,7 @@ class ThematicMap(Map):
106
112
 
107
113
  self._create_legend()
108
114
 
109
- def change_cmap(self, cmap: str, start: int = 0, stop: int = 256):
115
+ def change_cmap(self, cmap: str, start: int = 0, stop: int = 256) -> "ThematicMap":
110
116
  """Change the color palette of the plot.
111
117
 
112
118
  Args:
@@ -119,7 +125,9 @@ class ThematicMap(Map):
119
125
  super().change_cmap(cmap, start, stop)
120
126
  return self
121
127
 
122
- def add_background(self, gdf, color: str | None = None):
128
+ def add_background(
129
+ self, gdf: GeoDataFrame, color: str | None = None
130
+ ) -> "ThematicMap":
123
131
  """Add a GeoDataFrame as a background layer.
124
132
 
125
133
  Args:
@@ -145,7 +153,6 @@ class ThematicMap(Map):
145
153
 
146
154
  This method should be run after customising the map, but before saving.
147
155
  """
148
-
149
156
  __test = kwargs.pop("__test", False)
150
157
  include_legend = bool(kwargs.pop("legend", self.legend))
151
158
 
@@ -214,7 +221,7 @@ class ThematicMap(Map):
214
221
  with fs.open(path, "wb") as file:
215
222
  plt.savefig(file)
216
223
 
217
- def _prepare_plot(self, **kwargs):
224
+ def _prepare_plot(self, **kwargs) -> None:
218
225
  """Add figure and axis, title and background gdf."""
219
226
  for attr in self.__dict__.keys():
220
227
  if attr in self.kwargs:
@@ -236,7 +243,7 @@ class ThematicMap(Map):
236
243
  self.title, fontsize=self.title_fontsize, color=self.title_color
237
244
  )
238
245
 
239
- def _prepare_continous_plot(self, kwargs) -> dict:
246
+ def _prepare_continous_plot(self, kwargs: dict) -> dict:
240
247
  """Create bins and colors."""
241
248
  self._prepare_continous_map()
242
249
 
@@ -270,7 +277,7 @@ class ThematicMap(Map):
270
277
 
271
278
  return kwargs
272
279
 
273
- def _prepare_categorical_plot(self, kwargs) -> dict:
280
+ def _prepare_categorical_plot(self, kwargs: dict) -> dict:
274
281
  """Map values to colors."""
275
282
  self._get_categorical_colors()
276
283
  colorarray = self._gdf["color"]
@@ -300,7 +307,7 @@ class ThematicMap(Map):
300
307
  bin_values=self._bins_unique_values,
301
308
  )
302
309
 
303
- def _create_legend(self):
310
+ def _create_legend(self) -> None:
304
311
  """Instantiate the Legend class."""
305
312
  kwargs = {}
306
313
  if self._black:
@@ -313,8 +320,8 @@ class ThematicMap(Map):
313
320
  else:
314
321
  self.legend = ContinousLegend(title=self._column, size=self._size, **kwargs)
315
322
 
316
- def _choose_cmap(self):
317
- """kwargs is to catch start and stop points for the cmap in __init__."""
323
+ def _choose_cmap(self) -> None:
324
+ """Kwargs is to catch start and stop points for the cmap in __init__."""
318
325
  if self._black:
319
326
  self._cmap = "viridis"
320
327
  self.cmap_start = 0
@@ -324,7 +331,7 @@ class ThematicMap(Map):
324
331
  self.cmap_start = 23
325
332
  self.cmap_stop = 256
326
333
 
327
- def _make_bin_value_dict(self, gdf, classified) -> dict:
334
+ def _make_bin_value_dict(self, gdf: GeoDataFrame, classified: np.ndarray) -> dict:
328
335
  """Dict with unique values of all bins. Used in labels in ContinousLegend."""
329
336
  bins_unique_values = {
330
337
  i: list(set(gdf.loc[classified == i, self._column]))
@@ -332,18 +339,20 @@ class ThematicMap(Map):
332
339
  }
333
340
  return bins_unique_values
334
341
 
335
- def _actually_add_background(self):
342
+ def _actually_add_background(self) -> None:
336
343
  self.ax.set_xlim([self.minx - self.diffx * 0.03, self.maxx + self.diffx * 0.03])
337
344
  self.ax.set_ylim([self.miny - self.diffy * 0.03, self.maxy + self.diffy * 0.03])
338
345
  self._background_gdfs.plot(ax=self.ax, color=self.bg_gdf_color)
339
346
 
340
347
  @staticmethod
341
- def _get_matplotlib_figure_and_axix(figsize: tuple[int, int]):
348
+ def _get_matplotlib_figure_and_axix(
349
+ figsize: tuple[int, int]
350
+ ) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]:
342
351
  fig = plt.figure(figsize=figsize)
343
352
  ax = fig.add_subplot(1, 1, 1)
344
353
  return fig, ax
345
354
 
346
- def _black_or_white(self):
355
+ def _black_or_white(self) -> None:
347
356
  if self._black:
348
357
  self.facecolor, self.title_color, self.bg_gdf_color = (
349
358
  "#0f0f0f",
@@ -367,7 +376,8 @@ class ThematicMap(Map):
367
376
  self._create_legend()
368
377
 
369
378
  @property
370
- def black(self):
379
+ def black(self) -> bool:
380
+ """Whether to use dark background and light text colors."""
371
381
  return self._black
372
382
 
373
383
  @black.setter
@@ -376,20 +386,22 @@ class ThematicMap(Map):
376
386
  self._black_or_white()
377
387
 
378
388
  @property
379
- def title_fontsize(self):
389
+ def title_fontsize(self) -> int:
390
+ """Title fontsize, not to be confused with legend.title_fontsize."""
380
391
  return self._title_fontsize
381
392
 
382
393
  @title_fontsize.setter
383
- def title_fontsize(self, new_value: bool):
394
+ def title_fontsize(self, new_value: int) -> None:
384
395
  self._title_fontsize = new_value
385
396
  self._title_fontsize_has_been_set = True
386
397
 
387
398
  @property
388
- def size(self):
399
+ def size(self) -> int:
400
+ """Size of the image."""
389
401
  return self._size
390
402
 
391
403
  @size.setter
392
- def size(self, new_value: bool):
404
+ def size(self, new_value: bool) -> None:
393
405
  """Adjust font and marker size if not actively set."""
394
406
  self._size = new_value
395
407
  if not hasattr(self, "_title_fontsize_has_been_set"):
@@ -403,7 +415,8 @@ class ThematicMap(Map):
403
415
  if not hasattr(self.legend, "_markersize_has_been_set"):
404
416
  self.legend._markersize = self._size
405
417
 
406
- def __setattr__(self, __name: str, __value) -> None:
418
+ def __setattr__(self, __name: str, __value: Any) -> None:
419
+ """Set an attribute with square brackets."""
407
420
  if "legend_" in __name:
408
421
  last_part = __name.split("legend_")[-1]
409
422
  raise AttributeError(
sgis/maps/tilesources.py CHANGED
@@ -1,4 +1,6 @@
1
- from xyzservices import TileProvider, Bunch, providers
1
+ from xyzservices import Bunch
2
+ from xyzservices import TileProvider
3
+ from xyzservices import providers
2
4
 
3
5
  kartverket = Bunch(
4
6
  norgeskart=TileProvider(
@@ -7,49 +9,42 @@ kartverket = Bunch(
7
9
  attribution="© Kartverket",
8
10
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
9
11
  ),
10
-
11
12
  bakgrunnskart_forenklet=TileProvider(
12
13
  name="Norgeskart forenklet",
13
14
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=bakgrunnskart_forenklet&zoom={z}&x={x}&y={y}",
14
15
  attribution="© Kartverket",
15
16
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
16
17
  ),
17
-
18
18
  norges_grunnkart=TileProvider(
19
19
  name="Norges grunnkart",
20
20
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=norges_grunnkart&zoom={z}&x={x}&y={y}",
21
21
  attribution="© Kartverket",
22
22
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
23
23
  ),
24
-
25
24
  norges_grunnkart_gråtone=TileProvider(
26
25
  name="Norges grunnkart gråtone",
27
26
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=norges_grunnkart_graatone&zoom={z}&x={x}&y={y}",
28
27
  attribution="© Kartverket",
29
28
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
30
29
  ),
31
-
32
30
  n50=TileProvider(
33
31
  name="N5 til N50 kartdata",
34
32
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=kartdata3&zoom={z}&x={x}&y={y}",
35
33
  attribution="© Kartverket",
36
34
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
37
35
  ),
38
-
39
36
  topogråtone=TileProvider(
40
37
  name="Topografisk norgeskart gråtone",
41
38
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo4graatone&zoom={z}&x={x}&y={y}",
42
39
  attribution="© Kartverket",
43
40
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
44
41
  ),
45
-
46
42
  toporaster=TileProvider(
47
43
  name="Topografisk raster",
48
44
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=toporaster4&zoom={z}&x={x}&y={y}",
49
45
  attribution="© Kartverket",
50
46
  html_attribution='&copy; <a href="https://kartverket.no">Kartverket</a>',
51
47
  ),
52
-
53
48
  norge_i_bilder=TileProvider(
54
49
  name="Norge i bilder",
55
50
  url="https://opencache.statkart.no/gatekeeper/gk/gk.open_nib_web_mercator_wmts_v2?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=Nibcache_web_mercator_v2&STYLE=default&FORMAT=image/jpgpng&tileMatrixSet=default028mm&tileMatrix={z}&tileRow={y}&tileCol={x}",
@@ -7,7 +7,7 @@ from pandas import DataFrame
7
7
 
8
8
 
9
9
  def _get_route_frequencies(
10
- graph,
10
+ graph: Graph,
11
11
  roads: GeoDataFrame,
12
12
  weight_df: DataFrame,
13
13
  ) -> GeoDataFrame:
@@ -60,7 +60,6 @@ def _get_route(
60
60
  od_pairs: pd.MultiIndex,
61
61
  ) -> GeoDataFrame:
62
62
  """Function used in the get_route method of NetworkAnalysis."""
63
-
64
63
  warnings.filterwarnings("ignore", category=RuntimeWarning)
65
64
 
66
65
  resultlist: list[DataFrame] = []
@@ -86,7 +85,8 @@ def _get_route(
86
85
  if not resultlist:
87
86
  warnings.warn(
88
87
  "No paths were found. Try larger search_tolerance or search_factor. "
89
- "Or close_network_holes() or remove_isolated()."
88
+ "Or close_network_holes() or remove_isolated().",
89
+ stacklevel=1,
90
90
  )
91
91
  return pd.DataFrame(columns=["origin", "destination", weight, "geometry"])
92
92
 
@@ -121,7 +121,8 @@ def _get_k_routes(
121
121
  if not resultlist:
122
122
  warnings.warn(
123
123
  "No paths were found. Try larger search_tolerance or search_factor. "
124
- "Or close_network_holes() or remove_isolated()."
124
+ "Or close_network_holes() or remove_isolated().",
125
+ stacklevel=1,
125
126
  )
126
127
  return pd.DataFrame(columns=["origin", "destination", weight, "geometry"])
127
128
 
@@ -47,7 +47,9 @@ def _od_cost_matrix(
47
47
  return results.reset_index(drop=True)
48
48
 
49
49
 
50
- def _get_od_df(graph, origins, destinations, weight_col):
50
+ def _get_od_df(
51
+ graph: Graph, origins: GeoDataFrame, destinations: GeoDataFrame, weight_col: str
52
+ ) -> pd.DataFrame:
51
53
  distances: list[list[float]] = graph.distances(
52
54
  weights="weight",
53
55
  source=origins,
@@ -68,3 +70,44 @@ def _get_od_df(graph, origins, destinations, weight_col):
68
70
  .replace([np.inf, -np.inf], np.nan)
69
71
  .reset_index(drop=True)
70
72
  )
73
+
74
+
75
+ def _get_one_od_df(
76
+ graph: Graph, origins: GeoDataFrame, destinations: GeoDataFrame, weight_col: str
77
+ ) -> pd.DataFrame:
78
+ distances: list[list[float]] = graph.distances(
79
+ weights="weight",
80
+ source=origins,
81
+ target=destinations,
82
+ )
83
+
84
+ ori_idx, des_idx, costs = [], [], []
85
+ for i, f_idx in enumerate(origins):
86
+ for j, t_idx in enumerate(destinations):
87
+ ori_idx.append(f_idx)
88
+ des_idx.append(t_idx)
89
+ costs.append(distances[i][j])
90
+
91
+ return (
92
+ pd.DataFrame(
93
+ data={"origin": ori_idx, "destination": des_idx, weight_col: costs}
94
+ )
95
+ .replace([np.inf, -np.inf], np.nan)
96
+ .reset_index(drop=True)
97
+ )
98
+
99
+
100
+ # def _get_od_df(
101
+ # graph: Graph,
102
+ # origins: GeoDataFrame,
103
+ # destinations: GeoDataFrame,
104
+ # weight_col: str,
105
+ # ) -> pd.DataFrame:
106
+ # from ..parallel.parallel import Parallel
107
+
108
+ # results: list[pd.DataFrame] = Parallel(40, backend="loky").map(
109
+ # _get_one_od_df,
110
+ # [origins[origins.index == i] for i in origins.index.unique()],
111
+ # kwargs=dict(graph=graph, destinations=destinations, weight_col=weight_col),
112
+ # )
113
+ # return pd.concat(results, ignore_index=True)
@@ -1,10 +1,12 @@
1
+ from collections.abc import Sequence
2
+
1
3
  import numpy as np
4
+ import pandas as pd
2
5
  from geopandas import GeoDataFrame
3
6
 
4
7
  from ..geopandas_tools.neighbors import get_k_nearest_neighbors
5
8
  from .networkanalysisrules import NetworkAnalysisRules
6
9
 
7
-
8
10
  """
9
11
  These are internal classes used in the NetworkAnalysis class. The classes used in
10
12
  NetworkAnalysis are Origins and Destinations, which are subclasses of Points. The
@@ -26,7 +28,6 @@ class Points:
26
28
  The original indices are stored in a dict and mapped back to the results in the
27
29
  end.
28
30
  """
29
-
30
31
  self.gdf["temp_idx"] = np.arange(start=start, stop=start + len(self.gdf))
31
32
  self.gdf["temp_idx"] = self.gdf["temp_idx"].astype(str)
32
33
 
@@ -36,7 +37,9 @@ class Points:
36
37
  }
37
38
 
38
39
  @staticmethod
39
- def _convert_distance_to_weight(distances, rules):
40
+ def _convert_distance_to_weight(
41
+ distances: Sequence, rules: NetworkAnalysisRules
42
+ ) -> list[float]:
40
43
  """Meters to minutes based on 'weight_to_nodes_' attribute of the rules."""
41
44
  if not rules.nodedist_multiplier and not rules.nodedist_kmh:
42
45
  return [0 for _ in distances]
@@ -60,7 +63,9 @@ class Points:
60
63
 
61
64
  return [x / (16.666667 * rules.nodedist_kmh) for x in distances]
62
65
 
63
- def _make_edges(self, df, from_col, to_col):
66
+ def _make_edges(
67
+ self, df: GeoDataFrame | pd.DataFrame, from_col: str, to_col: str
68
+ ) -> list[tuple[int, int]]:
64
69
  return [(f, t) for f, t in zip(df[from_col], df[to_col], strict=True)]
65
70
 
66
71
  def _get_edges_and_weights(
@@ -71,6 +76,7 @@ class Points:
71
76
  from_col: str,
72
77
  to_col: str,
73
78
  ):
79
+ """Make edges and weights between points and the nodes of a network."""
74
80
  distances = get_k_nearest_neighbors(
75
81
  gdf=self.gdf.set_index("temp_idx"),
76
82
  neighbors=nodes.set_index("node_id"),
@@ -2,8 +2,11 @@ import numpy as np
2
2
  import pandas as pd
3
3
  from geopandas import GeoDataFrame
4
4
  from igraph import Graph
5
- from shapely import force_2d, reverse, unary_union
6
- from shapely.geometry import MultiLineString, MultiPoint, Point
5
+ from shapely import force_2d
6
+ from shapely import reverse
7
+ from shapely import unary_union
8
+ from shapely.geometry import MultiPoint
9
+ from shapely.geometry import Point
7
10
  from shapely.ops import nearest_points
8
11
  from shapely.wkt import loads
9
12