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.
- sgis/__init__.py +97 -115
- sgis/exceptions.py +3 -1
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +75 -38
- sgis/geopandas_tools/buffer_dissolve_explode.py +38 -34
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +149 -101
- sgis/geopandas_tools/duplicates.py +31 -17
- sgis/geopandas_tools/general.py +76 -48
- sgis/geopandas_tools/geometry_types.py +21 -7
- sgis/geopandas_tools/neighbors.py +20 -8
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +9 -8
- sgis/geopandas_tools/polygon_operations.py +48 -56
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +14 -14
- sgis/helpers.py +114 -56
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +1 -1
- sgis/maps/examine.py +39 -26
- sgis/maps/explore.py +112 -66
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +124 -65
- sgis/maps/map.py +66 -41
- sgis/maps/maps.py +31 -29
- sgis/maps/thematicmap.py +46 -33
- 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 +20 -62
- sgis/networkanalysis/cutting_lines.py +55 -43
- sgis/networkanalysis/directednetwork.py +15 -7
- sgis/networkanalysis/finding_isolated_networks.py +4 -3
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +72 -54
- sgis/networkanalysis/networkanalysisrules.py +20 -16
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +5 -2
- sgis/parallel/parallel.py +337 -127
- sgis/raster/__init__.py +6 -0
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +280 -208
- sgis/raster/cubebase.py +15 -29
- sgis/raster/indices.py +3 -7
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/raster.py +313 -127
- sgis/raster/torchgeo.py +58 -37
- sgis/raster/zonal.py +38 -13
- {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +89 -18
- ssb_sgis-1.0.2.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- 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
|
|
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
|
|
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:
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
482
|
-
if isinstance(
|
|
483
|
-
gdf = clean_geoms(to_gdf_func(
|
|
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(
|
|
488
|
+
elif isinstance(val, dict) or hasattr(val, "__dict__"):
|
|
488
489
|
try:
|
|
489
|
-
for k, v in
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
"""
|
|
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(
|
|
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:
|
|
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
|
|
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='© <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='© <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='© <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='© <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='© <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='© <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='© <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(
|
|
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)
|
sgis/networkanalysis/_points.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
6
|
-
from shapely
|
|
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
|
|