ssb-sgis 1.0.9__tar.gz → 1.0.10__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 (60) hide show
  1. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/PKG-INFO +1 -1
  2. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/pyproject.toml +1 -1
  3. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/cleaning.py +3 -3
  4. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/_is_dapla.py +2 -5
  5. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/image_collection.py +112 -47
  6. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/LICENSE +0 -0
  7. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/README.md +0 -0
  8. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/__init__.py +0 -0
  9. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/debug_config.py +0 -0
  10. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/exceptions.py +0 -0
  11. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/__init__.py +0 -0
  12. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/bounds.py +0 -0
  13. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +0 -0
  14. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/centerlines.py +0 -0
  15. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/conversion.py +0 -0
  16. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/duplicates.py +0 -0
  17. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/general.py +0 -0
  18. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/geocoding.py +0 -0
  19. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/geometry_types.py +0 -0
  20. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/neighbors.py +0 -0
  21. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/overlay.py +0 -0
  22. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/point_operations.py +0 -0
  23. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/polygon_operations.py +0 -0
  24. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
  25. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/sfilter.py +0 -0
  26. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/helpers.py +0 -0
  27. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/dapla_functions.py +0 -0
  28. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/opener.py +0 -0
  29. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/read_parquet.py +0 -0
  30. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/__init__.py +0 -0
  31. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/examine.py +0 -0
  32. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/explore.py +0 -0
  33. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/httpserver.py +0 -0
  34. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/legend.py +0 -0
  35. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/map.py +0 -0
  36. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/maps.py +0 -0
  37. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/thematicmap.py +0 -0
  38. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/tilesources.py +0 -0
  39. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/__init__.py +0 -0
  40. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_get_route.py +0 -0
  41. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
  42. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_points.py +0 -0
  43. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_service_area.py +0 -0
  44. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
  45. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/cutting_lines.py +0 -0
  46. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/directednetwork.py +0 -0
  47. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
  48. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/network.py +0 -0
  49. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/networkanalysis.py +0 -0
  50. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
  51. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/nodes.py +0 -0
  52. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
  53. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/parallel/parallel.py +0 -0
  54. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/py.typed +0 -0
  55. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/__init__.py +0 -0
  56. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/base.py +0 -0
  57. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/indices.py +0 -0
  58. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/regex.py +0 -0
  59. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/sentinel_config.py +0 -0
  60. {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/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.0.9
3
+ Version: 1.0.10
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.0.9"
3
+ version = "1.0.10"
4
4
  description = "GIS functions used at Statistics Norway."
5
5
  authors = ["Morten Letnes <morten.letnes@ssb.no>"]
6
6
  license = "MIT"
@@ -579,9 +579,9 @@ def _snap_to_anchors(
579
579
  # browser=True,
580
580
  )
581
581
 
582
- print(
583
- "line_is_simple", line_is_simple, range_index, i, index, j
584
- ) # , j2, j3, x)
582
+ # print(
583
+ # "line_is_simple", line_is_simple, range_index, i, index, j
584
+ # ) # , j2, j3, x)
585
585
 
586
586
  if not line_is_simple:
587
587
  # for j4 in range(len(ring)):
@@ -7,8 +7,5 @@ import os
7
7
 
8
8
 
9
9
  def is_dapla() -> bool:
10
- """From https://github.com/statisticsnorway/ssb-altinn-python/blob/main/src/altinn/utils.py."""
11
- try:
12
- return os.environ["GCS_TOKEN_PROVIDER_KEY"] == "google"
13
- except KeyError:
14
- return False
10
+ """Simply checks if an os environment variable contains the text 'dapla'."""
11
+ return any("dapla" in key.lower() for key in os.environ)
@@ -177,6 +177,90 @@ def _get_child_paths_threaded(data: Sequence[str]) -> set[str]:
177
177
  return set(itertools.chain.from_iterable(all_paths))
178
178
 
179
179
 
180
+ @dataclass
181
+ class PixelwiseResults:
182
+ """Container of results from pixelwise operation to be converted."""
183
+
184
+ row_indices: np.ndarray
185
+ col_indices: np.ndarray
186
+ results: list[Any]
187
+ res: int | tuple[int, int]
188
+ bounds: tuple[float, float, float, float]
189
+ shape: tuple[int, int]
190
+ crs: Any
191
+ nodata: int | float | None
192
+
193
+ def to_tuple(self) -> tuple[int, int, Any]:
194
+ """Return 3-length tuple of row indices, column indices and pixelwise results."""
195
+ return self.row_indices, self.col_indices, self.results
196
+
197
+ def to_dict(self) -> dict[tuple[int, int], Any]:
198
+ """Return dictionary with row and column indices as keys and pixelwise results as values."""
199
+ return {
200
+ (int(row), int(col)): value
201
+ for row, col, value in zip(
202
+ self.row_indices, self.col_indices, self.results, strict=True
203
+ )
204
+ }
205
+
206
+ def to_geopandas(self, column: str = "value") -> GeoDataFrame:
207
+ """Return GeoDataFrame with pixel geometries and values from the pixelwise operation."""
208
+ minx, miny = self.bounds[:2]
209
+ resx, resy = _res_as_tuple(self.res)
210
+
211
+ minxs = np.full(self.row_indices.shape, minx) + (minx * self.row_indices * resx)
212
+ minys = np.full(self.col_indices.shape, miny) + (miny * self.col_indices * resy)
213
+ maxxs = minxs + resx
214
+ maxys = minys + resy
215
+
216
+ return GeoDataFrame(
217
+ {
218
+ column: self.results,
219
+ "geometry": [
220
+ box(minx, miny, maxx, maxy)
221
+ for minx, miny, maxx, maxy in zip(
222
+ minxs, minys, maxxs, maxys, strict=True
223
+ )
224
+ ],
225
+ },
226
+ index=[self.row_indices, self.col_indices],
227
+ crs=self.crs,
228
+ )
229
+
230
+ def to_numpy(self) -> np.ndarray | tuple[np.ndarray, ...]:
231
+ """Reshape pixelwise results to 2d numpy arrays in the shape of the full arrays of the image bands."""
232
+ try:
233
+ n_out_arrays = len(next(iter(self.results)))
234
+ except TypeError:
235
+ n_out_arrays = 1
236
+
237
+ out_arrays = [
238
+ np.full(self.shape, self.nodata).astype(np.float64)
239
+ for _ in range(n_out_arrays)
240
+ ]
241
+
242
+ for row, col, these_results in zip(
243
+ self.row_indices, self.col_indices, self.results, strict=True
244
+ ):
245
+ if these_results is None:
246
+ continue
247
+ for i, arr in enumerate(out_arrays):
248
+ try:
249
+ arr[row, col] = these_results[i]
250
+ except TypeError:
251
+ arr[row, col] = these_results
252
+
253
+ for i, array in enumerate(out_arrays):
254
+ all_are_integers = np.all(np.mod(array, 1) == 0)
255
+ if all_are_integers:
256
+ out_arrays[i] = array.astype(int)
257
+
258
+ if len(out_arrays) == 1:
259
+ return out_arrays[0]
260
+
261
+ return tuple(out_arrays)
262
+
263
+
180
264
  class ImageCollectionGroupBy:
181
265
  """Iterator and merger class returned from groupby.
182
266
 
@@ -617,19 +701,19 @@ class _ImageBandBase(_ImageBase):
617
701
  return nonmissing_metadata_attributes
618
702
 
619
703
  # read all xml content once
620
- file_contents: list[str] = []
704
+ file_contents: dict[str, str] = {}
621
705
  for path in self._all_file_paths:
622
706
  if ".xml" not in path:
623
707
  continue
624
708
  with _open_func(path, "rb") as file:
625
- file_contents.append(file.read().decode("utf-8"))
709
+ file_contents[path] = file.read().decode("utf-8")
626
710
 
627
711
  def is_last_xml(i: int) -> bool:
628
712
  return i == len(file_contents) - 1
629
713
 
630
714
  for attr, value in missing_metadata_attributes.items():
631
715
  results = None
632
- for i, file_content in enumerate(file_contents):
716
+ for i, file_content in enumerate(file_contents.values()):
633
717
  if isinstance(value, str) and value in dir(self):
634
718
  # method or a hardcoded value
635
719
  value: Callable | Any = getattr(self, value)
@@ -639,7 +723,7 @@ class _ImageBandBase(_ImageBase):
639
723
  results = value(file_content)
640
724
  except _RegexError as e:
641
725
  if is_last_xml(i):
642
- raise e.__class__(self.path, e) from e
726
+ raise e.__class__(self.path, list(file_contents), e) from e
643
727
  continue
644
728
  if results is not None:
645
729
  break
@@ -804,9 +888,7 @@ class Band(_ImageBandBase):
804
888
  )
805
889
  else:
806
890
  self._path = _fix_path(str(data))
807
- if callable(res) and res() is None:
808
- res = None
809
- self._res = res
891
+ self._res = res if not (callable(res) and res() is None) else None
810
892
 
811
893
  if cmap is not None:
812
894
  self.cmap = cmap
@@ -1476,7 +1558,7 @@ class Image(_ImageBandBase):
1476
1558
  f"'data' must be string, Path-like or a sequence of Band. Got {data}"
1477
1559
  )
1478
1560
 
1479
- self._res = res
1561
+ self._res = res if not (callable(res) and res() is None) else None
1480
1562
  self._path = _fix_path(data)
1481
1563
 
1482
1564
  if all_file_paths is None and self.path:
@@ -1955,7 +2037,7 @@ class ImageCollection(_ImageBase):
1955
2037
  self.nodata = nodata
1956
2038
  self.level = level
1957
2039
  self.processes = processes
1958
- self._res = res
2040
+ self._res = res if not (callable(res) and res() is None) else None
1959
2041
  self._crs = None
1960
2042
 
1961
2043
  self._df = None
@@ -2079,6 +2161,7 @@ class ImageCollection(_ImageBase):
2079
2161
  kwargs: dict | None = None,
2080
2162
  index_aligned_kwargs: dict | None = None,
2081
2163
  masked: bool = True,
2164
+ processes: int | None = None,
2082
2165
  ) -> np.ndarray | tuple[np.ndarray] | None:
2083
2166
  """Run a function for each pixel.
2084
2167
 
@@ -2108,13 +2191,23 @@ class ImageCollection(_ImageBase):
2108
2191
  else:
2109
2192
  mask_array = None
2110
2193
 
2111
- return pixelwise(
2194
+ nonmissing_row_indices, nonmissing_col_indices, results = pixelwise(
2112
2195
  func=func,
2113
2196
  values=values,
2114
2197
  mask_array=mask_array,
2115
2198
  index_aligned_kwargs=index_aligned_kwargs,
2116
2199
  kwargs=kwargs,
2117
- processes=self.processes,
2200
+ processes=processes or self.processes,
2201
+ )
2202
+
2203
+ return PixelwiseResults(
2204
+ nonmissing_row_indices,
2205
+ nonmissing_col_indices,
2206
+ results,
2207
+ shape=values.shape[1:],
2208
+ res=self.res,
2209
+ bounds=self.bounds,
2210
+ crs=self.crs,
2118
2211
  nodata=self.nodata or np.nan,
2119
2212
  )
2120
2213
 
@@ -2990,14 +3083,14 @@ class Sentinel2Config:
2990
3083
  xml_file,
2991
3084
  )
2992
3085
  if match_ is None:
2993
- raise _RegexError()
3086
+ return None
2994
3087
 
2995
3088
  if "NOT_REFINED" in match_.group(0):
2996
3089
  return False
2997
3090
  elif "REFINED" in match_.group(0):
2998
3091
  return True
2999
3092
  else:
3000
- raise _RegexError()
3093
+ raise _RegexError(xml_file)
3001
3094
 
3002
3095
  def _get_boa_quantification_value(self, xml_file: str) -> int:
3003
3096
  return int(
@@ -3553,26 +3646,25 @@ def pixelwise(
3553
3646
  index_aligned_kwargs: dict | None = None,
3554
3647
  kwargs: dict | None = None,
3555
3648
  processes: int = 1,
3556
- nodata=np.nan,
3557
- ) -> Any:
3649
+ ) -> tuple[np.ndarray, np.ndarray, list[Any]]:
3558
3650
  """Run a function for each pixel of a 3d array."""
3559
3651
  index_aligned_kwargs = index_aligned_kwargs or {}
3560
3652
  kwargs = kwargs or {}
3561
3653
 
3562
3654
  if mask_array is not None:
3655
+ # skip pixels where all values are masked
3563
3656
  not_all_missing = np.all(mask_array, axis=0) == False
3564
-
3565
3657
  else:
3566
3658
  mask_array = np.full(values.shape, False)
3567
3659
  not_all_missing = np.full(values.shape[1:], True)
3568
3660
 
3569
- nonmissing_row_indices, nonmissing_col_indices = not_all_missing.nonzero()
3570
-
3571
3661
  def select_pixel_values(row: int, col: int) -> np.ndarray:
3572
3662
  return values[~mask_array[:, row, col], row, col]
3573
3663
 
3664
+ # loop through long 1d arrays of aligned row and col indices
3665
+ nonmissing_row_indices, nonmissing_col_indices = not_all_missing.nonzero()
3574
3666
  with joblib.Parallel(n_jobs=processes, backend="loky") as parallel:
3575
- results: list[tuple[np.float64, np.float64]] = parallel(
3667
+ results: list[Any] = parallel(
3576
3668
  joblib.delayed(func)(
3577
3669
  select_pixel_values(row, col),
3578
3670
  **kwargs,
@@ -3586,31 +3678,4 @@ def pixelwise(
3586
3678
  )
3587
3679
  )
3588
3680
 
3589
- if all(x is None for x in results):
3590
- return
3591
-
3592
- try:
3593
- n_out_arrays = len(next(iter(results)))
3594
- except TypeError:
3595
- n_out_arrays = 1
3596
-
3597
- out_arrays = tuple(np.full(values.shape[1:], nodata) for _ in range(n_out_arrays))
3598
-
3599
- counter = 0
3600
- for row, col in zip(nonmissing_row_indices, nonmissing_col_indices, strict=True):
3601
- these_results = results[counter]
3602
- if these_results is None:
3603
- counter += 1
3604
- continue
3605
- for i, arr in enumerate(out_arrays):
3606
- try:
3607
- arr[row, col] = these_results[i]
3608
- except TypeError:
3609
- arr[row, col] = these_results
3610
- counter += 1
3611
- assert counter == len(results), (counter, len(results))
3612
-
3613
- if len(out_arrays) == 1:
3614
- return out_arrays[0]
3615
-
3616
- return out_arrays
3681
+ return nonmissing_row_indices, nonmissing_col_indices, results
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes