ssb-sgis 1.2.3__tar.gz → 1.2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/PKG-INFO +1 -1
  2. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/pyproject.toml +1 -1
  3. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/__init__.py +1 -0
  4. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/cleaning.py +0 -1
  5. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/conversion.py +0 -27
  6. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/neighbors.py +2 -1
  7. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/runners.py +47 -0
  8. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/io/dapla_functions.py +31 -9
  9. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/explore.py +6 -12
  10. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/httpserver.py +2 -1
  11. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/map.py +15 -14
  12. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/maps.py +1 -8
  13. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/thematicmap.py +1 -3
  14. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/wms.py +2 -1
  15. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/_get_route.py +83 -37
  16. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/networkanalysis.py +6 -0
  17. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/parallel/parallel.py +3 -3
  18. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/image_collection.py +13 -168
  19. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/regex.py +2 -2
  20. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/LICENSE +0 -0
  21. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/README.md +0 -0
  22. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/conf.py +0 -0
  23. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/debug_config.py +0 -0
  24. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/exceptions.py +0 -0
  25. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/__init__.py +0 -0
  26. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/bounds.py +0 -0
  27. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +0 -0
  28. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/centerlines.py +0 -0
  29. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/duplicates.py +0 -0
  30. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/general.py +0 -0
  31. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/geocoding.py +0 -0
  32. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/geometry_types.py +0 -0
  33. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/overlay.py +0 -0
  34. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/point_operations.py +0 -0
  35. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/polygon_operations.py +0 -0
  36. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
  37. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/sfilter.py +0 -0
  38. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/geopandas_tools/utils.py +0 -0
  39. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/helpers.py +0 -0
  40. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/io/__init__.py +0 -0
  41. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/io/_is_dapla.py +0 -0
  42. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/io/opener.py +0 -0
  43. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/io/read_parquet.py +0 -0
  44. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/__init__.py +0 -0
  45. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/examine.py +0 -0
  46. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/legend.py +0 -0
  47. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/norge_i_bilder.json +0 -0
  48. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/maps/tilesources.py +0 -0
  49. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/__init__.py +0 -0
  50. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
  51. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/_points.py +0 -0
  52. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/_service_area.py +0 -0
  53. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
  54. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/cutting_lines.py +0 -0
  55. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/directednetwork.py +0 -0
  56. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
  57. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/network.py +0 -0
  58. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
  59. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/nodes.py +0 -0
  60. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
  61. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/parallel/__init__.py +0 -0
  62. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/py.typed +0 -0
  63. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/__init__.py +0 -0
  64. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/base.py +0 -0
  65. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/indices.py +0 -0
  66. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/sentinel_config.py +0 -0
  67. {ssb_sgis-1.2.3 → ssb_sgis-1.2.6}/src/sgis/raster/zonal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ssb-sgis
3
- Version: 1.2.3
3
+ Version: 1.2.6
4
4
  Summary: GIS functions used at Statistics Norway.
5
5
  Home-page: https://github.com/statisticsnorway/ssb-sgis
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ssb-sgis"
3
- version = "1.2.3"
3
+ version = "1.2.6"
4
4
  description = "GIS functions used at Statistics Norway."
5
5
  authors = ["Morten Letnes <morten.letnes@ssb.no>"]
6
6
  license = "MIT"
@@ -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
@@ -603,7 +603,6 @@ def split_by_neighbors(df, split_by, tolerance, grid_size=None) -> GeoDataFrame:
603
603
  buff(df, tolerance),
604
604
  how="identity",
605
605
  grid_size=grid_size,
606
- geom_type="polygon",
607
606
  )
608
607
  .pipe(get_line_segments)
609
608
  .reset_index(drop=True)
@@ -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
 
@@ -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,
@@ -40,7 +40,10 @@ from ..helpers import _get_file_system
40
40
  try:
41
41
  from gcsfs import GCSFileSystem
42
42
  except ImportError:
43
- pass
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(next(iter(filters))).replace("==", "=")
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
- return GeoDataFrame(cols | {"geometry": []})
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, file_system=file_system, mask=mask, use_threads=use_threads, **kwargs
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
- df = GeoDataFrame(cols | {"geometry": []})
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], file_system, use_threads, mask, **kwargs
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
- df = pd.DataFrame(columns=_get_columns(first_path, file_system))
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
@@ -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:
@@ -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='{display_address}/stop'>here</a> to stop.</p>
31
+ <p>Click <a href='{stop_address}'>here</a> to stop.</p>
31
32
  """
32
33
  )
33
34
  else:
@@ -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
- # custom categorical cmap
609
- if not self._cmap and len(self._unique_values) <= len(_CATEGORICAL_CMAP):
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,
@@ -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, RasterDataset)
526
+ allowed_types = (GeoDataFrame, GeoSeries, Geometry)
534
527
 
535
528
  local_gdfs = {}
536
529
  while True:
@@ -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": 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)
@@ -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) - 8,
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
- for ori_id in od_pairs.get_level_values(0).unique():
22
- relevant_pairs = od_pairs[od_pairs.get_level_values(0) == ori_id]
23
- destinations = relevant_pairs.get_level_values(1)
24
-
25
- res = graph.get_shortest_paths(
26
- weights="weight", v=ori_id, to=destinations, output="epath"
27
- )
28
-
29
- for i, des_id in enumerate(destinations):
30
- indices = graph.es[res[i]]
31
-
32
- if not indices:
33
- continue
34
-
35
- line_ids = DataFrame({"src_tgt_wt": indices["src_tgt_wt"]})
36
- line_ids["origin"] = ori_id
37
- line_ids["destination"] = des_id
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
- resultlist.append(line_ids)
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
- resultlist: list[DataFrame] = []
66
-
67
- for ori_id in od_pairs.get_level_values(0).unique():
68
- relevant_pairs = od_pairs[od_pairs.get_level_values(0) == ori_id]
69
- destinations = relevant_pairs.get_level_values(1)
70
-
71
- res = graph.get_shortest_paths(
72
- weights="weight", v=ori_id, to=destinations, output="epath"
73
- )
74
-
75
- for i, des_id in enumerate(destinations):
76
- indices = graph.es[res[i]]
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
- resultlist.append(line_ids)
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:
@@ -13,7 +13,7 @@ from typing import Any
13
13
  from pandas.api.types import is_array_like
14
14
 
15
15
  try:
16
- import dapla as dp
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"] = dp.FileClient.get_gcs_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 = dp.FileClient.get_gcs_file_system()
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 (
@@ -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 isinstance(self.values, (np.ndarray | DataArray)):
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 | DataArray, masked: bool = True
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,
@@ -7,7 +7,7 @@ import pandas as pd
7
7
  from ..io._is_dapla import is_dapla
8
8
 
9
9
  try:
10
- import dapla as dp
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 dp.FileClient.get_gcs_file_system().open(*args, **kwargs)
25
+ return GCSFileSystem().open(*args, **kwargs)
26
26
 
27
27
  else:
28
28
  _open_func = open
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes