ssb-sgis 1.2.3__py3-none-any.whl → 1.2.6__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 +1 -0
- sgis/geopandas_tools/cleaning.py +0 -1
- sgis/geopandas_tools/conversion.py +0 -27
- sgis/geopandas_tools/neighbors.py +2 -1
- sgis/geopandas_tools/runners.py +47 -0
- sgis/io/dapla_functions.py +31 -9
- sgis/maps/explore.py +6 -12
- sgis/maps/httpserver.py +2 -1
- sgis/maps/map.py +15 -14
- sgis/maps/maps.py +1 -8
- sgis/maps/thematicmap.py +1 -3
- sgis/maps/wms.py +2 -1
- sgis/networkanalysis/_get_route.py +83 -37
- sgis/networkanalysis/networkanalysis.py +6 -0
- sgis/parallel/parallel.py +3 -3
- sgis/raster/image_collection.py +13 -168
- sgis/raster/regex.py +2 -2
- {ssb_sgis-1.2.3.dist-info → ssb_sgis-1.2.6.dist-info}/METADATA +1 -1
- {ssb_sgis-1.2.3.dist-info → ssb_sgis-1.2.6.dist-info}/RECORD +21 -21
- {ssb_sgis-1.2.3.dist-info → ssb_sgis-1.2.6.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.2.3.dist-info → ssb_sgis-1.2.6.dist-info}/WHEEL +0 -0
sgis/__init__.py
CHANGED
|
@@ -78,6 +78,7 @@ from .geopandas_tools.polygon_operations import get_polygon_clusters
|
|
|
78
78
|
from .geopandas_tools.polygon_operations import split_polygons_by_lines
|
|
79
79
|
from .geopandas_tools.polygons_as_rings import PolygonsAsRings
|
|
80
80
|
from .geopandas_tools.runners import GridSizeOverlayRunner
|
|
81
|
+
from .geopandas_tools.runners import GridSizeUnionRunner
|
|
81
82
|
from .geopandas_tools.runners import OverlayRunner
|
|
82
83
|
from .geopandas_tools.runners import RTreeQueryRunner
|
|
83
84
|
from .geopandas_tools.runners import UnionRunner
|
sgis/geopandas_tools/cleaning.py
CHANGED
|
@@ -43,14 +43,6 @@ except ImportError:
|
|
|
43
43
|
"""Placeholder."""
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
try:
|
|
47
|
-
from torchgeo.datasets.geo import RasterDataset
|
|
48
|
-
except ImportError:
|
|
49
|
-
|
|
50
|
-
class RasterDataset: # type: ignore
|
|
51
|
-
"""Placeholder."""
|
|
52
|
-
|
|
53
|
-
|
|
54
46
|
def crs_to_string(crs: Any) -> str:
|
|
55
47
|
"""Extract the string of a CRS-like object."""
|
|
56
48
|
if crs is None:
|
|
@@ -415,21 +407,6 @@ def to_gdf(
|
|
|
415
407
|
except Exception:
|
|
416
408
|
pass
|
|
417
409
|
|
|
418
|
-
if isinstance(obj, RasterDataset):
|
|
419
|
-
# read the entire dataset
|
|
420
|
-
obj = obj[obj.bounds]
|
|
421
|
-
crs = obj["crs"]
|
|
422
|
-
array = np.array(obj["image"])
|
|
423
|
-
transform = get_transform_from_bounds(obj["bbox"], shape=array.shape)
|
|
424
|
-
return gpd.GeoDataFrame(
|
|
425
|
-
pd.DataFrame(
|
|
426
|
-
_array_to_geojson(array, transform),
|
|
427
|
-
columns=["value", "geometry"],
|
|
428
|
-
),
|
|
429
|
-
geometry="geometry",
|
|
430
|
-
crs=crs,
|
|
431
|
-
)
|
|
432
|
-
|
|
433
410
|
if is_array_like(geometry) and len(geometry) == len(obj): # type: ignore
|
|
434
411
|
geometry = GeoSeries(
|
|
435
412
|
_make_one_shapely_geom(g) for g in geometry if g is not None # type: ignore
|
|
@@ -442,10 +419,6 @@ def to_gdf(
|
|
|
442
419
|
# get done with iterators that would get consumed by 'all' later
|
|
443
420
|
if isinstance(obj, Iterator) and not isinstance(obj, Sized):
|
|
444
421
|
obj = list(obj)
|
|
445
|
-
# obj = GeoSeries(
|
|
446
|
-
# (_make_one_shapely_geom(g) for g in obj if g is not None), index=index
|
|
447
|
-
# )
|
|
448
|
-
# return GeoDataFrame({geom_col: obj}, geometry=geom_col, crs=crs, **kwargs)
|
|
449
422
|
|
|
450
423
|
if hasattr(obj, "__len__") and not len(obj):
|
|
451
424
|
return GeoDataFrame({"geometry": []}, crs=crs)
|
|
@@ -15,7 +15,6 @@ from geopandas import GeoSeries
|
|
|
15
15
|
from pandas import DataFrame
|
|
16
16
|
from pandas import MultiIndex
|
|
17
17
|
from pandas import Series
|
|
18
|
-
from sklearn.neighbors import NearestNeighbors
|
|
19
18
|
|
|
20
19
|
from ..conf import _get_instance
|
|
21
20
|
from ..conf import config
|
|
@@ -467,6 +466,8 @@ def k_nearest_neighbors(
|
|
|
467
466
|
of the neighbors.
|
|
468
467
|
|
|
469
468
|
"""
|
|
469
|
+
from sklearn.neighbors import NearestNeighbors
|
|
470
|
+
|
|
470
471
|
if not len(to_array) or not len(from_array):
|
|
471
472
|
return np.array([]), np.array([])
|
|
472
473
|
|
sgis/geopandas_tools/runners.py
CHANGED
|
@@ -120,6 +120,53 @@ class UnionRunner(AbstractRunner):
|
|
|
120
120
|
return agged
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
@dataclass
|
|
124
|
+
class GridSizeUnionRunner(UnionRunner):
|
|
125
|
+
"""Run shapely.union_all with pandas.groupby for different grid sizes until no GEOSException is raised.
|
|
126
|
+
|
|
127
|
+
Subclasses must implement a 'run' method that takes the arguments
|
|
128
|
+
'df' (GeoDataFrame or GeoSeries), 'by' (optional column to group by), 'grid_size'
|
|
129
|
+
(passed to shapely.union_all) and **kwargs passed to pandas.DataFrame.groupby.
|
|
130
|
+
Defaults to None, meaning the default runner with number of workers set
|
|
131
|
+
to 'n_jobs'.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
n_jobs: Number of workers.
|
|
136
|
+
backend: Backend for the workers.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
n_jobs: int
|
|
140
|
+
backend: str | None = None
|
|
141
|
+
grid_sizes: list[float | int] | None = None
|
|
142
|
+
|
|
143
|
+
def __post_init__(self) -> None:
|
|
144
|
+
"""Check that grid_sizes is passed."""
|
|
145
|
+
if self.grid_sizes is None:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"must set 'grid_sizes' in the {self.__class__.__name__} initialiser."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def run(
|
|
151
|
+
self,
|
|
152
|
+
df: GeoDataFrame | GeoSeries | pd.DataFrame | pd.Series,
|
|
153
|
+
by: str | list[str] | None = None,
|
|
154
|
+
grid_size: int | float | None = None,
|
|
155
|
+
**kwargs,
|
|
156
|
+
) -> GeoSeries | GeoDataFrame:
|
|
157
|
+
"""Run groupby on geometries in parallel (if n_jobs > 1) with grid_sizes."""
|
|
158
|
+
try:
|
|
159
|
+
return super().run(df, by=by, grid_size=grid_size, **kwargs)
|
|
160
|
+
except GEOSException:
|
|
161
|
+
pass
|
|
162
|
+
for i, grid_size in enumerate(self.grid_sizes):
|
|
163
|
+
try:
|
|
164
|
+
return super().run(df, by=by, grid_size=grid_size, **kwargs)
|
|
165
|
+
except GEOSException as e:
|
|
166
|
+
if i == len(self.grid_sizes) - 1:
|
|
167
|
+
raise e
|
|
168
|
+
|
|
169
|
+
|
|
123
170
|
def _strtree_query(
|
|
124
171
|
arr1: np.ndarray,
|
|
125
172
|
arr2: np.ndarray,
|
sgis/io/dapla_functions.py
CHANGED
|
@@ -40,7 +40,10 @@ from ..helpers import _get_file_system
|
|
|
40
40
|
try:
|
|
41
41
|
from gcsfs import GCSFileSystem
|
|
42
42
|
except ImportError:
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
class GCSFileSystem:
|
|
45
|
+
"""Placeholder."""
|
|
46
|
+
|
|
44
47
|
|
|
45
48
|
PANDAS_FALLBACK_INFO = " Set pandas_fallback=True to ignore this error."
|
|
46
49
|
NULL_VALUE = "__HIVE_DEFAULT_PARTITION__"
|
|
@@ -96,6 +99,7 @@ def read_geopandas(
|
|
|
96
99
|
file_system=file_system,
|
|
97
100
|
use_threads=use_threads,
|
|
98
101
|
pandas_fallback=pandas_fallback,
|
|
102
|
+
filters=filters,
|
|
99
103
|
**kwargs,
|
|
100
104
|
)
|
|
101
105
|
|
|
@@ -108,7 +112,9 @@ def read_geopandas(
|
|
|
108
112
|
# because glob is slow without GCSFileSystem from the root partition
|
|
109
113
|
if single_eq_filter:
|
|
110
114
|
try:
|
|
111
|
-
expression = "".join(
|
|
115
|
+
expression: list[str] = "".join(
|
|
116
|
+
[str(x) for x in next(iter(filters))]
|
|
117
|
+
).replace("==", "=")
|
|
112
118
|
glob_func = _get_glob_func(file_system)
|
|
113
119
|
suffix: str = Path(gcs_path).suffix
|
|
114
120
|
paths = glob_func(str(Path(gcs_path) / expression / f"*{suffix}"))
|
|
@@ -119,6 +125,7 @@ def read_geopandas(
|
|
|
119
125
|
file_system=file_system,
|
|
120
126
|
use_threads=use_threads,
|
|
121
127
|
pandas_fallback=pandas_fallback,
|
|
128
|
+
filters=filters,
|
|
122
129
|
**kwargs,
|
|
123
130
|
)
|
|
124
131
|
except FileNotFoundError:
|
|
@@ -178,11 +185,17 @@ def _read_geopandas_from_iterable(
|
|
|
178
185
|
except ArrowInvalid as e:
|
|
179
186
|
if file_system.isfile(path):
|
|
180
187
|
raise ArrowInvalid(e, path) from e
|
|
181
|
-
|
|
188
|
+
first_path = next(iter(paths.index))
|
|
189
|
+
_, crs = _get_bounds_parquet(first_path, file_system)
|
|
190
|
+
return GeoDataFrame(cols | {"geometry": []}, crs=crs)
|
|
182
191
|
paths = list(bounds_series.index)
|
|
183
192
|
|
|
184
193
|
results: list[pyarrow.Table] = _read_pyarrow_with_treads(
|
|
185
|
-
paths,
|
|
194
|
+
paths,
|
|
195
|
+
file_system=file_system,
|
|
196
|
+
mask=mask,
|
|
197
|
+
use_threads=use_threads,
|
|
198
|
+
**kwargs,
|
|
186
199
|
)
|
|
187
200
|
if results:
|
|
188
201
|
try:
|
|
@@ -192,16 +205,23 @@ def _read_geopandas_from_iterable(
|
|
|
192
205
|
print(e)
|
|
193
206
|
raise e
|
|
194
207
|
else:
|
|
195
|
-
|
|
208
|
+
first_path = next(iter(paths))
|
|
209
|
+
_, crs = _get_bounds_parquet(first_path, file_system)
|
|
210
|
+
df = GeoDataFrame(cols | {"geometry": []}, crs=crs)
|
|
196
211
|
|
|
197
212
|
return df
|
|
198
213
|
|
|
199
214
|
|
|
200
215
|
def _read_pyarrow_with_treads(
|
|
201
|
-
paths: list[str | Path | os.PathLike],
|
|
216
|
+
paths: list[str | Path | os.PathLike],
|
|
217
|
+
file_system,
|
|
218
|
+
use_threads,
|
|
219
|
+
mask,
|
|
220
|
+
filters,
|
|
221
|
+
**kwargs,
|
|
202
222
|
) -> list[pyarrow.Table]:
|
|
203
223
|
read_partial = functools.partial(
|
|
204
|
-
_read_pyarrow, mask=mask, file_system=file_system, **kwargs
|
|
224
|
+
_read_pyarrow, filters=filters, mask=mask, file_system=file_system, **kwargs
|
|
205
225
|
)
|
|
206
226
|
if not use_threads:
|
|
207
227
|
return [x for x in map(read_partial, paths) if x is not None]
|
|
@@ -645,7 +665,7 @@ def expression_match_path(expression: ds.Expression, path: str) -> bool:
|
|
|
645
665
|
"""Check if a file path match a pyarrow Expression.
|
|
646
666
|
|
|
647
667
|
Examples:
|
|
648
|
-
|
|
668
|
+
---------
|
|
649
669
|
>>> import pyarrow.compute as pc
|
|
650
670
|
>>> path = 'data/file.parquet/x=1/y=10/name0.parquet'
|
|
651
671
|
>>> expression = (pc.Field("x") == 1) & (pc.Field("y") == 10)
|
|
@@ -758,6 +778,7 @@ def _read_partitioned_parquet(
|
|
|
758
778
|
),
|
|
759
779
|
file_system=file_system,
|
|
760
780
|
mask=mask,
|
|
781
|
+
filters=filters,
|
|
761
782
|
use_threads=use_threads,
|
|
762
783
|
**kwargs,
|
|
763
784
|
)
|
|
@@ -769,7 +790,8 @@ def _read_partitioned_parquet(
|
|
|
769
790
|
|
|
770
791
|
# add columns to empty DataFrame
|
|
771
792
|
first_path = next(iter(child_paths + [path]))
|
|
772
|
-
|
|
793
|
+
_, crs = _get_bounds_parquet(first_path, file_system)
|
|
794
|
+
df = GeoDataFrame(columns=_get_columns(first_path, file_system), crs=crs)
|
|
773
795
|
if kwargs.get("columns"):
|
|
774
796
|
return df[list(kwargs["columns"])]
|
|
775
797
|
return df
|
sgis/maps/explore.py
CHANGED
|
@@ -69,14 +69,6 @@ from .map import _determine_best_name
|
|
|
69
69
|
from .tilesources import kartverket
|
|
70
70
|
from .tilesources import xyz
|
|
71
71
|
|
|
72
|
-
try:
|
|
73
|
-
from torchgeo.datasets.geo import RasterDataset
|
|
74
|
-
except ImportError:
|
|
75
|
-
|
|
76
|
-
class RasterDataset:
|
|
77
|
-
"""Placeholder."""
|
|
78
|
-
|
|
79
|
-
|
|
80
72
|
# the geopandas._explore raises a deprication warning. Ignoring for now.
|
|
81
73
|
warnings.filterwarnings(
|
|
82
74
|
action="ignore", category=matplotlib.MatplotlibDeprecationWarning
|
|
@@ -207,9 +199,9 @@ def _single_band_to_arr_is_too_much_nodata(
|
|
|
207
199
|
if band.has_array and mask is None:
|
|
208
200
|
arr = band.values
|
|
209
201
|
elif band.has_array:
|
|
210
|
-
arr = band.clip(mask).values
|
|
202
|
+
arr = band.copy().clip(mask).values
|
|
211
203
|
else:
|
|
212
|
-
arr = band.load(indexes=1, bounds=mask).values
|
|
204
|
+
arr = band.copy().load(indexes=1, bounds=mask).values
|
|
213
205
|
|
|
214
206
|
if _is_too_much_nodata([arr], band.nodata, max_nodata_percentage):
|
|
215
207
|
return True
|
|
@@ -618,6 +610,8 @@ class Explore(Map):
|
|
|
618
610
|
arr,
|
|
619
611
|
bounds=[[miny, minx], [maxy, maxx]],
|
|
620
612
|
show=self._show_rasters,
|
|
613
|
+
vmin=arr.min(),
|
|
614
|
+
vmax=arr.max(),
|
|
621
615
|
**kwargs,
|
|
622
616
|
)
|
|
623
617
|
image_overlay.layer_name = Path(label).stem
|
|
@@ -1399,9 +1393,9 @@ def _add_one_image(
|
|
|
1399
1393
|
def load(band_id: str) -> Band:
|
|
1400
1394
|
band = image[band_id]
|
|
1401
1395
|
if band.has_array and mask is not None:
|
|
1402
|
-
band = band.clip(mask, copy=True)
|
|
1396
|
+
band = band.copy().clip(mask, copy=True)
|
|
1403
1397
|
elif not band.has_array:
|
|
1404
|
-
band = band.load(indexes=1, bounds=mask)
|
|
1398
|
+
band = band.copy().load(indexes=1, bounds=mask)
|
|
1405
1399
|
return band
|
|
1406
1400
|
|
|
1407
1401
|
for red, blue, green in rbg_bands:
|
sgis/maps/httpserver.py
CHANGED
|
@@ -24,10 +24,11 @@ def run_html_server(contents: str | None = None, port: int = 3000) -> None:
|
|
|
24
24
|
if "JUPYTERHUB_SERVICE_PREFIX" in os.environ:
|
|
25
25
|
# Create a link using the https://github.com/jupyterhub/jupyter-server-proxy
|
|
26
26
|
display_address = os.environ["JUPYTERHUB_SERVICE_PREFIX"] + f"proxy/{port}/"
|
|
27
|
+
stop_address = os.environ["JUPYTERHUB_SERVICE_PREFIX"] + f"proxy/{port}/stop"
|
|
27
28
|
display_content = HTML(
|
|
28
29
|
f"""
|
|
29
30
|
<p>Click <a href='{display_address}'>here</a> to open in browser.</p>
|
|
30
|
-
<p>Click <a href='{
|
|
31
|
+
<p>Click <a href='{stop_address}'>here</a> to stop.</p>
|
|
31
32
|
"""
|
|
32
33
|
)
|
|
33
34
|
else:
|
sgis/maps/map.py
CHANGED
|
@@ -14,12 +14,12 @@ import numpy as np
|
|
|
14
14
|
import pandas as pd
|
|
15
15
|
from geopandas import GeoDataFrame
|
|
16
16
|
from geopandas import GeoSeries
|
|
17
|
+
from pandas.api.types import is_dict_like
|
|
17
18
|
|
|
18
19
|
try:
|
|
19
20
|
from jenkspy import jenks_breaks
|
|
20
21
|
except ImportError:
|
|
21
22
|
pass
|
|
22
|
-
from mapclassify import classify
|
|
23
23
|
from pandas.errors import PerformanceWarning
|
|
24
24
|
from shapely import Geometry
|
|
25
25
|
|
|
@@ -34,14 +34,6 @@ from ..raster.image_collection import Band
|
|
|
34
34
|
from ..raster.image_collection import Image
|
|
35
35
|
from ..raster.image_collection import ImageCollection
|
|
36
36
|
|
|
37
|
-
try:
|
|
38
|
-
from torchgeo.datasets.geo import RasterDataset
|
|
39
|
-
except ImportError:
|
|
40
|
-
|
|
41
|
-
class RasterDataset:
|
|
42
|
-
"""Placeholder."""
|
|
43
|
-
|
|
44
|
-
|
|
45
37
|
# the geopandas._explore raises a deprication warning. Ignoring for now.
|
|
46
38
|
warnings.filterwarnings(
|
|
47
39
|
action="ignore", category=matplotlib.MatplotlibDeprecationWarning
|
|
@@ -442,7 +434,6 @@ class Map:
|
|
|
442
434
|
GeoDataFrame,
|
|
443
435
|
GeoSeries,
|
|
444
436
|
Geometry,
|
|
445
|
-
RasterDataset,
|
|
446
437
|
ImageCollection,
|
|
447
438
|
Image,
|
|
448
439
|
Band,
|
|
@@ -605,22 +596,29 @@ class Map:
|
|
|
605
596
|
return False
|
|
606
597
|
|
|
607
598
|
def _make_categories_colors_dict(self) -> None:
|
|
608
|
-
|
|
609
|
-
|
|
599
|
+
if "color" in self.kwargs and is_dict_like(self.kwargs["color"]):
|
|
600
|
+
if self._column is None and not all(
|
|
601
|
+
key in self.kwargs["color"] for key in self._gdfs
|
|
602
|
+
):
|
|
603
|
+
raise ValueError(
|
|
604
|
+
"When specifying 'color' as dict-like, you must also pass a column "
|
|
605
|
+
"or all gdfs passed must have labels/names corresponding to keys in the color dict."
|
|
606
|
+
)
|
|
607
|
+
self._categories_colors_dict = self.kwargs.pop("color")
|
|
608
|
+
elif not self._cmap and len(self._unique_values) <= len(_CATEGORICAL_CMAP):
|
|
609
|
+
# custom categorical cmap
|
|
610
610
|
self._categories_colors_dict = {
|
|
611
611
|
category: _CATEGORICAL_CMAP[i]
|
|
612
612
|
for i, category in enumerate(self._unique_values)
|
|
613
613
|
} | self._categories_colors_dict
|
|
614
614
|
elif self._cmap:
|
|
615
615
|
cmap = matplotlib.colormaps.get_cmap(self._cmap)
|
|
616
|
-
|
|
617
616
|
self._categories_colors_dict = {
|
|
618
617
|
category: colors.to_hex(cmap(int(i)))
|
|
619
618
|
for i, category in enumerate(self._unique_values)
|
|
620
619
|
} | self._categories_colors_dict
|
|
621
620
|
else:
|
|
622
621
|
cmap = matplotlib.colormaps.get_cmap("tab20")
|
|
623
|
-
|
|
624
622
|
self._categories_colors_dict = {
|
|
625
623
|
category: colors.to_hex(cmap(int(i)))
|
|
626
624
|
for i, category in enumerate(self._unique_values)
|
|
@@ -664,6 +662,9 @@ class Map:
|
|
|
664
662
|
if self.scheme == "jenks":
|
|
665
663
|
bins = jenks_breaks(gdf[column].dropna(), n_classes=n_classes)
|
|
666
664
|
else:
|
|
665
|
+
# local import because slow
|
|
666
|
+
from mapclassify import classify
|
|
667
|
+
|
|
667
668
|
binning = classify(
|
|
668
669
|
np.asarray(gdf[column].dropna()),
|
|
669
670
|
scheme=self.scheme,
|
sgis/maps/maps.py
CHANGED
|
@@ -34,13 +34,6 @@ from .map import Map
|
|
|
34
34
|
from .thematicmap import ThematicMap
|
|
35
35
|
from .wms import WmsLoader
|
|
36
36
|
|
|
37
|
-
try:
|
|
38
|
-
from torchgeo.datasets.geo import RasterDataset
|
|
39
|
-
except ImportError:
|
|
40
|
-
|
|
41
|
-
class RasterDataset:
|
|
42
|
-
"""Placeholder."""
|
|
43
|
-
|
|
44
37
|
|
|
45
38
|
def _get_location_mask(kwargs: dict, gdfs) -> tuple[GeoDataFrame | None, dict]:
|
|
46
39
|
try:
|
|
@@ -530,7 +523,7 @@ def explore_locals(
|
|
|
530
523
|
|
|
531
524
|
frame = inspect.currentframe().f_back
|
|
532
525
|
|
|
533
|
-
allowed_types = (GeoDataFrame, GeoSeries, Geometry
|
|
526
|
+
allowed_types = (GeoDataFrame, GeoSeries, Geometry)
|
|
534
527
|
|
|
535
528
|
local_gdfs = {}
|
|
536
529
|
while True:
|
sgis/maps/thematicmap.py
CHANGED
|
@@ -296,13 +296,11 @@ class ThematicMap(Map):
|
|
|
296
296
|
if self._gdf[self._column].isna().any():
|
|
297
297
|
isnas = []
|
|
298
298
|
for label, gdf in self._gdfs.items():
|
|
299
|
-
|
|
300
299
|
isnas.append(gdf[gdf[self._column].isna()])
|
|
301
300
|
self._gdfs[label] = gdf[gdf[self._column].notna()]
|
|
302
|
-
color = self.facecolor if nan_hatch else self.nan_color
|
|
303
301
|
self._more_data[nan_label] = {
|
|
304
302
|
"gdf": pd.concat(isnas, ignore_index=True),
|
|
305
|
-
"color":
|
|
303
|
+
"color": self.nan_color,
|
|
306
304
|
"hatch": nan_hatch,
|
|
307
305
|
} | new_kwargs
|
|
308
306
|
self._gdf = pd.concat(self.gdfs.values(), ignore_index=True)
|
sgis/maps/wms.py
CHANGED
|
@@ -20,7 +20,7 @@ JSON_YEARS = [str(year) for year in range(1999, 2025)]
|
|
|
20
20
|
DEFAULT_YEARS: tuple[str] = tuple(
|
|
21
21
|
str(year)
|
|
22
22
|
for year in range(
|
|
23
|
-
int(datetime.datetime.now().year) -
|
|
23
|
+
int(datetime.datetime.now().year) - 10,
|
|
24
24
|
int(datetime.datetime.now().year) + 1,
|
|
25
25
|
)
|
|
26
26
|
)
|
|
@@ -111,6 +111,7 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
111
111
|
this_tile["year"] = year
|
|
112
112
|
else:
|
|
113
113
|
this_tile["year"] = "9999"
|
|
114
|
+
|
|
114
115
|
all_tiles.append(this_tile)
|
|
115
116
|
|
|
116
117
|
self.tiles = sorted(all_tiles, key=lambda x: x["year"])
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
|
|
3
|
+
import joblib
|
|
3
4
|
import pandas as pd
|
|
4
5
|
from geopandas import GeoDataFrame
|
|
5
6
|
from igraph import Graph
|
|
@@ -10,6 +11,7 @@ def _get_route_frequencies(
|
|
|
10
11
|
graph: Graph,
|
|
11
12
|
roads: GeoDataFrame,
|
|
12
13
|
weight_df: DataFrame,
|
|
14
|
+
n_jobs: int,
|
|
13
15
|
) -> GeoDataFrame:
|
|
14
16
|
"""Function used in the get_route_frequencies method of NetworkAnalysis."""
|
|
15
17
|
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
|
@@ -18,26 +20,25 @@ def _get_route_frequencies(
|
|
|
18
20
|
|
|
19
21
|
od_pairs = weight_df.index
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
line_ids["multiplier"] = weight_df.loc[ori_id, des_id].iloc[0]
|
|
23
|
+
ori_ids = od_pairs.get_level_values(0).unique()
|
|
24
|
+
if n_jobs == 1:
|
|
25
|
+
nested_results: list[list[DataFrame]] = [
|
|
26
|
+
_get_one_route_frequency(
|
|
27
|
+
ori_id, od_pairs=od_pairs, graph=graph, weight_df=weight_df
|
|
28
|
+
)
|
|
29
|
+
for ori_id in ori_ids
|
|
30
|
+
]
|
|
31
|
+
del nested_results
|
|
32
|
+
else:
|
|
33
|
+
with joblib.Parallel(n_jobs) as parallel:
|
|
34
|
+
nested_results: list[list[DataFrame]] = parallel(
|
|
35
|
+
joblib.delayed(_get_one_route_frequency)(
|
|
36
|
+
ori_id, od_pairs=od_pairs, graph=graph, weight_df=weight_df
|
|
37
|
+
)
|
|
38
|
+
for ori_id in ori_ids
|
|
39
|
+
)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
resultlist = [x for y in nested_results for x in y]
|
|
41
42
|
|
|
42
43
|
if not resultlist:
|
|
43
44
|
return pd.DataFrame(columns=["frequency", "geometry"])
|
|
@@ -53,34 +54,56 @@ def _get_route_frequencies(
|
|
|
53
54
|
return roads_visited
|
|
54
55
|
|
|
55
56
|
|
|
57
|
+
def _get_one_route_frequency(
|
|
58
|
+
ori_id: int, od_pairs: pd.MultiIndex, graph: Graph, weight_df: pd.DataFrame
|
|
59
|
+
):
|
|
60
|
+
relevant_pairs = od_pairs[od_pairs.get_level_values(0) == ori_id]
|
|
61
|
+
destinations = relevant_pairs.get_level_values(1)
|
|
62
|
+
|
|
63
|
+
res = graph.get_shortest_paths(
|
|
64
|
+
weights="weight", v=ori_id, to=destinations, output="epath"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
results = []
|
|
68
|
+
for i, des_id in enumerate(destinations):
|
|
69
|
+
indices = graph.es[res[i]]
|
|
70
|
+
|
|
71
|
+
if not indices:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
line_ids = DataFrame({"src_tgt_wt": indices["src_tgt_wt"]})
|
|
75
|
+
line_ids["origin"] = ori_id
|
|
76
|
+
line_ids["destination"] = des_id
|
|
77
|
+
line_ids["multiplier"] = weight_df.loc[ori_id, des_id].iloc[0]
|
|
78
|
+
|
|
79
|
+
results.append(line_ids)
|
|
80
|
+
return results
|
|
81
|
+
|
|
82
|
+
|
|
56
83
|
def _get_route(
|
|
57
84
|
graph: Graph,
|
|
58
85
|
weight: str,
|
|
59
86
|
roads: GeoDataFrame,
|
|
60
87
|
od_pairs: pd.MultiIndex,
|
|
88
|
+
n_jobs: int,
|
|
61
89
|
) -> GeoDataFrame:
|
|
62
90
|
"""Function used in the get_route method of NetworkAnalysis."""
|
|
63
91
|
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if not indices:
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
line_ids = _create_line_id_df(indices["src_tgt_wt"], ori_id, des_id)
|
|
93
|
+
ori_ids = od_pairs.get_level_values(0).unique()
|
|
94
|
+
if n_jobs == 1:
|
|
95
|
+
nested_results: list[list[DataFrame]] = [
|
|
96
|
+
_get_one_route(ori_id, od_pairs=od_pairs, graph=graph) for ori_id in ori_ids
|
|
97
|
+
]
|
|
98
|
+
del nested_results
|
|
99
|
+
else:
|
|
100
|
+
with joblib.Parallel(n_jobs) as parallel:
|
|
101
|
+
nested_results: list[list[DataFrame]] = parallel(
|
|
102
|
+
joblib.delayed(_get_one_route)(ori_id, od_pairs=od_pairs, graph=graph)
|
|
103
|
+
for ori_id in ori_ids
|
|
104
|
+
)
|
|
82
105
|
|
|
83
|
-
|
|
106
|
+
resultlist = [x for y in nested_results for x in y]
|
|
84
107
|
|
|
85
108
|
if not resultlist:
|
|
86
109
|
warnings.warn(
|
|
@@ -98,6 +121,29 @@ def _get_route(
|
|
|
98
121
|
return lines[["origin", "destination", weight, "geometry"]]
|
|
99
122
|
|
|
100
123
|
|
|
124
|
+
def _get_one_route(
|
|
125
|
+
ori_id: int,
|
|
126
|
+
od_pairs: pd.MultiIndex,
|
|
127
|
+
graph: Graph,
|
|
128
|
+
) -> list[DataFrame]:
|
|
129
|
+
relevant_pairs = od_pairs[od_pairs.get_level_values(0) == ori_id]
|
|
130
|
+
destinations = relevant_pairs.get_level_values(1)
|
|
131
|
+
|
|
132
|
+
results = graph.get_shortest_paths(
|
|
133
|
+
weights="weight", v=ori_id, to=destinations, output="epath"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
out_lines = []
|
|
137
|
+
for i, des_id in enumerate(destinations):
|
|
138
|
+
indices = graph.es[results[i]]
|
|
139
|
+
if not indices:
|
|
140
|
+
continue
|
|
141
|
+
line_ids: DataFrame = _create_line_id_df(indices["src_tgt_wt"], ori_id, des_id)
|
|
142
|
+
out_lines.append(line_ids)
|
|
143
|
+
|
|
144
|
+
return out_lines
|
|
145
|
+
|
|
146
|
+
|
|
101
147
|
def _get_k_routes(
|
|
102
148
|
graph: Graph,
|
|
103
149
|
weight: str,
|
|
@@ -436,6 +436,7 @@ class NetworkAnalysis:
|
|
|
436
436
|
rowwise: bool = False,
|
|
437
437
|
strict: bool = False,
|
|
438
438
|
frequency_col: str = "frequency",
|
|
439
|
+
n_jobs: int | None = None,
|
|
439
440
|
) -> GeoDataFrame:
|
|
440
441
|
"""Finds the number of times each line segment was visited in all trips.
|
|
441
442
|
|
|
@@ -465,6 +466,7 @@ class NetworkAnalysis:
|
|
|
465
466
|
to False.
|
|
466
467
|
frequency_col: Name of column with the number of times each road was
|
|
467
468
|
visited. Defaults to 'frequency'.
|
|
469
|
+
n_jobs: Number of parallell jobs.
|
|
468
470
|
|
|
469
471
|
Returns:
|
|
470
472
|
A GeoDataFrame with all line segments that were visited at least once,
|
|
@@ -635,6 +637,7 @@ class NetworkAnalysis:
|
|
|
635
637
|
graph=self.graph,
|
|
636
638
|
roads=self.network.gdf,
|
|
637
639
|
weight_df=weight_df,
|
|
640
|
+
n_jobs=n_jobs,
|
|
638
641
|
)
|
|
639
642
|
|
|
640
643
|
if isinstance(results, GeoDataFrame):
|
|
@@ -665,6 +668,7 @@ class NetworkAnalysis:
|
|
|
665
668
|
rowwise: bool = False,
|
|
666
669
|
destination_count: int | None = None,
|
|
667
670
|
cutoff: int | float | None = None,
|
|
671
|
+
n_jobs: int | None = None,
|
|
668
672
|
) -> GeoDataFrame:
|
|
669
673
|
"""Returns the geometry of the low-cost route between origins and destinations.
|
|
670
674
|
|
|
@@ -685,6 +689,7 @@ class NetworkAnalysis:
|
|
|
685
689
|
cutoff: the maximum cost (weight) for the trips. Defaults to None,
|
|
686
690
|
meaning all rows will be included. NaNs will also be removed if cutoff
|
|
687
691
|
is specified.
|
|
692
|
+
n_jobs: Number of parallell jobs.
|
|
688
693
|
|
|
689
694
|
Returns:
|
|
690
695
|
A DataFrame with the geometry of the routes between origin and destination.
|
|
@@ -738,6 +743,7 @@ class NetworkAnalysis:
|
|
|
738
743
|
weight=self.rules.weight,
|
|
739
744
|
roads=self.network.gdf,
|
|
740
745
|
od_pairs=od_pairs,
|
|
746
|
+
n_jobs=n_jobs,
|
|
741
747
|
)
|
|
742
748
|
|
|
743
749
|
if cutoff is not None:
|
sgis/parallel/parallel.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import Any
|
|
|
13
13
|
from pandas.api.types import is_array_like
|
|
14
14
|
|
|
15
15
|
try:
|
|
16
|
-
|
|
16
|
+
from gcsfs import GCSFileSystem
|
|
17
17
|
except ImportError:
|
|
18
18
|
pass
|
|
19
19
|
|
|
@@ -575,7 +575,7 @@ class Parallel:
|
|
|
575
575
|
A GeoDataFrame, or a list of GeoDataFrames if concat is False.
|
|
576
576
|
"""
|
|
577
577
|
if "file_system" not in kwargs:
|
|
578
|
-
kwargs["file_system"] =
|
|
578
|
+
kwargs["file_system"] = GCSFileSystem()
|
|
579
579
|
|
|
580
580
|
if strict:
|
|
581
581
|
res = self.map(read_geopandas, files, kwargs=kwargs)
|
|
@@ -653,7 +653,7 @@ class Parallel:
|
|
|
653
653
|
if funcdict is None:
|
|
654
654
|
funcdict = {}
|
|
655
655
|
|
|
656
|
-
fs =
|
|
656
|
+
fs = GCSFileSystem()
|
|
657
657
|
|
|
658
658
|
for _, data, folder, postfunc in dict_zip_union(in_data, out_data, funcdict):
|
|
659
659
|
if data is None or (
|
sgis/raster/image_collection.py
CHANGED
|
@@ -26,14 +26,11 @@ import numpy as np
|
|
|
26
26
|
import pandas as pd
|
|
27
27
|
import pyproj
|
|
28
28
|
import rasterio
|
|
29
|
-
from affine import Affine
|
|
30
29
|
from geopandas import GeoDataFrame
|
|
31
30
|
from geopandas import GeoSeries
|
|
31
|
+
from pandas.api.types import is_array_like
|
|
32
32
|
from pandas.api.types import is_dict_like
|
|
33
33
|
from rasterio.enums import MergeAlg
|
|
34
|
-
from scipy import stats
|
|
35
|
-
from scipy.ndimage import binary_dilation
|
|
36
|
-
from scipy.ndimage import binary_erosion
|
|
37
34
|
from shapely import Geometry
|
|
38
35
|
from shapely import box
|
|
39
36
|
from shapely import unary_union
|
|
@@ -64,42 +61,11 @@ except ImportError:
|
|
|
64
61
|
"""Placeholder."""
|
|
65
62
|
|
|
66
63
|
|
|
67
|
-
try:
|
|
68
|
-
from rioxarray.exceptions import NoDataInBounds
|
|
69
|
-
from rioxarray.merge import merge_arrays
|
|
70
|
-
from rioxarray.rioxarray import _generate_spatial_coords
|
|
71
|
-
except ImportError:
|
|
72
|
-
pass
|
|
73
|
-
try:
|
|
74
|
-
from xarray import DataArray
|
|
75
|
-
from xarray import Dataset
|
|
76
|
-
from xarray import combine_by_coords
|
|
77
|
-
except ImportError:
|
|
78
|
-
|
|
79
|
-
class DataArray:
|
|
80
|
-
"""Placeholder."""
|
|
81
|
-
|
|
82
|
-
class Dataset:
|
|
83
|
-
"""Placeholder."""
|
|
84
|
-
|
|
85
|
-
def combine_by_coords(*args, **kwargs) -> None:
|
|
86
|
-
raise ImportError("xarray")
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
try:
|
|
90
|
-
from gcsfs.core import GCSFile
|
|
91
|
-
except ImportError:
|
|
92
|
-
|
|
93
|
-
class GCSFile:
|
|
94
|
-
"""Placeholder."""
|
|
95
|
-
|
|
96
|
-
|
|
97
64
|
from ..conf import _get_instance
|
|
98
65
|
from ..conf import config
|
|
99
66
|
from ..geopandas_tools.bounds import get_total_bounds
|
|
100
67
|
from ..geopandas_tools.conversion import to_bbox
|
|
101
68
|
from ..geopandas_tools.conversion import to_gdf
|
|
102
|
-
from ..geopandas_tools.conversion import to_geoseries
|
|
103
69
|
from ..geopandas_tools.conversion import to_shapely
|
|
104
70
|
from ..geopandas_tools.general import get_common_crs
|
|
105
71
|
from ..helpers import _fix_path
|
|
@@ -799,41 +765,6 @@ class _ImageBandBase(_ImageBase):
|
|
|
799
765
|
|
|
800
766
|
return missing_metadata_attributes | nonmissing_metadata_attributes
|
|
801
767
|
|
|
802
|
-
def _to_xarray(self, array: np.ndarray, transform: Affine) -> DataArray:
|
|
803
|
-
"""Convert the raster to an xarray.DataArray."""
|
|
804
|
-
attrs = {"crs": self.crs}
|
|
805
|
-
for attr in set(self.metadata_attributes).union({"date"}):
|
|
806
|
-
try:
|
|
807
|
-
attrs[attr] = getattr(self, attr)
|
|
808
|
-
except Exception:
|
|
809
|
-
pass
|
|
810
|
-
|
|
811
|
-
if len(array.shape) == 2:
|
|
812
|
-
height, width = array.shape
|
|
813
|
-
dims = ["y", "x"]
|
|
814
|
-
elif len(array.shape) == 3:
|
|
815
|
-
height, width = array.shape[1:]
|
|
816
|
-
dims = ["band", "y", "x"]
|
|
817
|
-
elif not any(dim for dim in array.shape):
|
|
818
|
-
DataArray(
|
|
819
|
-
name=self.name or self.__class__.__name__,
|
|
820
|
-
attrs=attrs,
|
|
821
|
-
)
|
|
822
|
-
else:
|
|
823
|
-
raise ValueError(
|
|
824
|
-
f"Array should be 2 or 3 dimensional. Got shape {array.shape}"
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
coords = _generate_spatial_coords(transform, width, height)
|
|
828
|
-
|
|
829
|
-
return DataArray(
|
|
830
|
-
array,
|
|
831
|
-
coords=coords,
|
|
832
|
-
dims=dims,
|
|
833
|
-
name=self.name or self.__class__.__name__,
|
|
834
|
-
attrs=attrs,
|
|
835
|
-
)
|
|
836
|
-
|
|
837
768
|
|
|
838
769
|
class Band(_ImageBandBase):
|
|
839
770
|
"""Band holding a single 2 dimensional array representing an image band."""
|
|
@@ -1264,7 +1195,7 @@ class Band(_ImageBandBase):
|
|
|
1264
1195
|
def has_array(self) -> bool:
|
|
1265
1196
|
"""Whether the array is loaded."""
|
|
1266
1197
|
try:
|
|
1267
|
-
if not
|
|
1198
|
+
if not is_array_like(self.values):
|
|
1268
1199
|
raise ValueError()
|
|
1269
1200
|
return True
|
|
1270
1201
|
except ValueError: # also catches _ArrayNotLoadedError
|
|
@@ -1501,20 +1432,12 @@ class Band(_ImageBandBase):
|
|
|
1501
1432
|
return df[(df[column] != self.nodata) & (df[column].notna())]
|
|
1502
1433
|
return df
|
|
1503
1434
|
|
|
1504
|
-
def to_xarray(self) -> DataArray:
|
|
1505
|
-
"""Convert the raster to an xarray.DataArray."""
|
|
1506
|
-
return self._to_xarray(
|
|
1507
|
-
self.values,
|
|
1508
|
-
transform=self.transform,
|
|
1509
|
-
# name=self.name or self.__class__.__name__.lower(),
|
|
1510
|
-
)
|
|
1511
|
-
|
|
1512
1435
|
def to_numpy(self) -> np.ndarray | np.ma.core.MaskedArray:
|
|
1513
1436
|
"""Convert the raster to a numpy.ndarray."""
|
|
1514
1437
|
return self._to_numpy(self.values).copy()
|
|
1515
1438
|
|
|
1516
1439
|
def _to_numpy(
|
|
1517
|
-
self, arr: np.ndarray
|
|
1440
|
+
self, arr: np.ndarray, masked: bool = True
|
|
1518
1441
|
) -> np.ndarray | np.ma.core.MaskedArray:
|
|
1519
1442
|
if not isinstance(arr, np.ndarray):
|
|
1520
1443
|
mask_arr = None
|
|
@@ -1891,13 +1814,6 @@ class Image(_ImageBandBase):
|
|
|
1891
1814
|
**self._common_init_kwargs_after_load,
|
|
1892
1815
|
)
|
|
1893
1816
|
|
|
1894
|
-
def to_xarray(self) -> DataArray:
|
|
1895
|
-
"""Convert the raster to an xarray.DataArray."""
|
|
1896
|
-
return self._to_xarray(
|
|
1897
|
-
np.array([band.values for band in self]),
|
|
1898
|
-
transform=self[0].transform,
|
|
1899
|
-
)
|
|
1900
|
-
|
|
1901
1817
|
@property
|
|
1902
1818
|
def band_ids(self) -> list[str]:
|
|
1903
1819
|
"""The Band ids."""
|
|
@@ -2539,6 +2455,10 @@ class ImageCollection(_ImageBase):
|
|
|
2539
2455
|
indexes: int | tuple[int] | None = None,
|
|
2540
2456
|
**kwargs,
|
|
2541
2457
|
) -> np.ndarray:
|
|
2458
|
+
from rioxarray.merge import merge_arrays
|
|
2459
|
+
from rioxarray.rioxarray import _generate_spatial_coords
|
|
2460
|
+
from xarray import DataArray
|
|
2461
|
+
|
|
2542
2462
|
arrs = []
|
|
2543
2463
|
kwargs["indexes"] = indexes
|
|
2544
2464
|
bounds = to_shapely(bounds) if bounds is not None else None
|
|
@@ -2777,69 +2697,6 @@ class ImageCollection(_ImageBase):
|
|
|
2777
2697
|
]
|
|
2778
2698
|
return self
|
|
2779
2699
|
|
|
2780
|
-
def to_xarray(
|
|
2781
|
-
self,
|
|
2782
|
-
**kwargs,
|
|
2783
|
-
) -> Dataset:
|
|
2784
|
-
"""Convert the raster to an xarray.Dataset.
|
|
2785
|
-
|
|
2786
|
-
Images are converted to 2d arrays for each unique bounds.
|
|
2787
|
-
The spatial dimensions will be labeled "x" and "y". The third
|
|
2788
|
-
dimension defaults to "date" if all images have date attributes.
|
|
2789
|
-
Otherwise defaults to the image name.
|
|
2790
|
-
"""
|
|
2791
|
-
if any(not band.has_array for img in self for band in img):
|
|
2792
|
-
raise ValueError("Arrays must be loaded.")
|
|
2793
|
-
|
|
2794
|
-
# if by is None:
|
|
2795
|
-
if all(img.date for img in self):
|
|
2796
|
-
by = ["date"]
|
|
2797
|
-
elif not pd.Index([img.name for img in self]).is_unique:
|
|
2798
|
-
raise ValueError("Images must have unique names.")
|
|
2799
|
-
else:
|
|
2800
|
-
by = ["name"]
|
|
2801
|
-
# elif isinstance(by, str):
|
|
2802
|
-
# by = [by]
|
|
2803
|
-
|
|
2804
|
-
xarrs: dict[str, DataArray] = {}
|
|
2805
|
-
for (bounds, band_id), collection in self.groupby(["bounds", "band_id"]):
|
|
2806
|
-
name = f"{band_id}_{'-'.join(str(int(x)) for x in bounds)}"
|
|
2807
|
-
first_band = collection[0][0]
|
|
2808
|
-
coords = _generate_spatial_coords(
|
|
2809
|
-
first_band.transform, first_band.width, first_band.height
|
|
2810
|
-
)
|
|
2811
|
-
values = np.array([band.to_numpy() for img in collection for band in img])
|
|
2812
|
-
assert len(values) == len(collection)
|
|
2813
|
-
|
|
2814
|
-
# coords["band_id"] = [
|
|
2815
|
-
# band.band_id or i for i, band in enumerate(collection[0])
|
|
2816
|
-
# ]
|
|
2817
|
-
for attr in by:
|
|
2818
|
-
coords[attr] = [getattr(img, attr) for img in collection]
|
|
2819
|
-
# coords["band"] = band_id #
|
|
2820
|
-
|
|
2821
|
-
dims = [*by, "y", "x"]
|
|
2822
|
-
# dims = ["band", "y", "x"]
|
|
2823
|
-
# dims = {}
|
|
2824
|
-
# for attr in by:
|
|
2825
|
-
# dims[attr] = [getattr(img, attr) for img in collection]
|
|
2826
|
-
|
|
2827
|
-
xarrs[name] = DataArray(
|
|
2828
|
-
values,
|
|
2829
|
-
coords=coords,
|
|
2830
|
-
dims=dims,
|
|
2831
|
-
# name=name,
|
|
2832
|
-
name=band_id,
|
|
2833
|
-
attrs={
|
|
2834
|
-
"crs": collection.crs,
|
|
2835
|
-
"band_id": band_id,
|
|
2836
|
-
}, # , "bounds": bounds},
|
|
2837
|
-
**kwargs,
|
|
2838
|
-
)
|
|
2839
|
-
|
|
2840
|
-
return combine_by_coords(list(xarrs.values()))
|
|
2841
|
-
# return Dataset(xarrs)
|
|
2842
|
-
|
|
2843
2700
|
def sample(self, n: int = 1, size: int = 500) -> "ImageCollection":
|
|
2844
2701
|
"""Sample one or more areas of a given size and set this as mask for the images."""
|
|
2845
2702
|
unioned = self.union_all()
|
|
@@ -3407,24 +3264,6 @@ def _slope_2d(array: np.ndarray, res: int | tuple[int], degrees: int) -> np.ndar
|
|
|
3407
3264
|
return degrees
|
|
3408
3265
|
|
|
3409
3266
|
|
|
3410
|
-
def _clip_xarray(
|
|
3411
|
-
xarr: DataArray,
|
|
3412
|
-
mask: tuple[int, int, int, int],
|
|
3413
|
-
crs: Any,
|
|
3414
|
-
**kwargs,
|
|
3415
|
-
) -> DataArray:
|
|
3416
|
-
# xarray needs a numpy array of polygons
|
|
3417
|
-
mask_arr: np.ndarray = to_geoseries(mask).values
|
|
3418
|
-
try:
|
|
3419
|
-
return xarr.rio.clip(
|
|
3420
|
-
mask_arr,
|
|
3421
|
-
crs=crs,
|
|
3422
|
-
**kwargs,
|
|
3423
|
-
)
|
|
3424
|
-
except NoDataInBounds:
|
|
3425
|
-
return np.array([])
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
3267
|
def _get_all_file_paths(path: str) -> set[str]:
|
|
3429
3268
|
return {_fix_path(x) for x in sorted(set(_glob_func(path + "/**")))}
|
|
3430
3269
|
|
|
@@ -3645,6 +3484,9 @@ def array_buffer(arr: np.ndarray, distance: int) -> np.ndarray:
|
|
|
3645
3484
|
Returns:
|
|
3646
3485
|
Array with buffered values.
|
|
3647
3486
|
"""
|
|
3487
|
+
from scipy.ndimage import binary_dilation
|
|
3488
|
+
from scipy.ndimage import binary_erosion
|
|
3489
|
+
|
|
3648
3490
|
if not np.all(np.isin(arr, (1, 0, True, False))):
|
|
3649
3491
|
raise ValueError("Array must be all 0s and 1s or boolean.")
|
|
3650
3492
|
|
|
@@ -3655,6 +3497,7 @@ def array_buffer(arr: np.ndarray, distance: int) -> np.ndarray:
|
|
|
3655
3497
|
arr = np.where(arr, 1, 0)
|
|
3656
3498
|
|
|
3657
3499
|
if distance > 0:
|
|
3500
|
+
|
|
3658
3501
|
return binary_dilation(arr, structure=structure).astype(dtype)
|
|
3659
3502
|
elif distance < 0:
|
|
3660
3503
|
|
|
@@ -3671,6 +3514,8 @@ def _plot_pixels_1d(
|
|
|
3671
3514
|
figsize: tuple,
|
|
3672
3515
|
first_date: pd.Timestamp,
|
|
3673
3516
|
) -> None:
|
|
3517
|
+
from scipy import stats
|
|
3518
|
+
|
|
3674
3519
|
coef, intercept = np.linalg.lstsq(
|
|
3675
3520
|
np.vstack([x, np.ones(x.shape[0])]).T,
|
|
3676
3521
|
y,
|
sgis/raster/regex.py
CHANGED
|
@@ -7,7 +7,7 @@ import pandas as pd
|
|
|
7
7
|
from ..io._is_dapla import is_dapla
|
|
8
8
|
|
|
9
9
|
try:
|
|
10
|
-
|
|
10
|
+
from gcsfs import GCSFileSystem
|
|
11
11
|
except ImportError:
|
|
12
12
|
pass
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ except ImportError:
|
|
|
22
22
|
if is_dapla():
|
|
23
23
|
|
|
24
24
|
def _open_func(*args, **kwargs) -> GCSFile:
|
|
25
|
-
return
|
|
25
|
+
return GCSFileSystem().open(*args, **kwargs)
|
|
26
26
|
|
|
27
27
|
else:
|
|
28
28
|
_open_func = open
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sgis/__init__.py,sha256=
|
|
1
|
+
sgis/__init__.py,sha256=FcE8XYmVlyO-6s15AC8dLqIjp2KZpar2ND3siIxX830,7747
|
|
2
2
|
sgis/conf.py,sha256=pLqmvIKoKmXoW8chja3iQpbDUp9Z39vzl97MGH8ZHW0,2614
|
|
3
3
|
sgis/debug_config.py,sha256=Tfr19kU46hSkkspsIJcrUWvlhaL4U3-f8xEPkujSCAQ,593
|
|
4
4
|
sgis/exceptions.py,sha256=WNaEBPNNx0rmz-YDzlFX4vIE7ocJQruUTqS2RNAu2zU,660
|
|
@@ -6,39 +6,39 @@ sgis/geopandas_tools/__init__.py,sha256=bo8lFMcltOz7TtWAi52_ekR2gd3mjfBfKeMDV5zu
|
|
|
6
6
|
sgis/geopandas_tools/bounds.py,sha256=YJyF0gp78hFAjLLZmDquRKCBAtbt7QouG3snTcJeNQs,23822
|
|
7
7
|
sgis/geopandas_tools/buffer_dissolve_explode.py,sha256=ReIgoeh6CUVcLmsUZ_pyoWYg6iBZzYiGmFq6CMOKRvE,19535
|
|
8
8
|
sgis/geopandas_tools/centerlines.py,sha256=Q65Sx01SeAlulBEd9oaZkB2maBBNdLcJwAbTILg4SPU,11848
|
|
9
|
-
sgis/geopandas_tools/cleaning.py,sha256=
|
|
10
|
-
sgis/geopandas_tools/conversion.py,sha256=
|
|
9
|
+
sgis/geopandas_tools/cleaning.py,sha256=fST0xFztmyn-QUOAfvjZmu7aO_zPiolWK7gd7TR6ffI,24393
|
|
10
|
+
sgis/geopandas_tools/conversion.py,sha256=CrasgWHAnUmLC5tP73ZTDjQ6ahKFHQGqWj86PUif24M,24176
|
|
11
11
|
sgis/geopandas_tools/duplicates.py,sha256=TDDM4u1n7SIkyJrOfl1Lno92AmUPqtXBHsj1IUKC0hI,14992
|
|
12
12
|
sgis/geopandas_tools/general.py,sha256=YRpNEdwTHyFdQOdAfbCmYXS7PxoDjXxoagwpteXkYdI,43937
|
|
13
13
|
sgis/geopandas_tools/geocoding.py,sha256=n47aFQMm4yX1MsPnTM4dFjwegCA1ZmGUDj1uyu7OJV4,691
|
|
14
14
|
sgis/geopandas_tools/geometry_types.py,sha256=ijQDbQaZPqPGjBl707H4yooNXpk21RXyatI7itnvqLk,7603
|
|
15
|
-
sgis/geopandas_tools/neighbors.py,sha256=
|
|
15
|
+
sgis/geopandas_tools/neighbors.py,sha256=qTvnOyQyrcWQd0BDrmOvFQd8csMh005e_idJGmB4g7k,17485
|
|
16
16
|
sgis/geopandas_tools/overlay.py,sha256=5i9u8GgFuU0fCqzELsbIaoUPhw-E7eZHl_yKB0wEcGM,23464
|
|
17
17
|
sgis/geopandas_tools/point_operations.py,sha256=JM4hvfIVxZaZdGNlGzcCurrKzkgC_b9hzbFYN42f9WY,6972
|
|
18
18
|
sgis/geopandas_tools/polygon_operations.py,sha256=v-B9IgbFfm4dVHKPyzvmnNiqVCdtl9ddpCsQpZZ-9sU,49284
|
|
19
19
|
sgis/geopandas_tools/polygons_as_rings.py,sha256=BX_GZS6F9I4NbEpiOlNBd7zywJjdfdJVi_MkeONBuiM,14941
|
|
20
|
-
sgis/geopandas_tools/runners.py,sha256=
|
|
20
|
+
sgis/geopandas_tools/runners.py,sha256=J4lH0RXYDYTLVeQFgNv8gEY0E97QGIQ4zPW5vfoxgDU,12979
|
|
21
21
|
sgis/geopandas_tools/sfilter.py,sha256=BPz6-_9B7QdyYmVatZXavdHj7FIW_ztIyJHQOkKJt7A,10284
|
|
22
22
|
sgis/geopandas_tools/utils.py,sha256=X0pRvB1tWgV_0BCrRS1HU9LtLGnZCpvVPxyqM9JGb0Y,1415
|
|
23
23
|
sgis/helpers.py,sha256=4N6vFWQ3TYVzRHNcWY_fNa_GkFuaZB3vtCkkFND-qs0,9628
|
|
24
24
|
sgis/io/__init__.py,sha256=uyBr20YDqB2bQttrd5q1JuGOvX32A-MSvS7Wmw5f5qg,177
|
|
25
25
|
sgis/io/_is_dapla.py,sha256=wmfkSe98IrLhUg3dtXZusV6OVC8VlY1kbc5EQDf3P-Q,358
|
|
26
|
-
sgis/io/dapla_functions.py,sha256=
|
|
26
|
+
sgis/io/dapla_functions.py,sha256=ISF1vaBZI0D9pMngWVvHDvQr9wFHagQaAGY3z5P3Iqo,31116
|
|
27
27
|
sgis/io/opener.py,sha256=HWO3G1NB6bpXKM94JadCD513vjat1o1TFjWGWzyVasg,898
|
|
28
28
|
sgis/io/read_parquet.py,sha256=FvZYv1rLkUlrSaUY6QW6E1yntmntTeQuZ9ZRgCDO4IM,3776
|
|
29
29
|
sgis/maps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
sgis/maps/examine.py,sha256=Pb0dH8JazU5E2svfQrzHO1Bi-sjy5SeyY6zoeMO34jE,9369
|
|
31
|
-
sgis/maps/explore.py,sha256=
|
|
32
|
-
sgis/maps/httpserver.py,sha256=
|
|
31
|
+
sgis/maps/explore.py,sha256=A6-i0uKYIdfS46zrPIURObJqmt2VuQjXyS8FVk58cHU,47657
|
|
32
|
+
sgis/maps/httpserver.py,sha256=TETSGOgLjKl3TquPGoIP0tCJCz7BIwmXrrzSCT7jhXE,2550
|
|
33
33
|
sgis/maps/legend.py,sha256=qq2RkebuaNAdFztlXrDOWbN0voeK5w5VycmRKyx0NdM,26512
|
|
34
|
-
sgis/maps/map.py,sha256=
|
|
35
|
-
sgis/maps/maps.py,sha256=
|
|
34
|
+
sgis/maps/map.py,sha256=WZ5JnCpdioBCEh6kmV3XB4xVw41fYsEmQ6v8MIfwzmY,30727
|
|
35
|
+
sgis/maps/maps.py,sha256=Zkp-UGj92BCFYhY1_TLXL4FjnvwOjGMAryoWCtFdwPk,22989
|
|
36
36
|
sgis/maps/norge_i_bilder.json,sha256=VKmb7rg4jvgc8_Ve1fFnHyZ_Dkv4T5GTA0UCpqpFAi4,492751
|
|
37
|
-
sgis/maps/thematicmap.py,sha256=
|
|
37
|
+
sgis/maps/thematicmap.py,sha256=Z3o_Bca0oty5Cn35pZfX5Qy52sXDVIMVSFD6IlZrovo,25111
|
|
38
38
|
sgis/maps/tilesources.py,sha256=F4mFHxPwkiPJdVKzNkScTX6xbJAMIUtlTq4mQ83oguw,1746
|
|
39
|
-
sgis/maps/wms.py,sha256=
|
|
39
|
+
sgis/maps/wms.py,sha256=LSBtKkG5d-sggK_qO8BvOHBvPZZV_7AAo25kHF6kPio,6933
|
|
40
40
|
sgis/networkanalysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
sgis/networkanalysis/_get_route.py,sha256=
|
|
41
|
+
sgis/networkanalysis/_get_route.py,sha256=dMX4Vm6O90ISIZPjQWuZMVMuEubkeSdC2osMCbFvrRU,7750
|
|
42
42
|
sgis/networkanalysis/_od_cost_matrix.py,sha256=zkyPX7ObT996ahaFJ2oI0D0SqQWbWyfy_qLtXwValPg,3434
|
|
43
43
|
sgis/networkanalysis/_points.py,sha256=ajCy17dAmRq3bWRkNu_0LHreCVJ5Uh8DzAKWxyw7ipw,4481
|
|
44
44
|
sgis/networkanalysis/_service_area.py,sha256=jE0X54yS4eMfZYJXeKe_NgMKPDpau-05xWZaxDi_c6Y,5546
|
|
@@ -47,21 +47,21 @@ sgis/networkanalysis/cutting_lines.py,sha256=ZQAt0cufaPeNAEqUzp-imu26AIL9S5-lw6X
|
|
|
47
47
|
sgis/networkanalysis/directednetwork.py,sha256=Mrc2zHip4P5RNxnyffKm-xU832AVQeSHz-YZueAc0pM,11413
|
|
48
48
|
sgis/networkanalysis/finding_isolated_networks.py,sha256=Wg4ILhm7uS9RLOGcL0WN8uZBMJYjdljJc8L5DU5nIPY,3754
|
|
49
49
|
sgis/networkanalysis/network.py,sha256=zV9bAbVdTgTohg2o2RFGy2uhOJrd3Ma57hwIAStxMAQ,7847
|
|
50
|
-
sgis/networkanalysis/networkanalysis.py,sha256
|
|
50
|
+
sgis/networkanalysis/networkanalysis.py,sha256=KkmMt_AH4iRBGe2acNbhABVpyDV8FP_O5TKnDoZFLpM,68692
|
|
51
51
|
sgis/networkanalysis/networkanalysisrules.py,sha256=9sXigaCzvKhXFwpeVNMtOiIK3_Hzp9yDpFklmEEAPak,12956
|
|
52
52
|
sgis/networkanalysis/nodes.py,sha256=atFSpqz-_uJHMrf6MC0zhrrcWIydRMFZrsaHC2xr1GU,3374
|
|
53
53
|
sgis/networkanalysis/traveling_salesman.py,sha256=Jjo6bHY4KJ-eK0LycyTy0sWxZjgITs5MBllZ_G9FhTE,5655
|
|
54
54
|
sgis/parallel/__init__.py,sha256=fw_Fl3IJk1bKzrRBhZIoOpznJqwd09NVHJJFj2ZLeIU,32
|
|
55
|
-
sgis/parallel/parallel.py,sha256=
|
|
55
|
+
sgis/parallel/parallel.py,sha256=V7O5mEZdfJpcOPrmn2H4bEGtbzA0FggjQ8dGhznjr80,40052
|
|
56
56
|
sgis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
sgis/raster/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
sgis/raster/base.py,sha256=tiZEuMcVK6hOm_aIjWhQ1WGshcjsxT1fFkuBSLFiMC0,7785
|
|
59
|
-
sgis/raster/image_collection.py,sha256=
|
|
59
|
+
sgis/raster/image_collection.py,sha256=gbC0V2Xmckrsi1iAKbYgrsb_f9rja6Y7QUqMMJVCy2Y,121700
|
|
60
60
|
sgis/raster/indices.py,sha256=efJmgfPg_VuSzXFosXV661IendF8CwPFWtMhyP4TMUg,222
|
|
61
|
-
sgis/raster/regex.py,sha256=
|
|
61
|
+
sgis/raster/regex.py,sha256=4idTJ9vFtsGtbxcjJrx2VrpJJuDMP3bLdqF93Vc_cmY,3752
|
|
62
62
|
sgis/raster/sentinel_config.py,sha256=nySDqn2R8M6W8jguoBeSAK_zzbAsqmaI59i32446FwY,1268
|
|
63
63
|
sgis/raster/zonal.py,sha256=D4Gyptw-yOLTCO41peIuYbY-DANsJCG19xXDlf1QAz4,2299
|
|
64
|
-
ssb_sgis-1.2.
|
|
65
|
-
ssb_sgis-1.2.
|
|
66
|
-
ssb_sgis-1.2.
|
|
67
|
-
ssb_sgis-1.2.
|
|
64
|
+
ssb_sgis-1.2.6.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
|
|
65
|
+
ssb_sgis-1.2.6.dist-info/METADATA,sha256=1g2k5KcE4NTLcxfNqL08atchi_udhavHB7YLWfLrbew,11740
|
|
66
|
+
ssb_sgis-1.2.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
67
|
+
ssb_sgis-1.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|