ssb-sgis 1.0.2__py3-none-any.whl → 1.0.4__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 +20 -9
- sgis/debug_config.py +24 -0
- sgis/exceptions.py +2 -2
- sgis/geopandas_tools/bounds.py +33 -36
- sgis/geopandas_tools/buffer_dissolve_explode.py +136 -35
- sgis/geopandas_tools/centerlines.py +4 -91
- sgis/geopandas_tools/cleaning.py +1576 -583
- sgis/geopandas_tools/conversion.py +38 -19
- sgis/geopandas_tools/duplicates.py +29 -8
- sgis/geopandas_tools/general.py +263 -100
- sgis/geopandas_tools/geometry_types.py +4 -4
- sgis/geopandas_tools/neighbors.py +19 -15
- sgis/geopandas_tools/overlay.py +2 -2
- sgis/geopandas_tools/point_operations.py +5 -5
- sgis/geopandas_tools/polygon_operations.py +510 -105
- sgis/geopandas_tools/polygons_as_rings.py +40 -8
- sgis/geopandas_tools/sfilter.py +29 -12
- sgis/helpers.py +3 -3
- sgis/io/dapla_functions.py +238 -19
- sgis/io/read_parquet.py +1 -1
- sgis/maps/examine.py +27 -12
- sgis/maps/explore.py +450 -65
- sgis/maps/legend.py +177 -76
- sgis/maps/map.py +206 -103
- sgis/maps/maps.py +178 -105
- sgis/maps/thematicmap.py +243 -83
- sgis/networkanalysis/_service_area.py +6 -1
- sgis/networkanalysis/closing_network_holes.py +2 -2
- sgis/networkanalysis/cutting_lines.py +15 -8
- sgis/networkanalysis/directednetwork.py +1 -1
- sgis/networkanalysis/finding_isolated_networks.py +15 -8
- sgis/networkanalysis/networkanalysis.py +17 -19
- sgis/networkanalysis/networkanalysisrules.py +1 -1
- sgis/networkanalysis/traveling_salesman.py +1 -1
- sgis/parallel/parallel.py +64 -27
- sgis/raster/__init__.py +0 -6
- sgis/raster/base.py +208 -0
- sgis/raster/cube.py +54 -8
- sgis/raster/image_collection.py +3257 -0
- sgis/raster/indices.py +17 -5
- sgis/raster/raster.py +138 -243
- sgis/raster/sentinel_config.py +120 -0
- sgis/raster/zonal.py +0 -1
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/METADATA +6 -7
- ssb_sgis-1.0.4.dist-info/RECORD +62 -0
- sgis/raster/methods_as_functions.py +0 -0
- sgis/raster/torchgeo.py +0 -171
- ssb_sgis-1.0.2.dist-info/RECORD +0 -61
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/WHEEL +0 -0
|
@@ -57,7 +57,7 @@ class NetworkAnalysis:
|
|
|
57
57
|
log: A DataFrame with information about each analysis run.
|
|
58
58
|
|
|
59
59
|
Examples:
|
|
60
|
-
|
|
60
|
+
---------
|
|
61
61
|
Read example data.
|
|
62
62
|
|
|
63
63
|
>>> import sgis as sg
|
|
@@ -191,7 +191,7 @@ class NetworkAnalysis:
|
|
|
191
191
|
straight lines between origin and destination.
|
|
192
192
|
|
|
193
193
|
Examples:
|
|
194
|
-
|
|
194
|
+
---------
|
|
195
195
|
Create the NetworkAnalysis instance.
|
|
196
196
|
|
|
197
197
|
>>> import sgis as sg
|
|
@@ -479,7 +479,7 @@ class NetworkAnalysis:
|
|
|
479
479
|
that contain weights and all indices of 'origins' and 'destinations'.
|
|
480
480
|
|
|
481
481
|
Examples:
|
|
482
|
-
|
|
482
|
+
---------
|
|
483
483
|
Create the NetworkAnalysis instance.
|
|
484
484
|
|
|
485
485
|
>>> import sgis as sg
|
|
@@ -692,7 +692,7 @@ class NetworkAnalysis:
|
|
|
692
692
|
containing the indices of the origins and destinations GeoDataFrames.
|
|
693
693
|
|
|
694
694
|
Examples:
|
|
695
|
-
|
|
695
|
+
---------
|
|
696
696
|
Create the NetworkAnalysis instance.
|
|
697
697
|
|
|
698
698
|
>>> import sgis as sg
|
|
@@ -820,7 +820,7 @@ class NetworkAnalysis:
|
|
|
820
820
|
ValueError: if drop_middle_percent is not between 0 and 100.
|
|
821
821
|
|
|
822
822
|
Examples:
|
|
823
|
-
|
|
823
|
+
---------
|
|
824
824
|
Create the NetworkAnalysis instance.
|
|
825
825
|
|
|
826
826
|
>>> import sgis as sg
|
|
@@ -966,7 +966,7 @@ class NetworkAnalysis:
|
|
|
966
966
|
precice results.
|
|
967
967
|
|
|
968
968
|
Examples:
|
|
969
|
-
|
|
969
|
+
---------
|
|
970
970
|
Create the NetworkAnalysis instance.
|
|
971
971
|
|
|
972
972
|
>>> import sgis as sg
|
|
@@ -1027,7 +1027,7 @@ class NetworkAnalysis:
|
|
|
1027
1027
|
|
|
1028
1028
|
if dissolve:
|
|
1029
1029
|
results = results.dissolve(by=["origin", self.rules.weight]).loc[
|
|
1030
|
-
:, [
|
|
1030
|
+
:, [results.geometry.name]
|
|
1031
1031
|
]
|
|
1032
1032
|
|
|
1033
1033
|
results = results.reset_index()
|
|
@@ -1038,7 +1038,7 @@ class NetworkAnalysis:
|
|
|
1038
1038
|
].rename(columns={"temp_idx": "origin"})[["origin"]]
|
|
1039
1039
|
|
|
1040
1040
|
if len(missing):
|
|
1041
|
-
missing[
|
|
1041
|
+
missing[results.geometry.name] = pd.NA
|
|
1042
1042
|
results = pd.concat([results, missing], ignore_index=True)
|
|
1043
1043
|
|
|
1044
1044
|
results["origin"] = results["origin"].map(self.origins.idx_dict)
|
|
@@ -1093,7 +1093,7 @@ class NetworkAnalysis:
|
|
|
1093
1093
|
service_area: Faster method where lines are not cut to get precice results.
|
|
1094
1094
|
|
|
1095
1095
|
Examples:
|
|
1096
|
-
|
|
1096
|
+
---------
|
|
1097
1097
|
Create the NetworkAnalysis instance.
|
|
1098
1098
|
|
|
1099
1099
|
>>> import sgis as sg
|
|
@@ -1151,7 +1151,7 @@ class NetworkAnalysis:
|
|
|
1151
1151
|
if not all(results.geometry.isna()):
|
|
1152
1152
|
if dissolve:
|
|
1153
1153
|
results = results.dissolve(by=["origin", self.rules.weight]).loc[
|
|
1154
|
-
:, [
|
|
1154
|
+
:, [results.geometry.name]
|
|
1155
1155
|
]
|
|
1156
1156
|
else:
|
|
1157
1157
|
results = results.dissolve(
|
|
@@ -1166,7 +1166,7 @@ class NetworkAnalysis:
|
|
|
1166
1166
|
].rename(columns={"temp_idx": "origin"})[["origin"]]
|
|
1167
1167
|
|
|
1168
1168
|
if len(missing):
|
|
1169
|
-
missing[
|
|
1169
|
+
missing[results.geometry.name] = pd.NA
|
|
1170
1170
|
results = pd.concat([results, missing], ignore_index=True)
|
|
1171
1171
|
|
|
1172
1172
|
results["origin"] = results["origin"].map(self.origins.idx_dict)
|
|
@@ -1329,7 +1329,7 @@ class NetworkAnalysis:
|
|
|
1329
1329
|
df["cost_std"] = results[self.rules.weight].std()
|
|
1330
1330
|
|
|
1331
1331
|
if fun == "service_area":
|
|
1332
|
-
df["percent_missing"] = results[
|
|
1332
|
+
df["percent_missing"] = results[results.geometry.name].isna().mean() * 100
|
|
1333
1333
|
else:
|
|
1334
1334
|
df["destinations_count"] = len(self.destinations.gdf)
|
|
1335
1335
|
|
|
@@ -1456,7 +1456,7 @@ class NetworkAnalysis:
|
|
|
1456
1456
|
else:
|
|
1457
1457
|
points = self.origins.gdf
|
|
1458
1458
|
|
|
1459
|
-
points = points.drop_duplicates(
|
|
1459
|
+
points = points.drop_duplicates(points.geometry.name)
|
|
1460
1460
|
|
|
1461
1461
|
self.network.gdf["meters_"] = self.network.gdf.length
|
|
1462
1462
|
|
|
@@ -1573,7 +1573,7 @@ class NetworkAnalysis:
|
|
|
1573
1573
|
This method is best stored in the NetworkAnalysis class,
|
|
1574
1574
|
since the point classes are instantiated each time an analysis is run.
|
|
1575
1575
|
"""
|
|
1576
|
-
if self.wkts[what]
|
|
1576
|
+
if not np.array_equal(self.wkts[what], points.geometry.to_wkt().values):
|
|
1577
1577
|
return True
|
|
1578
1578
|
|
|
1579
1579
|
if not all(x in self.graph.vs["name"] for x in list(points.temp_idx.values)):
|
|
@@ -1590,17 +1590,15 @@ class NetworkAnalysis:
|
|
|
1590
1590
|
"""
|
|
1591
1591
|
self.wkts = {}
|
|
1592
1592
|
|
|
1593
|
-
self.wkts["network"] =
|
|
1593
|
+
self.wkts["network"] = self.network.gdf.geometry.to_wkt().values
|
|
1594
1594
|
|
|
1595
1595
|
if not hasattr(self, "origins"):
|
|
1596
1596
|
return
|
|
1597
1597
|
|
|
1598
|
-
self.wkts["origins"] =
|
|
1598
|
+
self.wkts["origins"] = self.origins.gdf.geometry.to_wkt().values
|
|
1599
1599
|
|
|
1600
1600
|
if self.destinations is not None:
|
|
1601
|
-
self.wkts["destinations"] =
|
|
1602
|
-
geom.wkt for geom in self.destinations.gdf.geometry
|
|
1603
|
-
]
|
|
1601
|
+
self.wkts["destinations"] = self.destinations.gdf.geometry.to_wkt().values
|
|
1604
1602
|
|
|
1605
1603
|
@staticmethod
|
|
1606
1604
|
def _sort_breaks(breaks: str | list | tuple | int | float) -> list[float | int]:
|
sgis/parallel/parallel.py
CHANGED
|
@@ -2,6 +2,7 @@ import functools
|
|
|
2
2
|
import inspect
|
|
3
3
|
import itertools
|
|
4
4
|
import multiprocessing
|
|
5
|
+
import pickle
|
|
5
6
|
import warnings
|
|
6
7
|
from collections.abc import Callable
|
|
7
8
|
from collections.abc import Collection
|
|
@@ -31,7 +32,6 @@ try:
|
|
|
31
32
|
from ..io.dapla_functions import read_geopandas
|
|
32
33
|
from ..io.dapla_functions import write_geopandas
|
|
33
34
|
|
|
34
|
-
# from ..io.write_municipality_data import write_municipality_data
|
|
35
35
|
except ImportError:
|
|
36
36
|
pass
|
|
37
37
|
|
|
@@ -39,11 +39,8 @@ except ImportError:
|
|
|
39
39
|
try:
|
|
40
40
|
from dapla import read_pandas
|
|
41
41
|
from dapla import write_pandas
|
|
42
|
-
from dapla.gcs import GCSFileSystem
|
|
43
42
|
except ImportError:
|
|
44
|
-
|
|
45
|
-
class GCSFileSystem:
|
|
46
|
-
"""Placeholder."""
|
|
43
|
+
pass
|
|
47
44
|
|
|
48
45
|
|
|
49
46
|
class Parallel:
|
|
@@ -132,7 +129,7 @@ class Parallel:
|
|
|
132
129
|
'iterable'.
|
|
133
130
|
|
|
134
131
|
Examples:
|
|
135
|
-
|
|
132
|
+
---------
|
|
136
133
|
Multiply each list element by 2.
|
|
137
134
|
|
|
138
135
|
>>> iterable = [1, 2, 3]
|
|
@@ -183,7 +180,7 @@ class Parallel:
|
|
|
183
180
|
func_with_kwargs = functools.partial(func, **kwargs)
|
|
184
181
|
|
|
185
182
|
if self.processes == 1:
|
|
186
|
-
return
|
|
183
|
+
return [func_with_kwargs(item) for item in iterable]
|
|
187
184
|
|
|
188
185
|
iterable = list(iterable)
|
|
189
186
|
|
|
@@ -192,23 +189,42 @@ class Parallel:
|
|
|
192
189
|
|
|
193
190
|
if not processes:
|
|
194
191
|
return []
|
|
192
|
+
elif processes == 1:
|
|
193
|
+
return [func_with_kwargs(item) for item in iterable]
|
|
195
194
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
try:
|
|
196
|
+
if self.backend == "multiprocessing":
|
|
197
|
+
with multiprocessing.get_context(self.context).Pool(
|
|
198
|
+
processes, maxtasksperchild=self.maxtasksperchild, **self.kwargs
|
|
199
|
+
) as pool:
|
|
200
|
+
try:
|
|
201
|
+
return pool.map(
|
|
202
|
+
func_with_kwargs, iterable, chunksize=self.chunksize
|
|
203
|
+
)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
pool.terminate()
|
|
206
|
+
raise e
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
with joblib.Parallel(
|
|
209
|
+
n_jobs=processes, backend=self.backend, **self.kwargs
|
|
210
|
+
) as parallel:
|
|
211
|
+
return parallel(
|
|
212
|
+
joblib.delayed(func)(item, **kwargs) for item in iterable
|
|
213
|
+
)
|
|
214
|
+
except pickle.PickleError as e:
|
|
215
|
+
unpicklable = []
|
|
216
|
+
for k, v in locals().items():
|
|
217
|
+
try:
|
|
218
|
+
pickle.dumps(v)
|
|
219
|
+
except pickle.PickleError:
|
|
220
|
+
unpicklable.append(k)
|
|
221
|
+
except TypeError:
|
|
222
|
+
pass
|
|
223
|
+
if unpicklable:
|
|
224
|
+
raise pickle.PickleError(
|
|
225
|
+
f"Cannot unpickle objects: {unpicklable}"
|
|
226
|
+
) from e
|
|
227
|
+
raise e
|
|
212
228
|
|
|
213
229
|
def starmap(
|
|
214
230
|
self,
|
|
@@ -236,7 +252,7 @@ class Parallel:
|
|
|
236
252
|
'iterable'.
|
|
237
253
|
|
|
238
254
|
Examples:
|
|
239
|
-
|
|
255
|
+
---------
|
|
240
256
|
Multiply each list element by 2.
|
|
241
257
|
|
|
242
258
|
>>> iterable = [(1, 2), (2, 3), (3, 4)]
|
|
@@ -786,7 +802,11 @@ def _write_one_muni(
|
|
|
786
802
|
|
|
787
803
|
if not len(gdf_muni):
|
|
788
804
|
if write_empty:
|
|
789
|
-
|
|
805
|
+
try:
|
|
806
|
+
geom_col = gdf.geometry.name
|
|
807
|
+
except AttributeError:
|
|
808
|
+
geom_col = "geometry"
|
|
809
|
+
gdf_muni = gdf_muni.drop(columns=geom_col, errors="ignore")
|
|
790
810
|
gdf_muni["geometry"] = None
|
|
791
811
|
write_pandas(gdf_muni, out)
|
|
792
812
|
return
|
|
@@ -814,7 +834,11 @@ def _write_one_muni_with_neighbors(
|
|
|
814
834
|
|
|
815
835
|
if not len(gdf_neighbor):
|
|
816
836
|
if write_empty:
|
|
817
|
-
|
|
837
|
+
try:
|
|
838
|
+
geom_col = gdf.geometry.name
|
|
839
|
+
except AttributeError:
|
|
840
|
+
geom_col = "geometry"
|
|
841
|
+
gdf_neighbor = gdf_neighbor.drop(columns=geom_col, errors="ignore")
|
|
818
842
|
gdf_neighbor["geometry"] = None
|
|
819
843
|
write_pandas(gdf_neighbor, out)
|
|
820
844
|
return
|
|
@@ -860,7 +884,9 @@ def _fix_missing_muni_numbers(
|
|
|
860
884
|
)
|
|
861
885
|
|
|
862
886
|
try:
|
|
863
|
-
municipalities = municipalities[
|
|
887
|
+
municipalities = municipalities[
|
|
888
|
+
[muni_number_col, municipalities.geometry.name]
|
|
889
|
+
].to_crs(gdf.crs)
|
|
864
890
|
except Exception as e:
|
|
865
891
|
raise e.__class__(e, to_print) from e
|
|
866
892
|
|
|
@@ -950,7 +976,18 @@ def _clean_intersection(
|
|
|
950
976
|
df1: GeoDataFrame, df2: GeoDataFrame, to_print: str | None = None
|
|
951
977
|
) -> GeoDataFrame:
|
|
952
978
|
print(to_print, "- intersection chunk len:", len(df1))
|
|
953
|
-
|
|
979
|
+
cols_to_keep = df1.columns.union(df2.columns.difference({df2.geometry.name}))
|
|
980
|
+
df1["_range_idx"] = range(len(df1))
|
|
981
|
+
joined = df1.sjoin(df2, predicate="within", how="left")
|
|
982
|
+
within = joined.loc[joined["_range_idx"].notna(), cols_to_keep]
|
|
983
|
+
not_within = joined.loc[joined["_range_idx"].isna(), df1.columns]
|
|
984
|
+
return pd.concat(
|
|
985
|
+
[
|
|
986
|
+
within,
|
|
987
|
+
clean_overlay(not_within, df2, how="intersection"),
|
|
988
|
+
],
|
|
989
|
+
ignore_index=True,
|
|
990
|
+
)
|
|
954
991
|
|
|
955
992
|
|
|
956
993
|
def chunkwise(
|
sgis/raster/__init__.py
CHANGED
sgis/raster/base.py
CHANGED
|
@@ -1,8 +1,216 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import numbers
|
|
3
|
+
import warnings
|
|
4
|
+
from collections.abc import Callable
|
|
1
5
|
from contextlib import contextmanager
|
|
6
|
+
from typing import Any
|
|
2
7
|
|
|
8
|
+
import joblib
|
|
3
9
|
import numpy as np
|
|
4
10
|
import pandas as pd
|
|
5
11
|
import rasterio
|
|
12
|
+
from affine import Affine
|
|
13
|
+
from geopandas import GeoDataFrame
|
|
14
|
+
from geopandas import GeoSeries
|
|
15
|
+
from rasterio import features
|
|
16
|
+
from rasterio.enums import MergeAlg
|
|
17
|
+
from shapely import Geometry
|
|
18
|
+
from shapely.geometry import shape
|
|
19
|
+
|
|
20
|
+
from ..geopandas_tools.conversion import to_bbox
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_transform_from_bounds(
|
|
24
|
+
obj: GeoDataFrame | GeoSeries | Geometry | tuple, shape: tuple[float, ...]
|
|
25
|
+
) -> Affine:
|
|
26
|
+
minx, miny, maxx, maxy = to_bbox(obj)
|
|
27
|
+
if len(shape) == 2:
|
|
28
|
+
height, width = shape
|
|
29
|
+
elif len(shape) == 3:
|
|
30
|
+
_, height, width = shape
|
|
31
|
+
else:
|
|
32
|
+
return None
|
|
33
|
+
# raise ValueError(shape)
|
|
34
|
+
return rasterio.transform.from_bounds(minx, miny, maxx, maxy, width, height)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_shape_from_bounds(
|
|
38
|
+
obj: GeoDataFrame | GeoSeries | Geometry | tuple,
|
|
39
|
+
res: int,
|
|
40
|
+
indexes: int | tuple[int],
|
|
41
|
+
) -> tuple[int, int]:
|
|
42
|
+
resx, resy = (res, res) if isinstance(res, numbers.Number) else res
|
|
43
|
+
|
|
44
|
+
minx, miny, maxx, maxy = to_bbox(obj)
|
|
45
|
+
|
|
46
|
+
# minx = math.floor(minx / res) * res
|
|
47
|
+
# maxx = math.ceil(maxx / res) * res
|
|
48
|
+
# miny = math.floor(miny / res) * res
|
|
49
|
+
# maxy = math.ceil(maxy / res) * res
|
|
50
|
+
|
|
51
|
+
# # Compute output array shape. We guarantee it will cover the output
|
|
52
|
+
# # bounds completely
|
|
53
|
+
# width = round((maxx - minx) // res)
|
|
54
|
+
# height = round((maxy - miny) // res)
|
|
55
|
+
|
|
56
|
+
# if not isinstance(indexes, int):
|
|
57
|
+
# return len(indexes), height, width
|
|
58
|
+
# return height, width
|
|
59
|
+
|
|
60
|
+
diffx = maxx - minx
|
|
61
|
+
diffy = maxy - miny
|
|
62
|
+
width = int(diffx / resx)
|
|
63
|
+
height = int(diffy / resy)
|
|
64
|
+
if not isinstance(indexes, int):
|
|
65
|
+
return len(indexes), width, height
|
|
66
|
+
return height, width
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _array_to_geojson(
|
|
70
|
+
array: np.ndarray, transform: Affine, processes: int
|
|
71
|
+
) -> list[tuple]:
|
|
72
|
+
if hasattr(array, "mask"):
|
|
73
|
+
if isinstance(array.mask, np.ndarray):
|
|
74
|
+
mask = array.mask == False
|
|
75
|
+
else:
|
|
76
|
+
mask = None
|
|
77
|
+
array = array.data
|
|
78
|
+
else:
|
|
79
|
+
mask = None
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
return _array_to_geojson_loop(array, transform, mask, processes)
|
|
83
|
+
except ValueError:
|
|
84
|
+
try:
|
|
85
|
+
array = array.astype(np.float32)
|
|
86
|
+
return _array_to_geojson_loop(array, transform, mask, processes)
|
|
87
|
+
|
|
88
|
+
except Exception as err:
|
|
89
|
+
raise err.__class__(array.shape, err) from err
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _array_to_geojson_loop(array, transform, mask, processes):
|
|
93
|
+
if processes == 1:
|
|
94
|
+
return [
|
|
95
|
+
(value, shape(geom))
|
|
96
|
+
for geom, value in features.shapes(array, transform=transform, mask=mask)
|
|
97
|
+
]
|
|
98
|
+
else:
|
|
99
|
+
with joblib.Parallel(n_jobs=processes, backend="threading") as parallel:
|
|
100
|
+
return parallel(
|
|
101
|
+
joblib.delayed(_value_geom_pair)(value, geom)
|
|
102
|
+
for geom, value in features.shapes(
|
|
103
|
+
array, transform=transform, mask=mask
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _value_geom_pair(value, geom):
|
|
109
|
+
return (value, shape(geom))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _gdf_to_arr(
|
|
113
|
+
gdf: GeoDataFrame,
|
|
114
|
+
res: int | float,
|
|
115
|
+
fill: int = 0,
|
|
116
|
+
all_touched: bool = False,
|
|
117
|
+
merge_alg: Callable = MergeAlg.replace,
|
|
118
|
+
default_value: int = 1,
|
|
119
|
+
dtype: Any | None = None,
|
|
120
|
+
) -> np.ndarray:
|
|
121
|
+
"""Construct Raster from a GeoDataFrame or GeoSeries.
|
|
122
|
+
|
|
123
|
+
The GeoDataFrame should have
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
gdf: The GeoDataFrame to rasterize.
|
|
127
|
+
res: Resolution of the raster in units of the GeoDataFrame's coordinate reference system.
|
|
128
|
+
fill: Fill value for areas outside of input geometries (default is 0).
|
|
129
|
+
all_touched: Whether to consider all pixels touched by geometries,
|
|
130
|
+
not just those whose center is within the polygon (default is False).
|
|
131
|
+
merge_alg: Merge algorithm to use when combining geometries
|
|
132
|
+
(default is 'MergeAlg.replace').
|
|
133
|
+
default_value: Default value to use for the rasterized pixels
|
|
134
|
+
(default is 1).
|
|
135
|
+
dtype: Data type of the output array. If None, it will be
|
|
136
|
+
determined automatically.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
A Raster instance based on the specified GeoDataFrame and parameters.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
TypeError: If 'transform' is provided in kwargs, as this is
|
|
143
|
+
computed based on the GeoDataFrame bounds and resolution.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(gdf, GeoSeries):
|
|
146
|
+
values = gdf.index
|
|
147
|
+
gdf = gdf.to_frame("geometry")
|
|
148
|
+
elif isinstance(gdf, GeoDataFrame):
|
|
149
|
+
if len(gdf.columns) > 2:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
"gdf should have only a geometry column and one numeric column to "
|
|
152
|
+
"use as array values. "
|
|
153
|
+
"Alternatively only a geometry column and a numeric index."
|
|
154
|
+
)
|
|
155
|
+
elif len(gdf.columns) == 1:
|
|
156
|
+
values = gdf.index
|
|
157
|
+
else:
|
|
158
|
+
col: str = next(
|
|
159
|
+
iter([col for col in gdf if col != gdf._geometry_column_name])
|
|
160
|
+
)
|
|
161
|
+
values = gdf[col]
|
|
162
|
+
|
|
163
|
+
if isinstance(values, pd.MultiIndex):
|
|
164
|
+
raise ValueError("Index cannot be MultiIndex.")
|
|
165
|
+
|
|
166
|
+
shape = _get_shape_from_bounds(gdf.total_bounds, res=res, indexes=1)
|
|
167
|
+
transform = _get_transform_from_bounds(gdf.total_bounds, shape)
|
|
168
|
+
|
|
169
|
+
return features.rasterize(
|
|
170
|
+
_gdf_to_geojson_with_col(gdf, values),
|
|
171
|
+
out_shape=shape,
|
|
172
|
+
transform=transform,
|
|
173
|
+
fill=fill,
|
|
174
|
+
all_touched=all_touched,
|
|
175
|
+
merge_alg=merge_alg,
|
|
176
|
+
default_value=default_value,
|
|
177
|
+
dtype=dtype,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _gdf_to_geojson_with_col(gdf: GeoDataFrame, values: np.ndarray) -> list[dict]:
|
|
182
|
+
with warnings.catch_warnings():
|
|
183
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
184
|
+
return [
|
|
185
|
+
(feature["geometry"], val)
|
|
186
|
+
for val, feature in zip(
|
|
187
|
+
values, json.loads(gdf.to_json())["features"], strict=False
|
|
188
|
+
)
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _shapely_to_raster(
|
|
193
|
+
geometry: Geometry,
|
|
194
|
+
res: int | float,
|
|
195
|
+
fill: int = 0,
|
|
196
|
+
all_touched: bool = False,
|
|
197
|
+
merge_alg: Callable = MergeAlg.replace,
|
|
198
|
+
default_value: int = 1,
|
|
199
|
+
dtype: Any | None = None,
|
|
200
|
+
) -> np.array:
|
|
201
|
+
shape = _get_shape_from_bounds(geometry.bounds, res=res, indexes=1)
|
|
202
|
+
transform = _get_transform_from_bounds(geometry.bounds, shape)
|
|
203
|
+
|
|
204
|
+
return features.rasterize(
|
|
205
|
+
[(geometry, default_value)],
|
|
206
|
+
out_shape=shape,
|
|
207
|
+
transform=transform,
|
|
208
|
+
fill=fill,
|
|
209
|
+
all_touched=all_touched,
|
|
210
|
+
merge_alg=merge_alg,
|
|
211
|
+
default_value=default_value,
|
|
212
|
+
dtype=dtype,
|
|
213
|
+
)
|
|
6
214
|
|
|
7
215
|
|
|
8
216
|
@contextmanager
|