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.
- sgis/__init__.py +107 -121
- sgis/exceptions.py +5 -3
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +86 -47
- sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +164 -107
- sgis/geopandas_tools/duplicates.py +33 -19
- sgis/geopandas_tools/general.py +84 -52
- sgis/geopandas_tools/geometry_types.py +24 -10
- sgis/geopandas_tools/neighbors.py +23 -11
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +11 -10
- sgis/geopandas_tools/polygon_operations.py +53 -61
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +17 -17
- sgis/helpers.py +116 -58
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +2 -2
- sgis/maps/examine.py +55 -28
- sgis/maps/explore.py +471 -112
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +285 -134
- sgis/maps/map.py +248 -129
- sgis/maps/maps.py +123 -119
- sgis/maps/thematicmap.py +260 -94
- sgis/maps/tilesources.py +3 -8
- sgis/networkanalysis/_get_route.py +5 -4
- sgis/networkanalysis/_od_cost_matrix.py +44 -1
- sgis/networkanalysis/_points.py +10 -4
- sgis/networkanalysis/_service_area.py +5 -2
- sgis/networkanalysis/closing_network_holes.py +22 -64
- sgis/networkanalysis/cutting_lines.py +58 -46
- sgis/networkanalysis/directednetwork.py +16 -8
- sgis/networkanalysis/finding_isolated_networks.py +6 -5
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +79 -61
- sgis/networkanalysis/networkanalysisrules.py +21 -17
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +6 -3
- sgis/parallel/parallel.py +372 -142
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +331 -213
- sgis/raster/cubebase.py +15 -29
- sgis/raster/image_collection.py +2560 -0
- sgis/raster/indices.py +17 -12
- sgis/raster/raster.py +356 -275
- sgis/raster/sentinel_config.py +104 -0
- sgis/raster/zonal.py +38 -14
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
- ssb_sgis-1.0.3.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/torchgeo.py +0 -150
- ssb_sgis-1.0.1.dist-info/RECORD +0 -63
sgis/networkanalysis/network.py
CHANGED
|
@@ -5,7 +5,8 @@ network, finding and removing isolated network islands and creating unique node
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import warnings
|
|
8
|
-
from copy import copy
|
|
8
|
+
from copy import copy
|
|
9
|
+
from copy import deepcopy
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
from geopandas import GeoDataFrame
|
|
@@ -21,9 +22,12 @@ from .nodes import make_node_ids
|
|
|
21
22
|
class Network:
|
|
22
23
|
"""Class used in NetworkAnalysis."""
|
|
23
24
|
|
|
24
|
-
def __init__(self, gdf: GeoDataFrame):
|
|
25
|
+
def __init__(self, gdf: GeoDataFrame) -> None:
|
|
25
26
|
"""The lines are fixed, welded together rowwise and exploded. Creates node-ids.
|
|
26
27
|
|
|
28
|
+
Args:
|
|
29
|
+
gdf: GeoDataFrame of line geometries to make up the network.
|
|
30
|
+
|
|
27
31
|
Raises:
|
|
28
32
|
TypeError: If 'gdf' is not of type GeoDataFrame.
|
|
29
33
|
ZeroLinesError: If 'gdf' has zero rows.
|
|
@@ -152,6 +156,7 @@ class Network:
|
|
|
152
156
|
return True
|
|
153
157
|
|
|
154
158
|
def get_edges(self) -> list[tuple[str, str]]:
|
|
159
|
+
"""Get a list of edges in the network."""
|
|
155
160
|
return [
|
|
156
161
|
(str(source), str(target))
|
|
157
162
|
for source, target in zip(
|
|
@@ -166,12 +171,12 @@ class Network:
|
|
|
166
171
|
"""Edge identifiers represented with source and target ids and the weight."""
|
|
167
172
|
return [f"{s}_{t}_{w}" for (s, t), w in zip(edges, weights, strict=True)]
|
|
168
173
|
|
|
169
|
-
def _update_nodes_if(self):
|
|
174
|
+
def _update_nodes_if(self) -> None:
|
|
170
175
|
if not self._nodes_are_up_to_date():
|
|
171
176
|
self._make_node_ids()
|
|
172
177
|
|
|
173
178
|
@property
|
|
174
|
-
def nodes(self):
|
|
179
|
+
def nodes(self) -> GeoDataFrame:
|
|
175
180
|
"""GeoDataFrame with the network nodes (line endpoints).
|
|
176
181
|
|
|
177
182
|
Upon instantiation of the class, a GeoDataFrame of points is created from the
|
|
@@ -182,7 +187,7 @@ class Network:
|
|
|
182
187
|
"""
|
|
183
188
|
return self._nodes
|
|
184
189
|
|
|
185
|
-
def _warn_if_undirected(self):
|
|
190
|
+
def _warn_if_undirected(self) -> None:
|
|
186
191
|
"""Road data often have to be duplicated and flipped to make it directed."""
|
|
187
192
|
if self.percent_bidirectional > 5:
|
|
188
193
|
return
|
|
@@ -202,15 +207,15 @@ class Network:
|
|
|
202
207
|
warnings.warn(mess, stacklevel=2)
|
|
203
208
|
|
|
204
209
|
@property
|
|
205
|
-
def percent_bidirectional(self):
|
|
210
|
+
def percent_bidirectional(self) -> float:
|
|
206
211
|
"""The percentage of lines that appear in both directions."""
|
|
207
212
|
return self._percent_bidirectional
|
|
208
213
|
|
|
209
|
-
def copy(self):
|
|
214
|
+
def copy(self) -> "Network":
|
|
210
215
|
"""Returns a shallow copy of the class instance."""
|
|
211
216
|
return copy(self)
|
|
212
217
|
|
|
213
|
-
def deepcopy(self):
|
|
218
|
+
def deepcopy(self) -> "Network":
|
|
214
219
|
"""Returns a deep copy of the class instance."""
|
|
215
220
|
return deepcopy(self)
|
|
216
221
|
|
|
@@ -220,9 +225,6 @@ class Network:
|
|
|
220
225
|
km = int(sum(self.gdf.length) / 1000)
|
|
221
226
|
return f"{cl}({km} km, percent_bidirectional={self._percent_bidirectional})"
|
|
222
227
|
|
|
223
|
-
def
|
|
224
|
-
"""
|
|
225
|
-
return iter(self.__dict__.items())
|
|
226
|
-
|
|
227
|
-
def __len__(self):
|
|
228
|
+
def __len__(self) -> int:
|
|
229
|
+
"""Number og rows in the GeoDataFrame."""
|
|
228
230
|
return len(self.gdf)
|
|
@@ -4,22 +4,27 @@ The class has five analysis methods: od_cost_matrix, get_route, get_k_routes,
|
|
|
4
4
|
get_route_frequencies and service_area.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from copy import
|
|
7
|
+
from copy import copy
|
|
8
|
+
from copy import deepcopy
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from time import perf_counter
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
import igraph
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
15
16
|
from geopandas import GeoDataFrame
|
|
16
17
|
from igraph import Graph
|
|
17
|
-
from pandas import DataFrame
|
|
18
|
+
from pandas import DataFrame
|
|
19
|
+
from pandas import MultiIndex
|
|
18
20
|
|
|
19
21
|
from ..geopandas_tools.general import _push_geom_col
|
|
20
|
-
from ._get_route import _get_k_routes
|
|
22
|
+
from ._get_route import _get_k_routes
|
|
23
|
+
from ._get_route import _get_route
|
|
24
|
+
from ._get_route import _get_route_frequencies
|
|
21
25
|
from ._od_cost_matrix import _od_cost_matrix
|
|
22
|
-
from ._points import Destinations
|
|
26
|
+
from ._points import Destinations
|
|
27
|
+
from ._points import Origins
|
|
23
28
|
from ._service_area import _service_area
|
|
24
29
|
from .cutting_lines import split_lines_by_nearest_point
|
|
25
30
|
from .network import Network
|
|
@@ -46,25 +51,13 @@ class NetworkAnalysis:
|
|
|
46
51
|
line segments that were visited with an added column for how many times the
|
|
47
52
|
segments were used.
|
|
48
53
|
|
|
49
|
-
Args:
|
|
50
|
-
network: A GeoDataFrame of line geometries.
|
|
51
|
-
rules: The rules for the analysis, either as an instance of
|
|
52
|
-
NetworkAnalysisRules or a dictionary with the parameters
|
|
53
|
-
as keys.
|
|
54
|
-
log: If True (default), a DataFrame with information about each
|
|
55
|
-
analysis run will be stored in the 'log' attribute.
|
|
56
|
-
detailed_log: If True, the log DataFrame will include columns for
|
|
57
|
-
all arguments passed to the analysis method, plus standard deviation and
|
|
58
|
-
percentiles (25th, 50th, 75th) of the weight column in the results.
|
|
59
|
-
Defaults to False.
|
|
60
|
-
|
|
61
54
|
Attributes:
|
|
62
55
|
network: A Network instance that holds the lines and nodes (points).
|
|
63
56
|
rules: NetworkAnalysisRules instance.
|
|
64
57
|
log: A DataFrame with information about each analysis run.
|
|
65
58
|
|
|
66
|
-
Examples
|
|
67
|
-
|
|
59
|
+
Examples:
|
|
60
|
+
---------
|
|
68
61
|
Read example data.
|
|
69
62
|
|
|
70
63
|
>>> import sgis as sg
|
|
@@ -79,13 +72,15 @@ class NetworkAnalysis:
|
|
|
79
72
|
... direction_col="oneway",
|
|
80
73
|
... direction_vals_bft=("B", "FT", "TF"),
|
|
81
74
|
... minute_cols=("drivetime_fw", "drivetime_bw"),
|
|
75
|
+
... dropnegative=True,
|
|
76
|
+
... dropna=True,
|
|
82
77
|
... )
|
|
83
78
|
|
|
84
|
-
>>> rules = sg.NetworkAnalysisRules(weight="minutes")
|
|
79
|
+
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
85
80
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
86
81
|
>>> nwa
|
|
87
82
|
NetworkAnalysis(
|
|
88
|
-
network=
|
|
83
|
+
network=Network(6364 km, percent_bidirectional=87),
|
|
89
84
|
rules=NetworkAnalysisRules(weight=minutes, directed=True, search_tolerance=250, search_factor=0, split_lines=False, ...),
|
|
90
85
|
log=True, detailed_log=True,
|
|
91
86
|
)
|
|
@@ -100,7 +95,22 @@ class NetworkAnalysis:
|
|
|
100
95
|
rules: NetworkAnalysisRules | dict,
|
|
101
96
|
log: bool = True,
|
|
102
97
|
detailed_log: bool = False,
|
|
103
|
-
):
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Initialise NetworkAnalysis instance.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
network: A GeoDataFrame of line geometries.
|
|
103
|
+
rules: The rules for the analysis, either as an instance of
|
|
104
|
+
NetworkAnalysisRules or a dictionary with the parameters
|
|
105
|
+
as keys.
|
|
106
|
+
log: If True (default), a DataFrame with information about each
|
|
107
|
+
analysis run will be stored in the 'log' attribute.
|
|
108
|
+
detailed_log: If True, the log DataFrame will include columns for
|
|
109
|
+
all arguments passed to the analysis method, plus standard deviation and
|
|
110
|
+
percentiles (25th, 50th, 75th) of the weight column in the results.
|
|
111
|
+
Defaults to False.
|
|
112
|
+
|
|
113
|
+
"""
|
|
104
114
|
if not isinstance(rules, NetworkAnalysisRules):
|
|
105
115
|
rules = NetworkAnalysisRules(**rules)
|
|
106
116
|
|
|
@@ -125,8 +135,8 @@ class NetworkAnalysis:
|
|
|
125
135
|
self._graph_updated_count = 0
|
|
126
136
|
self._k_nearest_points = 50
|
|
127
137
|
|
|
128
|
-
def _check_if_holes_are_nan(self):
|
|
129
|
-
|
|
138
|
+
def _check_if_holes_are_nan(self) -> None:
|
|
139
|
+
holes_are_nan: str = (
|
|
130
140
|
"Network holes have been filled by straigt lines, but the rows have "
|
|
131
141
|
f"NaN values in the {self.rules.weight!r} column. Either remove NaNs "
|
|
132
142
|
"or fill these values with a numeric value (e.g. 0)."
|
|
@@ -140,7 +150,7 @@ class NetworkAnalysis:
|
|
|
140
150
|
.all()
|
|
141
151
|
)
|
|
142
152
|
):
|
|
143
|
-
raise ValueError(
|
|
153
|
+
raise ValueError(holes_are_nan)
|
|
144
154
|
|
|
145
155
|
def od_cost_matrix(
|
|
146
156
|
self,
|
|
@@ -180,13 +190,13 @@ class NetworkAnalysis:
|
|
|
180
190
|
GeoDataFrames. If lines is True, also returns a geometry column with
|
|
181
191
|
straight lines between origin and destination.
|
|
182
192
|
|
|
183
|
-
Examples
|
|
184
|
-
|
|
193
|
+
Examples:
|
|
194
|
+
---------
|
|
185
195
|
Create the NetworkAnalysis instance.
|
|
186
196
|
|
|
187
197
|
>>> import sgis as sg
|
|
188
198
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
189
|
-
>>> directed_roads = sg.
|
|
199
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
190
200
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
191
201
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
192
202
|
|
|
@@ -423,8 +433,8 @@ class NetworkAnalysis:
|
|
|
423
433
|
destinations: GeoDataFrame,
|
|
424
434
|
weight_df: DataFrame | None = None,
|
|
425
435
|
default_weight: int | float | None = None,
|
|
426
|
-
strict: bool = False,
|
|
427
436
|
rowwise: bool = False,
|
|
437
|
+
strict: bool = False,
|
|
428
438
|
frequency_col: str = "frequency",
|
|
429
439
|
) -> GeoDataFrame:
|
|
430
440
|
"""Finds the number of times each line segment was visited in all trips.
|
|
@@ -446,6 +456,11 @@ class NetworkAnalysis:
|
|
|
446
456
|
index, destination index and weight. In that order) or only a weight
|
|
447
457
|
column and a MultiIndex where level 0 is origin index and level 1 is
|
|
448
458
|
destination index.
|
|
459
|
+
default_weight: If set, OD pairs not represented in 'weight_df'
|
|
460
|
+
will be given a default weight value.
|
|
461
|
+
rowwise: if False (default), it will calculate the cost from each
|
|
462
|
+
origins to each destination. If true, it will calculate the cost from
|
|
463
|
+
origin 1 to destination 1, origin 2 to destination 2 and so on.
|
|
449
464
|
strict: If True, all OD pairs must be in weigth_df if specified. Defaults
|
|
450
465
|
to False.
|
|
451
466
|
frequency_col: Name of column with the number of times each road was
|
|
@@ -463,14 +478,14 @@ class NetworkAnalysis:
|
|
|
463
478
|
ValueError: If weight_df is not a DataFrame with one or three columns
|
|
464
479
|
that contain weights and all indices of 'origins' and 'destinations'.
|
|
465
480
|
|
|
466
|
-
Examples
|
|
467
|
-
|
|
481
|
+
Examples:
|
|
482
|
+
---------
|
|
468
483
|
Create the NetworkAnalysis instance.
|
|
469
484
|
|
|
470
485
|
>>> import sgis as sg
|
|
471
486
|
>>> import pandas as pd
|
|
472
487
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
473
|
-
>>> directed_roads = sg.
|
|
488
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
474
489
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
475
490
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
476
491
|
|
|
@@ -599,10 +614,13 @@ class NetworkAnalysis:
|
|
|
599
614
|
# map to temporary ids
|
|
600
615
|
ori_idx_mapper = {v: k for k, v in self.origins.idx_dict.items()}
|
|
601
616
|
des_idx_mapper = {v: k for k, v in self.destinations.idx_dict.items()}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
617
|
+
|
|
618
|
+
def multiindex_mapper(x: tuple[int, int]) -> tuple[int, int]:
|
|
619
|
+
return (
|
|
620
|
+
ori_idx_mapper.get(x[0]),
|
|
621
|
+
des_idx_mapper.get(x[1]),
|
|
622
|
+
)
|
|
623
|
+
|
|
606
624
|
weight_df.index = weight_df.index.map(multiindex_mapper)
|
|
607
625
|
else:
|
|
608
626
|
od_pairs = self._create_od_pairs(
|
|
@@ -673,13 +691,13 @@ class NetworkAnalysis:
|
|
|
673
691
|
Also returns a weight column and the columns 'origin' and 'destination',
|
|
674
692
|
containing the indices of the origins and destinations GeoDataFrames.
|
|
675
693
|
|
|
676
|
-
Examples
|
|
677
|
-
|
|
694
|
+
Examples:
|
|
695
|
+
---------
|
|
678
696
|
Create the NetworkAnalysis instance.
|
|
679
697
|
|
|
680
698
|
>>> import sgis as sg
|
|
681
699
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
682
|
-
>>> directed_roads = sg.
|
|
700
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
683
701
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
684
702
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
685
703
|
|
|
@@ -801,13 +819,13 @@ class NetworkAnalysis:
|
|
|
801
819
|
Raises:
|
|
802
820
|
ValueError: if drop_middle_percent is not between 0 and 100.
|
|
803
821
|
|
|
804
|
-
Examples
|
|
805
|
-
|
|
822
|
+
Examples:
|
|
823
|
+
---------
|
|
806
824
|
Create the NetworkAnalysis instance.
|
|
807
825
|
|
|
808
826
|
>>> import sgis as sg
|
|
809
827
|
>>> roads = sg.read_parquet_url('https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet')
|
|
810
|
-
>>> directed_roads = sg.
|
|
828
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
811
829
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
812
830
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
813
831
|
|
|
@@ -943,17 +961,17 @@ class NetworkAnalysis:
|
|
|
943
961
|
a dissolved line geometry. If dissolve is False, it will return each line
|
|
944
962
|
that is part of the service area.
|
|
945
963
|
|
|
946
|
-
See
|
|
964
|
+
See Also:
|
|
947
965
|
precice_service_area: Equivelent method where lines are also cut to get
|
|
948
966
|
precice results.
|
|
949
967
|
|
|
950
|
-
Examples
|
|
951
|
-
|
|
968
|
+
Examples:
|
|
969
|
+
---------
|
|
952
970
|
Create the NetworkAnalysis instance.
|
|
953
971
|
|
|
954
972
|
>>> import sgis as sg
|
|
955
973
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
956
|
-
>>> directed_roads = sg.
|
|
974
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
957
975
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
958
976
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
959
977
|
|
|
@@ -972,7 +990,6 @@ class NetworkAnalysis:
|
|
|
972
990
|
|
|
973
991
|
Service areas of 5, 10 and 15 minutes from three origin points.
|
|
974
992
|
|
|
975
|
-
>>> nwa = NetworkAnalysis(network=nw, rules=rules)
|
|
976
993
|
>>> service_areas = nwa.service_area(
|
|
977
994
|
... points.iloc[:2],
|
|
978
995
|
... breaks=[5, 10, 15],
|
|
@@ -1072,16 +1089,16 @@ class NetworkAnalysis:
|
|
|
1072
1089
|
geometry. If dissolve is False, it will return all the columns of the
|
|
1073
1090
|
network.gdf as well.
|
|
1074
1091
|
|
|
1075
|
-
See
|
|
1092
|
+
See Also:
|
|
1076
1093
|
service_area: Faster method where lines are not cut to get precice results.
|
|
1077
1094
|
|
|
1078
|
-
Examples
|
|
1079
|
-
|
|
1095
|
+
Examples:
|
|
1096
|
+
---------
|
|
1080
1097
|
Create the NetworkAnalysis instance.
|
|
1081
1098
|
|
|
1082
1099
|
>>> import sgis as sg
|
|
1083
1100
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
1084
|
-
>>> directed_roads = sg.
|
|
1101
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
1085
1102
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
1086
1103
|
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=False)
|
|
1087
1104
|
|
|
@@ -1099,7 +1116,6 @@ class NetworkAnalysis:
|
|
|
1099
1116
|
|
|
1100
1117
|
Service areas of 5, 10 and 15 minutes from three origin points.
|
|
1101
1118
|
|
|
1102
|
-
>>> nwa = NetworkAnalysis(network=nw, rules=rules)
|
|
1103
1119
|
>>> sa = nwa.precice_service_area(
|
|
1104
1120
|
... points.iloc[:2],
|
|
1105
1121
|
... breaks=[5, 10, 15],
|
|
@@ -1189,7 +1205,7 @@ class NetworkAnalysis:
|
|
|
1189
1205
|
"the trip frequency for this origin-destination pair."
|
|
1190
1206
|
)
|
|
1191
1207
|
|
|
1192
|
-
if not isinstance(weight_df, (DataFrame
|
|
1208
|
+
if not isinstance(weight_df, (DataFrame | pd.Series)):
|
|
1193
1209
|
raise ValueError(error_message)
|
|
1194
1210
|
|
|
1195
1211
|
if isinstance(weight_df, pd.Series):
|
|
@@ -1221,7 +1237,7 @@ class NetworkAnalysis:
|
|
|
1221
1237
|
def _make_sure_index_match(
|
|
1222
1238
|
weight_df: DataFrame,
|
|
1223
1239
|
od_pairs: MultiIndex,
|
|
1224
|
-
):
|
|
1240
|
+
) -> None:
|
|
1225
1241
|
"""Make sure this index matches the index of origins and destinations."""
|
|
1226
1242
|
if not od_pairs.isin(weight_df.index).all():
|
|
1227
1243
|
if not od_pairs.isin(weight_df.index).any():
|
|
@@ -1321,7 +1337,7 @@ class NetworkAnalysis:
|
|
|
1321
1337
|
for key, value in kwargs.items():
|
|
1322
1338
|
if isinstance(value, np.ndarray):
|
|
1323
1339
|
value = list(value)
|
|
1324
|
-
if isinstance(value, (list
|
|
1340
|
+
if isinstance(value, (list | tuple)):
|
|
1325
1341
|
value = [str(x) for x in value]
|
|
1326
1342
|
value = ", ".join(value)
|
|
1327
1343
|
df[key] = value
|
|
@@ -1329,7 +1345,10 @@ class NetworkAnalysis:
|
|
|
1329
1345
|
self.log = pd.concat([self.log, df], ignore_index=True)
|
|
1330
1346
|
|
|
1331
1347
|
def _prepare_network_analysis(
|
|
1332
|
-
self,
|
|
1348
|
+
self,
|
|
1349
|
+
origins: GeoDataFrame,
|
|
1350
|
+
destinations: GeoDataFrame | None = None,
|
|
1351
|
+
rowwise: bool = False,
|
|
1333
1352
|
) -> None:
|
|
1334
1353
|
"""Prepares the weight column, node ids, origins, destinations and graph.
|
|
1335
1354
|
|
|
@@ -1337,7 +1356,6 @@ class NetworkAnalysis:
|
|
|
1337
1356
|
has changed. this method is run inside od_cost_matrix, get_route and
|
|
1338
1357
|
service_area.
|
|
1339
1358
|
"""
|
|
1340
|
-
|
|
1341
1359
|
if rowwise and len(origins) != len(destinations):
|
|
1342
1360
|
raise ValueError(
|
|
1343
1361
|
"'origins' and 'destinations' must have the same length when "
|
|
@@ -1585,14 +1603,14 @@ class NetworkAnalysis:
|
|
|
1585
1603
|
]
|
|
1586
1604
|
|
|
1587
1605
|
@staticmethod
|
|
1588
|
-
def _sort_breaks(breaks) -> list[float | int]:
|
|
1606
|
+
def _sort_breaks(breaks: str | list | tuple | int | float) -> list[float | int]:
|
|
1589
1607
|
if isinstance(breaks, str):
|
|
1590
1608
|
breaks = float(breaks)
|
|
1591
1609
|
|
|
1592
1610
|
if hasattr(breaks, "__iter__"):
|
|
1593
1611
|
return list(sorted(list(breaks)))
|
|
1594
1612
|
|
|
1595
|
-
if isinstance(breaks, (int
|
|
1613
|
+
if isinstance(breaks, (int | float)):
|
|
1596
1614
|
return [breaks]
|
|
1597
1615
|
|
|
1598
1616
|
raise ValueError(
|
|
@@ -1628,11 +1646,11 @@ class NetworkAnalysis:
|
|
|
1628
1646
|
"\n)"
|
|
1629
1647
|
)
|
|
1630
1648
|
|
|
1631
|
-
def __getitem__(self, item):
|
|
1649
|
+
def __getitem__(self, item: str) -> Any:
|
|
1632
1650
|
"""To be able to write self['origins'] as well as self.origins."""
|
|
1633
1651
|
return getattr(self, item)
|
|
1634
1652
|
|
|
1635
|
-
def copy(self, deep=True):
|
|
1653
|
+
def copy(self, deep: bool = True) -> "NetworkAnalysis":
|
|
1636
1654
|
"""Returns a (deep) copy of the class instance.
|
|
1637
1655
|
|
|
1638
1656
|
Args:
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
The class is to be used as the 'rules' parameter in the NetworkAnalysis
|
|
4
4
|
class.
|
|
5
5
|
"""
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
from copy import copy
|
|
8
|
+
from copy import deepcopy
|
|
7
9
|
from dataclasses import dataclass
|
|
8
10
|
|
|
9
11
|
from geopandas import GeoDataFrame
|
|
@@ -20,7 +22,7 @@ class NetworkAnalysisRules:
|
|
|
20
22
|
Args:
|
|
21
23
|
weight: Either a column in the GeoDataFrame of the Network or
|
|
22
24
|
'meters'/'metres'. A 'minutes' column can be created with the
|
|
23
|
-
'make_directed_network'
|
|
25
|
+
'make_directed_network' or 'make_directed_network_norway' functions.
|
|
24
26
|
directed: Whether the lines will be considered traversable in both directions.
|
|
25
27
|
search_tolerance: distance to search for nodes in the network. Origins and
|
|
26
28
|
destinations further away from the network than the search_tolerance will
|
|
@@ -47,8 +49,8 @@ class NetworkAnalysisRules:
|
|
|
47
49
|
them. Defaults to None, meaning 0 weight is added for these edges. If set
|
|
48
50
|
to 1, the weight will be equal to the straigt line distance.
|
|
49
51
|
|
|
50
|
-
Examples
|
|
51
|
-
|
|
52
|
+
Examples:
|
|
53
|
+
---------
|
|
52
54
|
Read testdata.
|
|
53
55
|
|
|
54
56
|
>>> import sgis as sg
|
|
@@ -59,12 +61,12 @@ class NetworkAnalysisRules:
|
|
|
59
61
|
values.
|
|
60
62
|
|
|
61
63
|
>>> rules = sg.NetworkAnalysisRules(weight="minutes", directed=True)
|
|
62
|
-
>>> directed_roads = sg.
|
|
63
|
-
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=
|
|
64
|
+
>>> directed_roads = sg.get_connected_components(roads).loc[lambda x: x["connected"] == 1].pipe(sg.make_directed_network_norway, dropnegative=True)
|
|
65
|
+
>>> nwa = sg.NetworkAnalysis(network=directed_roads, rules=rules, detailed_log=True)
|
|
64
66
|
>>> nwa
|
|
65
67
|
NetworkAnalysis(
|
|
66
|
-
network=
|
|
67
|
-
rules=NetworkAnalysisRules(weight=minutes, search_tolerance=250, search_factor=0, split_lines=
|
|
68
|
+
network=Network(6364 km, percent_bidirectional=87),
|
|
69
|
+
rules=NetworkAnalysisRules(weight=minutes, directed=True, search_tolerance=250, search_factor=0, split_lines=True, ...),
|
|
68
70
|
log=True, detailed_log=True,
|
|
69
71
|
)
|
|
70
72
|
|
|
@@ -171,7 +173,7 @@ class NetworkAnalysisRules:
|
|
|
171
173
|
nodedist_multiplier: int | float | None = None
|
|
172
174
|
nodedist_kmh: int | float | None = None
|
|
173
175
|
|
|
174
|
-
def _update_rules(self):
|
|
176
|
+
def _update_rules(self) -> None:
|
|
175
177
|
"""Stores the rules as separate attributes.
|
|
176
178
|
|
|
177
179
|
Used for checking whether the rules have changed and the graph have to be
|
|
@@ -185,7 +187,7 @@ class NetworkAnalysisRules:
|
|
|
185
187
|
self._nodedist_multiplier = self.nodedist_multiplier
|
|
186
188
|
self._nodedist_kmh = self.nodedist_kmh
|
|
187
189
|
|
|
188
|
-
def _rules_have_changed(self):
|
|
190
|
+
def _rules_have_changed(self) -> bool:
|
|
189
191
|
"""Checks if any of the rules have changed since the graph was last created.
|
|
190
192
|
|
|
191
193
|
If no rules have changed, time can be saved by not remaking the graph
|
|
@@ -235,8 +237,8 @@ class NetworkAnalysisRules:
|
|
|
235
237
|
self._check_for_negative_values(gdf, self.weight)
|
|
236
238
|
return gdf
|
|
237
239
|
|
|
238
|
-
# at this point, the weight is wrong.
|
|
239
|
-
# message
|
|
240
|
+
# at this point, the weight is wrong.
|
|
241
|
+
# Now to determine the error message
|
|
240
242
|
|
|
241
243
|
if "meter" in self.weight or "metre" in self.weight:
|
|
242
244
|
raise ValueError(
|
|
@@ -257,7 +259,7 @@ class NetworkAnalysisRules:
|
|
|
257
259
|
raise KeyError(incorrect_weight_column)
|
|
258
260
|
|
|
259
261
|
@staticmethod
|
|
260
|
-
def _check_for_nans(df, col):
|
|
262
|
+
def _check_for_nans(df: GeoDataFrame, col: str) -> None:
|
|
261
263
|
"""Remove NaNs and give warning if there are any."""
|
|
262
264
|
if all(df[col].isna()):
|
|
263
265
|
raise ValueError(f"All values in the {col!r} column are NaN.")
|
|
@@ -270,7 +272,7 @@ class NetworkAnalysisRules:
|
|
|
270
272
|
)
|
|
271
273
|
|
|
272
274
|
@staticmethod
|
|
273
|
-
def _check_for_negative_values(df, col):
|
|
275
|
+
def _check_for_negative_values(df: GeoDataFrame, col: str) -> None:
|
|
274
276
|
"""Remove negative values and give warning if there are any."""
|
|
275
277
|
negative = sum(df[col] < 0)
|
|
276
278
|
if negative:
|
|
@@ -280,7 +282,7 @@ class NetworkAnalysisRules:
|
|
|
280
282
|
)
|
|
281
283
|
|
|
282
284
|
@staticmethod
|
|
283
|
-
def _try_to_float(df, col):
|
|
285
|
+
def _try_to_float(df: GeoDataFrame, col: str) -> GeoDataFrame:
|
|
284
286
|
"""Try to convert weight column to float, raise ValueError if it fails."""
|
|
285
287
|
try:
|
|
286
288
|
df[col] = df[col].astype(float)
|
|
@@ -291,8 +293,10 @@ class NetworkAnalysisRules:
|
|
|
291
293
|
) from e
|
|
292
294
|
return df
|
|
293
295
|
|
|
294
|
-
def copy(self):
|
|
296
|
+
def copy(self) -> "NetworkAnalysisRules":
|
|
297
|
+
"""Return a shallow copy the instance."""
|
|
295
298
|
return copy(self)
|
|
296
299
|
|
|
297
|
-
def deepcopy(self):
|
|
300
|
+
def deepcopy(self) -> "NetworkAnalysisRules":
|
|
301
|
+
"""Return a deep copy the instance."""
|
|
298
302
|
return deepcopy(self)
|
sgis/networkanalysis/nodes.py
CHANGED
|
@@ -6,7 +6,8 @@ to use explicitly.
|
|
|
6
6
|
|
|
7
7
|
import geopandas as gpd
|
|
8
8
|
import pandas as pd
|
|
9
|
-
from geopandas import GeoDataFrame
|
|
9
|
+
from geopandas import GeoDataFrame
|
|
10
|
+
from geopandas import GeoSeries
|
|
10
11
|
from shapely.geometry import Point
|
|
11
12
|
|
|
12
13
|
from ..geopandas_tools.general import _push_geom_col
|
|
@@ -34,7 +35,6 @@ def make_node_ids(
|
|
|
34
35
|
Note:
|
|
35
36
|
The lines must be singlepart linestrings.
|
|
36
37
|
"""
|
|
37
|
-
|
|
38
38
|
gdf = gdf.explode(index_parts=False).explode(index_parts=False)
|
|
39
39
|
|
|
40
40
|
if wkt:
|
|
@@ -192,7 +192,6 @@ def _prepare_make_edge_cols_simple(
|
|
|
192
192
|
lines: GeoDataFrame,
|
|
193
193
|
) -> tuple[GeoDataFrame, GeoDataFrame]:
|
|
194
194
|
"""Faster version of _prepare_make_edge_cols."""
|
|
195
|
-
|
|
196
195
|
endpoints = lines[lines._geometry_column_name].boundary.explode(ignore_index=True)
|
|
197
196
|
|
|
198
197
|
if len(lines) and len(endpoints) / len(lines) != 2:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import networkx as nx
|
|
2
2
|
import pandas as pd
|
|
3
|
-
from geopandas import GeoDataFrame
|
|
3
|
+
from geopandas import GeoDataFrame
|
|
4
|
+
from geopandas import GeoSeries
|
|
4
5
|
from networkx.utils import pairwise
|
|
5
6
|
from shapely.geometry import Point
|
|
6
7
|
|
|
@@ -30,8 +31,10 @@ def traveling_salesman_problem(
|
|
|
30
31
|
Returns:
|
|
31
32
|
List of Points making up the traveling salesman's path.
|
|
32
33
|
|
|
33
|
-
Examples
|
|
34
|
-
|
|
34
|
+
Examples:
|
|
35
|
+
---------
|
|
36
|
+
>>> import sgis as sg
|
|
37
|
+
>>> from shapely.geometry import LineString
|
|
35
38
|
>>> points = sg.to_gdf(
|
|
36
39
|
... [
|
|
37
40
|
... (0, 0),
|