ssb-sgis 1.0.2__py3-none-any.whl → 1.0.3__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.
Files changed (42) hide show
  1. sgis/__init__.py +10 -6
  2. sgis/exceptions.py +2 -2
  3. sgis/geopandas_tools/bounds.py +17 -15
  4. sgis/geopandas_tools/buffer_dissolve_explode.py +24 -5
  5. sgis/geopandas_tools/conversion.py +15 -6
  6. sgis/geopandas_tools/duplicates.py +2 -2
  7. sgis/geopandas_tools/general.py +9 -5
  8. sgis/geopandas_tools/geometry_types.py +3 -3
  9. sgis/geopandas_tools/neighbors.py +3 -3
  10. sgis/geopandas_tools/point_operations.py +2 -2
  11. sgis/geopandas_tools/polygon_operations.py +5 -5
  12. sgis/geopandas_tools/sfilter.py +3 -3
  13. sgis/helpers.py +3 -3
  14. sgis/io/read_parquet.py +1 -1
  15. sgis/maps/examine.py +16 -2
  16. sgis/maps/explore.py +370 -57
  17. sgis/maps/legend.py +164 -72
  18. sgis/maps/map.py +184 -90
  19. sgis/maps/maps.py +92 -90
  20. sgis/maps/thematicmap.py +236 -83
  21. sgis/networkanalysis/closing_network_holes.py +2 -2
  22. sgis/networkanalysis/cutting_lines.py +3 -3
  23. sgis/networkanalysis/directednetwork.py +1 -1
  24. sgis/networkanalysis/finding_isolated_networks.py +2 -2
  25. sgis/networkanalysis/networkanalysis.py +7 -7
  26. sgis/networkanalysis/networkanalysisrules.py +1 -1
  27. sgis/networkanalysis/traveling_salesman.py +1 -1
  28. sgis/parallel/parallel.py +39 -19
  29. sgis/raster/__init__.py +0 -6
  30. sgis/raster/cube.py +51 -5
  31. sgis/raster/image_collection.py +2560 -0
  32. sgis/raster/indices.py +14 -5
  33. sgis/raster/raster.py +131 -236
  34. sgis/raster/sentinel_config.py +104 -0
  35. sgis/raster/zonal.py +0 -1
  36. {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +1 -1
  37. ssb_sgis-1.0.3.dist-info/RECORD +61 -0
  38. sgis/raster/methods_as_functions.py +0 -0
  39. sgis/raster/torchgeo.py +0 -171
  40. ssb_sgis-1.0.2.dist-info/RECORD +0 -61
  41. {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +0 -0
  42. {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +0 -0
sgis/raster/raster.py CHANGED
@@ -88,7 +88,7 @@ class Raster:
88
88
 
89
89
 
90
90
  Examples:
91
- --------
91
+ ---------
92
92
  Read tif file.
93
93
 
94
94
  >>> import sgis as sg
@@ -102,8 +102,8 @@ class Raster:
102
102
  The array is stored in the array attribute.
103
103
 
104
104
  >>> raster.load()
105
- >>> raster.array[raster.array < 0] = 0
106
- >>> raster.array
105
+ >>> raster.values[raster.values < 0] = 0
106
+ >>> raster.values
107
107
  [[[ 0. 0. 0. ... 158.4 155.6 152.6]
108
108
  [ 0. 0. 0. ... 158. 154.8 151.9]
109
109
  [ 0. 0. 0. ... 158.5 155.1 152.3]
@@ -194,7 +194,12 @@ class Raster:
194
194
  **kwargs: Arguments concerning file metadata or
195
195
  spatial properties of the image.
196
196
  """
197
+ warnings.warn("This class is deprecated in favor of Band", stacklevel=1)
197
198
  self.filename_regex = filename_regex
199
+ if filename_regex:
200
+ self.filename_pattern = re.compile(self.filename_regex, re.VERBOSE)
201
+ else:
202
+ self.filename_pattern = None
198
203
 
199
204
  if isinstance(data, Raster):
200
205
  for key, value in data.__dict__.items():
@@ -208,9 +213,9 @@ class Raster:
208
213
  self.path = None
209
214
 
210
215
  if isinstance(data, (np.ndarray)):
211
- self.array = data
216
+ self.values = data
212
217
  else:
213
- self.array = None
218
+ self.values = None
214
219
 
215
220
  if self.path is None and not any(
216
221
  [kwargs.get("transform"), kwargs.get("bounds")]
@@ -457,24 +462,25 @@ class Raster:
457
462
  Thise will override the items in the Raster's profile,
458
463
  if overlapping.
459
464
  """
460
- if self.array is None:
465
+ if self.values is None:
461
466
  raise AttributeError("The image hasn't been loaded.")
462
467
 
463
468
  profile = self.profile | kwargs
464
469
 
465
- with opener(path, file_system=self.file_system) as file:
470
+ with opener(path, "wb", file_system=self.file_system) as file:
466
471
  with rasterio.open(file, "w", **profile) as dst:
467
472
  self._write(dst, window)
468
473
 
469
474
  self.path = str(path)
470
475
 
471
- def load(self, **kwargs) -> Self:
476
+ def load(self, reload: bool = False, **kwargs) -> Self:
472
477
  """Load the entire image as an np.array.
473
478
 
474
479
  The array is stored in the 'array' attribute
475
480
  of the Raster.
476
481
 
477
482
  Args:
483
+ reload: Whether to reload the array if already loaded.
478
484
  **kwargs: Keyword arguments passed to the rasterio read
479
485
  method.
480
486
  """
@@ -483,7 +489,8 @@ class Raster:
483
489
  if "window" in kwargs:
484
490
  raise ValueError("Got an unexpected keyword argument 'window'")
485
491
 
486
- self._read_tif(**kwargs)
492
+ if reload or self.values is None:
493
+ self._read_tif(**kwargs)
487
494
 
488
495
  return self
489
496
 
@@ -575,10 +582,12 @@ class Raster:
575
582
  aggregated = []
576
583
  for i, poly in poly_iter:
577
584
  clipped = self.clip(poly)
578
- if not np.size(clipped.array):
585
+ if not np.size(clipped.values):
579
586
  aggregated.append(_no_overlap_df(func_names, i, date=self.date))
580
587
  aggregated.append(
581
- _aggregate(clipped.array, array_func, aggfunc, func_names, self.date, i)
588
+ _aggregate(
589
+ clipped.values, array_func, aggfunc, func_names, self.date, i
590
+ )
582
591
  )
583
592
 
584
593
  return _zonal_post(
@@ -589,74 +598,23 @@ class Raster:
589
598
  dropna=dropna,
590
599
  )
591
600
 
592
- def gradient(self, degrees: bool = False, copy: bool = False) -> Self:
593
- """Get the slope of an elevation raster.
594
-
595
- Calculates the absolute slope between the grid cells
596
- based on the image resolution.
597
-
598
- For multiband images, the calculation is done for each band.
599
-
600
- Args:
601
- degrees: If False (default), the returned values will be in ratios,
602
- where a value of 1 means 1 meter up per 1 meter forward. If True,
603
- the values will be in degrees from 0 to 90.
604
- copy: Whether to copy or overwrite the original Raster.
605
- Defaults to False to save memory.
606
-
607
- Returns:
608
- The class instance with new array values, or a copy if copy is True.
609
-
610
- Examples:
611
- --------
612
- Making an array where the gradient to the center is always 10.
613
-
614
- >>> import sgis as sg
615
- >>> import numpy as np
616
- >>> arr = np.array(
617
- ... [
618
- ... [100, 100, 100, 100, 100],
619
- ... [100, 110, 110, 110, 100],
620
- ... [100, 110, 120, 110, 100],
621
- ... [100, 110, 110, 110, 100],
622
- ... [100, 100, 100, 100, 100],
623
- ... ]
624
- ... )
625
-
626
- Now let's create a Raster from this array with a resolution of 10.
627
-
628
- >>> r = sg.Raster.from_array(arr, crs=None, bounds=(0, 0, 50, 50))
629
-
630
- The gradient will be 1 (1 meter up for every meter forward).
631
- The calculation is by default done in place to save memory.
632
-
633
- >>> r.gradient()
634
- >>> r.array
635
- array([[0., 1., 1., 1., 0.],
636
- [1., 1., 1., 1., 1.],
637
- [1., 1., 0., 1., 1.],
638
- [1., 1., 1., 1., 1.],
639
- [0., 1., 1., 1., 0.]])
640
- """
641
- return get_gradient(self, degrees=degrees, copy=copy)
642
-
643
601
  def to_xarray(self) -> DataArray:
644
602
  """Convert the raster to an xarray.DataArray."""
645
603
  self._check_for_array()
646
604
  self.name = self.name or self.__class__.__name__.lower()
647
605
  coords = _generate_spatial_coords(self.transform, self.width, self.height)
648
- if len(self.array.shape) == 2:
606
+ if len(self.values.shape) == 2:
649
607
  dims = ["y", "x"]
650
608
  # dims = ["band", "y", "x"]
651
- # array = np.array([self.array])
609
+ # array = np.array([self.values])
652
610
  # assert len(array.shape) == 3
653
- elif len(self.array.shape) == 3:
611
+ elif len(self.values.shape) == 3:
654
612
  dims = ["band", "y", "x"]
655
- # array = self.array
613
+ # array = self.values
656
614
  else:
657
615
  raise ValueError("Array must be 2 or 3 dimensional.")
658
616
  return xr.DataArray(
659
- self.array,
617
+ self.values,
660
618
  coords=coords,
661
619
  dims=dims,
662
620
  name=self.name,
@@ -728,7 +686,7 @@ class Raster:
728
686
  if not allow_override and self.crs is not None:
729
687
  raise ValueError("Cannot overwrite crs when allow_override is False.")
730
688
 
731
- if self.array is None:
689
+ if self.values is None:
732
690
  raise ValueError("array must be loaded/clipped before set_crs")
733
691
 
734
692
  self._crs = pyproj.CRS(crs)
@@ -750,7 +708,7 @@ class Raster:
750
708
  # ):
751
709
  # return self
752
710
 
753
- if self.array is None:
711
+ if self.values is None:
754
712
  project = pyproj.Transformer.from_crs(
755
713
  pyproj.CRS(self._prev_crs), pyproj.CRS(crs), always_xy=True
756
714
  ).transform
@@ -782,16 +740,16 @@ class Raster:
782
740
  # self._bounds = shapely.transform(old_box, project)
783
741
  else:
784
742
  was_2d = len(self.shape) == 2
785
- self.array, transform = reproject(
786
- source=self.array,
743
+ self.values, transform = reproject(
744
+ source=self.values,
787
745
  src_crs=self._prev_crs,
788
746
  src_transform=self.transform,
789
747
  dst_crs=pyproj.CRS(crs),
790
748
  **kwargs,
791
749
  )
792
- if was_2d and len(self.array.shape) == 3:
793
- assert self.array.shape[0] == 1
794
- self.array = self.array[0]
750
+ if was_2d and len(self.values.shape) == 3:
751
+ assert self.values.shape[0] == 1
752
+ self.values = self.values[0]
795
753
 
796
754
  self._bounds = rasterio.transform.array_bounds(
797
755
  self.height, self.width, transform
@@ -811,9 +769,9 @@ class Raster:
811
769
  raster = self
812
770
 
813
771
  if len(raster.shape) == 2:
814
- array = np.array([raster.array])
772
+ array = np.array([raster.values])
815
773
  else:
816
- array = raster.array
774
+ array = raster.values
817
775
 
818
776
  for arr in array:
819
777
  ax = plt.axes()
@@ -824,31 +782,31 @@ class Raster:
824
782
 
825
783
  def astype(self, dtype: type) -> Self:
826
784
  """Convert the datatype of the array."""
827
- if self.array is None:
785
+ if self.values is None:
828
786
  raise ValueError("Array is not loaded.")
829
- if not rasterio.dtypes.can_cast_dtype(self.array, dtype):
830
- min_dtype = rasterio.dtypes.get_minimum_dtype(self.array)
787
+ if not rasterio.dtypes.can_cast_dtype(self.values, dtype):
788
+ min_dtype = rasterio.dtypes.get_minimum_dtype(self.values)
831
789
  raise ValueError(f"Cannot cast to dtype. Minimum dtype is {min_dtype}")
832
- self.array = self.array.astype(dtype)
790
+ self.values = self.values.astype(dtype)
833
791
  self._dtype = dtype
834
792
  return self
835
793
 
836
794
  def as_minimum_dtype(self) -> Self:
837
795
  """Convert the array to the minimum dtype without overflow."""
838
- min_dtype = rasterio.dtypes.get_minimum_dtype(self.array)
839
- self.array = self.array.astype(min_dtype)
796
+ min_dtype = rasterio.dtypes.get_minimum_dtype(self.values)
797
+ self.values = self.values.astype(min_dtype)
840
798
  return self
841
799
 
842
800
  def min(self) -> int | None:
843
801
  """Minimum value in the array."""
844
- if np.size(self.array):
845
- return np.min(self.array)
802
+ if np.size(self.values):
803
+ return np.min(self.values)
846
804
  return None
847
805
 
848
806
  def max(self) -> int | None:
849
807
  """Maximum value in the array."""
850
- if np.size(self.array):
851
- return np.max(self.array)
808
+ if np.size(self.values):
809
+ return np.max(self.values)
852
810
  return None
853
811
 
854
812
  def _add_meta(self) -> Self:
@@ -867,10 +825,10 @@ class Raster:
867
825
  def array_list(self) -> list[np.ndarray]:
868
826
  """Get a list of 2D arrays."""
869
827
  self._check_for_array()
870
- if len(self.array.shape) == 2:
871
- return [self.array]
872
- elif len(self.array.shape) == 3:
873
- return list(self.array)
828
+ if len(self.values.shape) == 2:
829
+ return [self.values]
830
+ elif len(self.values.shape) == 3:
831
+ return list(self.values)
874
832
  else:
875
833
  raise ValueError
876
834
 
@@ -898,8 +856,7 @@ class Raster:
898
856
  def date(self) -> str | None:
899
857
  """Date in the image file name, if filename_regex is present."""
900
858
  try:
901
- pattern = re.compile(self.filename_regex, re.VERBOSE)
902
- return re.match(pattern, Path(self.path).name).group("date")
859
+ return re.match(self.filename_pattern, Path(self.path).name).group("date")
903
860
  except (AttributeError, TypeError):
904
861
  return None
905
862
 
@@ -907,8 +864,7 @@ class Raster:
907
864
  def band(self) -> str | None:
908
865
  """Band name of the image file name, if filename_regex is present."""
909
866
  try:
910
- pattern = re.compile(self.filename_regex, re.VERBOSE)
911
- return re.match(pattern, Path(self.path).name).group("band")
867
+ return re.match(self.filename_pattern, Path(self.path).name).group("band")
912
868
  except (AttributeError, TypeError):
913
869
  return None
914
870
 
@@ -916,7 +872,7 @@ class Raster:
916
872
  def dtype(self) -> Any:
917
873
  """Data type of the array."""
918
874
  try:
919
- return self.array.dtype
875
+ return self.values.dtype
920
876
  except AttributeError:
921
877
  try:
922
878
  return self._dtype
@@ -925,7 +881,7 @@ class Raster:
925
881
 
926
882
  @dtype.setter
927
883
  def dtype(self, new_dtype: Any) -> None:
928
- self.array = self.array.astype(new_dtype)
884
+ self.values = self.values.astype(new_dtype)
929
885
 
930
886
  @property
931
887
  def nodata(self) -> int | None:
@@ -937,10 +893,11 @@ class Raster:
937
893
 
938
894
  @property
939
895
  def tile(self) -> str | None:
940
- """The lower left corner (minx, miny) of the image as a string."""
941
- if self.bounds is None:
896
+ """Tile name from regex."""
897
+ try:
898
+ return re.match(self.filename_pattern, Path(self.path).name).group("tile")
899
+ except (AttributeError, TypeError):
942
900
  return None
943
- return f"{int(self.bounds[0])}_{int(self.bounds[1])}"
944
901
 
945
902
  @property
946
903
  def meta(self) -> dict:
@@ -976,7 +933,7 @@ class Raster:
976
933
  return {
977
934
  "indexes": self.indexes,
978
935
  "fill_value": self.nodata,
979
- "masked": True,
936
+ "masked": False,
980
937
  }
981
938
 
982
939
  @property
@@ -992,18 +949,18 @@ class Raster:
992
949
  @property
993
950
  def height(self) -> int | None:
994
951
  """Get the height of the image as number of pixels."""
995
- if self.array is None:
952
+ if self.values is None:
996
953
  try:
997
954
  return self._height
998
955
  except AttributeError:
999
956
  return None
1000
- i = 1 if len(self.array.shape) == 3 else 0
1001
- return self.array.shape[i]
957
+ i = 1 if len(self.values.shape) == 3 else 0
958
+ return self.values.shape[i]
1002
959
 
1003
960
  @property
1004
961
  def width(self) -> int | None:
1005
962
  """Get the width of the image as number of pixels."""
1006
- if self.array is None:
963
+ if self.values is None:
1007
964
  try:
1008
965
  return self._width
1009
966
  except AttributeError:
@@ -1014,16 +971,16 @@ class Raster:
1014
971
  return self._width
1015
972
  except Exception:
1016
973
  return None
1017
- i = 2 if len(self.array.shape) == 3 else 1
1018
- return self.array.shape[i]
974
+ i = 2 if len(self.values.shape) == 3 else 1
975
+ return self.values.shape[i]
1019
976
 
1020
977
  @property
1021
978
  def count(self) -> int:
1022
979
  """Get the number of bands in the image."""
1023
- if self.array is not None:
1024
- if len(self.array.shape) == 3:
1025
- return self.array.shape[0]
1026
- if len(self.array.shape) == 2:
980
+ if self.values is not None:
981
+ if len(self.values.shape) == 3:
982
+ return self.values.shape[0]
983
+ if len(self.values.shape) == 2:
1027
984
  return 1
1028
985
  if not hasattr(self._indexes, "__iter__"):
1029
986
  return 1
@@ -1032,8 +989,8 @@ class Raster:
1032
989
  @property
1033
990
  def shape(self) -> tuple[int]:
1034
991
  """Shape that is consistent with the array, whether it is loaded or not."""
1035
- if self.array is not None:
1036
- return self.array.shape
992
+ if self.values is not None:
993
+ return self.values.shape
1037
994
  if hasattr(self._indexes, "__iter__"):
1038
995
  return self.count, self.width, self.height
1039
996
  return self.width, self.height
@@ -1123,9 +1080,9 @@ class Raster:
1123
1080
  raise NotImplementedError("other must be of type Raster")
1124
1081
  if type(other) != type(self):
1125
1082
  return False
1126
- if self.array is None and other.array is not None:
1083
+ if self.values is None and other.values is not None:
1127
1084
  return False
1128
- if self.array is not None and other.array is None:
1085
+ if self.values is not None and other.values is None:
1129
1086
  return False
1130
1087
 
1131
1088
  for method in dir(self):
@@ -1134,7 +1091,7 @@ class Raster:
1134
1091
  if getattr(self, method) != getattr(other, method):
1135
1092
  return False
1136
1093
 
1137
- return np.array_equal(self.array, other.array)
1094
+ return np.array_equal(self.values, other.values)
1138
1095
 
1139
1096
  def __repr__(self) -> str:
1140
1097
  """The print representation."""
@@ -1144,52 +1101,52 @@ class Raster:
1144
1101
  res = int(self.res)
1145
1102
  except TypeError:
1146
1103
  res = None
1147
- return f"{self.__class__.__name__}(shape=({shp}), res={res}, name={self.name}, path={self.path})"
1104
+ return f"{self.__class__.__name__}(shape=({shp}), res={res}, band={self.band})"
1148
1105
 
1149
1106
  def __iter__(self) -> Iterator[np.ndarray]:
1150
1107
  """Iterate over the arrays."""
1151
- if len(self.array.shape) == 2:
1152
- return iter([self.array])
1153
- if len(self.array.shape) == 3:
1154
- return iter(self.array)
1108
+ if len(self.values.shape) == 2:
1109
+ return iter([self.values])
1110
+ if len(self.values.shape) == 3:
1111
+ return iter(self.values)
1155
1112
  raise ValueError(
1156
- f"Array should have shape length 2 or 3. Got {len(self.array.shape)}"
1113
+ f"Array should have shape length 2 or 3. Got {len(self.values.shape)}"
1157
1114
  )
1158
1115
 
1159
1116
  def __mul__(self, scalar: int | float) -> "Raster":
1160
1117
  """Multiply the array values with *."""
1161
1118
  self._check_for_array()
1162
- self.array = self.array * scalar
1119
+ self.values = self.values * scalar
1163
1120
  return self
1164
1121
 
1165
1122
  def __add__(self, scalar: int | float) -> "Raster":
1166
1123
  """Add to the array values with +."""
1167
1124
  self._check_for_array()
1168
- self.array = self.array + scalar
1125
+ self.values = self.values + scalar
1169
1126
  return self
1170
1127
 
1171
1128
  def __sub__(self, scalar: int | float) -> "Raster":
1172
1129
  """Subtract the array values with -."""
1173
1130
  self._check_for_array()
1174
- self.array = self.array - scalar
1131
+ self.values = self.values - scalar
1175
1132
  return self
1176
1133
 
1177
1134
  def __truediv__(self, scalar: int | float) -> "Raster":
1178
1135
  """Divide the array values with /."""
1179
1136
  self._check_for_array()
1180
- self.array = self.array / scalar
1137
+ self.values = self.values / scalar
1181
1138
  return self
1182
1139
 
1183
1140
  def __floordiv__(self, scalar: int | float) -> "Raster":
1184
1141
  """Floor divide the array values with //."""
1185
1142
  self._check_for_array()
1186
- self.array = self.array // scalar
1143
+ self.values = self.values // scalar
1187
1144
  return self
1188
1145
 
1189
1146
  def __pow__(self, exponent: int | float) -> "Raster":
1190
1147
  """Exponentiate the array values with **."""
1191
1148
  self._check_for_array()
1192
- self.array = self.array**exponent
1149
+ self.values = self.values**exponent
1193
1150
  return self
1194
1151
 
1195
1152
  def _has_nessecary_attrs(self, dict_like: dict) -> bool:
@@ -1204,11 +1161,11 @@ class Raster:
1204
1161
 
1205
1162
  def _return_self_or_copy(self, array: np.ndarray, copy: bool) -> "Raster":
1206
1163
  if not copy:
1207
- self.array = array
1164
+ self.values = array
1208
1165
  return self
1209
1166
  else:
1210
1167
  copy = self.copy()
1211
- copy.array = array
1168
+ copy.values = array
1212
1169
  return copy
1213
1170
 
1214
1171
  @classmethod
@@ -1251,35 +1208,35 @@ class Raster:
1251
1208
  def _write(
1252
1209
  self, dst: rasterio.io.DatasetReader, window: rasterio.windows.Window
1253
1210
  ) -> None:
1254
- if np.ma.is_masked(self.array):
1255
- if len(self.array.shape) == 2:
1211
+ if np.ma.is_masked(self.values):
1212
+ if len(self.values.shape) == 2:
1256
1213
  return dst.write(
1257
- self.array.filled(self.nodata), indexes=1, window=window
1214
+ self.values.filled(self.nodata), indexes=1, window=window
1258
1215
  )
1259
1216
 
1260
1217
  for i in range(len(self.indexes_as_tuple())):
1261
1218
  dst.write(
1262
- self.array[i].filled(self.nodata),
1219
+ self.values[i].filled(self.nodata),
1263
1220
  indexes=i + 1,
1264
1221
  window=window,
1265
1222
  )
1266
1223
 
1267
1224
  else:
1268
- if len(self.array.shape) == 2:
1269
- return dst.write(self.array, indexes=1, window=window)
1225
+ if len(self.values.shape) == 2:
1226
+ return dst.write(self.values, indexes=1, window=window)
1270
1227
 
1271
1228
  for i, idx in enumerate(self.indexes_as_tuple()):
1272
- dst.write(self.array[i], indexes=idx, window=window)
1229
+ dst.write(self.values[i], indexes=idx, window=window)
1273
1230
 
1274
1231
  def _get_indexes(self, indexes: int | tuple[int] | None) -> int | tuple[int] | None:
1275
1232
  if isinstance(indexes, numbers.Number):
1276
1233
  return int(indexes)
1277
1234
  if indexes is None:
1278
- if self.array is not None and len(self.array.shape) == 3:
1279
- return tuple(i + 1 for i in range(self.array.shape[0]))
1280
- elif self.array is not None and len(self.array.shape) == 2:
1235
+ if self.values is not None and len(self.values.shape) == 3:
1236
+ return tuple(i + 1 for i in range(self.values.shape[0]))
1237
+ elif self.values is not None and len(self.values.shape) == 2:
1281
1238
  return 1
1282
- elif self.array is not None:
1239
+ elif self.values is not None:
1283
1240
  raise ValueError("Array must be 2 or 3 dimensional.")
1284
1241
  else:
1285
1242
  return None
@@ -1320,24 +1277,30 @@ class Raster:
1320
1277
 
1321
1278
  @staticmethod
1322
1279
  def _array_to_geojson(array: np.ndarray, transform: Affine) -> list[tuple]:
1280
+ if np.ma.is_masked(array):
1281
+ array = array.data
1323
1282
  try:
1324
1283
  return [
1325
1284
  (value, shape(geom))
1326
- for geom, value in features.shapes(array, transform=transform)
1285
+ for geom, value in features.shapes(
1286
+ array, transform=transform, mask=None
1287
+ )
1327
1288
  ]
1328
1289
  except ValueError:
1329
1290
  array = array.astype(np.float32)
1330
1291
  return [
1331
1292
  (value, shape(geom))
1332
- for geom, value in features.shapes(array, transform=transform)
1293
+ for geom, value in features.shapes(
1294
+ array, transform=transform, mask=None
1295
+ )
1333
1296
  ]
1334
1297
 
1335
1298
  def _add_indexes_from_array(self, indexes: int | tuple[int]) -> int | tuple[int]:
1336
1299
  if indexes is not None:
1337
1300
  return indexes
1338
- elif len(self.array.shape) == 3:
1339
- return tuple(x + 1 for x in range(len(self.array)))
1340
- elif len(self.array.shape) == 2:
1301
+ elif len(self.values.shape) == 3:
1302
+ return tuple(x + 1 for x in range(len(self.values)))
1303
+ elif len(self.values.shape) == 2:
1341
1304
  return 1
1342
1305
  else:
1343
1306
  raise ValueError
@@ -1364,10 +1327,15 @@ class Raster:
1364
1327
  # except AttributeError:
1365
1328
  # pass
1366
1329
 
1367
- for attr in ["_indexes", "_nodata"]:
1368
- if not hasattr(self, attr) or getattr(self, attr) is None:
1369
- new_value = getattr(src, attr.replace("_", ""))
1370
- setattr(self, attr, new_value)
1330
+ if not hasattr(self, "_indexes") or self._indexes is None:
1331
+ new_value = src.indexes
1332
+ if new_value == 1 or new_value == (1,):
1333
+ new_value = 1
1334
+ self._indexes = new_value
1335
+
1336
+ if not hasattr(self, "_nodata") or self._nodata is None:
1337
+ new_value = src.nodata
1338
+ self._nodata = new_value
1371
1339
 
1372
1340
  # if not hasattr(self, "_indexes") or self._indexes is None:
1373
1341
  # self._indexes = src.indexes
@@ -1407,7 +1375,7 @@ class Raster:
1407
1375
  if hasattr(self, "_warped_crs"):
1408
1376
  src = WarpedVRT(src, crs=self.crs)
1409
1377
 
1410
- self.array = src.read(
1378
+ self.values = src.read(
1411
1379
  out_shape=out_shape,
1412
1380
  **(self.read_kwargs | kwargs),
1413
1381
  )
@@ -1439,11 +1407,17 @@ class Raster:
1439
1407
  if hasattr(self, "_warped_crs"):
1440
1408
  src = WarpedVRT(src, crs=self.crs)
1441
1409
 
1442
- self.array = src.read(out_shape=out_shape, **kwargs)
1410
+ self.values = src.read(out_shape=out_shape, **kwargs)
1443
1411
 
1444
1412
  if not masked:
1445
- self.array[self.array.mask] = self.nodata
1446
- self.array = self.array.data
1413
+ try:
1414
+ self.values[self.values.mask] = self.nodata
1415
+ self.values = self.values.data
1416
+ except AttributeError:
1417
+ pass
1418
+ # self.values = np.ma.masked_array(self.values, mask=mask)
1419
+ # self.values[self.values.mask] = self.nodata
1420
+ # self.values = self.values.data
1447
1421
 
1448
1422
  if boundless:
1449
1423
  self._bounds = src.window_bounds(window=window)
@@ -1454,7 +1428,7 @@ class Raster:
1454
1428
  else:
1455
1429
  self._bounds = intersected.bounds
1456
1430
 
1457
- if not np.size(self.array):
1431
+ if not np.size(self.values):
1458
1432
  return
1459
1433
 
1460
1434
  if self._dtype:
@@ -1462,8 +1436,8 @@ class Raster:
1462
1436
  else:
1463
1437
  self = self.as_minimum_dtype()
1464
1438
 
1465
- if self.array is not None:
1466
- with memfile_from_array(self.array, **self.profile) as src:
1439
+ if self.values is not None:
1440
+ with memfile_from_array(self.values, **self.profile) as src:
1467
1441
  _read(self, src, **kwargs)
1468
1442
  else:
1469
1443
  with opener(self.path, file_system=self.file_system) as file:
@@ -1471,7 +1445,7 @@ class Raster:
1471
1445
  _read(self, src, **kwargs)
1472
1446
 
1473
1447
  def _check_for_array(self, text=""):
1474
- if self.array is None:
1448
+ if self.values is None:
1475
1449
  raise ValueError("Arrays are not loaded. " + text)
1476
1450
 
1477
1451
 
@@ -1499,82 +1473,3 @@ def get_shape_from_bounds(
1499
1473
  width = int(diffx / resx)
1500
1474
  heigth = int(diffy / resy)
1501
1475
  return heigth, width
1502
-
1503
-
1504
- def get_gradient(raster: Raster, degrees: bool = False, copy: bool = False) -> Raster:
1505
- """Get the slope of an elevation raster.
1506
-
1507
- Calculates the absolute slope between the grid cells
1508
- based on the image resolution.
1509
-
1510
- For multiband images, the calculation is done for each band.
1511
-
1512
- Args:
1513
- raster: Raster instance.
1514
- degrees: If False (default), the returned values will be in ratios,
1515
- where a value of 1 means 1 meter up per 1 meter forward. If True,
1516
- the values will be in degrees from 0 to 90.
1517
- copy: Whether to copy or overwrite the original Raster.
1518
- Defaults to False to save memory.
1519
-
1520
- Returns:
1521
- The class instance with new array values, or a copy if copy is True.
1522
-
1523
- Examples:
1524
- --------
1525
- Making an array where the gradient to the center is always 10.
1526
-
1527
- >>> import sgis as sg
1528
- >>> import numpy as np
1529
- >>> arr = np.array(
1530
- ... [
1531
- ... [100, 100, 100, 100, 100],
1532
- ... [100, 110, 110, 110, 100],
1533
- ... [100, 110, 120, 110, 100],
1534
- ... [100, 110, 110, 110, 100],
1535
- ... [100, 100, 100, 100, 100],
1536
- ... ]
1537
- ... )
1538
-
1539
- Now let's create a Raster from this array with a resolution of 10.
1540
-
1541
- >>> r = sg.Raster.from_array(arr, crs=None, bounds=(0, 0, 50, 50), res=10)
1542
-
1543
- The gradient will be 1 (1 meter up for every meter forward).
1544
- The calculation is by default done in place to save memory.
1545
-
1546
- >>> r.gradient()
1547
- >>> r.array
1548
- array([[0., 1., 1., 1., 0.],
1549
- [1., 1., 1., 1., 1.],
1550
- [1., 1., 0., 1., 1.],
1551
- [1., 1., 1., 1., 1.],
1552
- [0., 1., 1., 1., 0.]])
1553
- """
1554
- out_array = []
1555
- for array in raster:
1556
- results = _slope_2d(array, raster.res, degrees=degrees)
1557
- out_array.append(results)
1558
-
1559
- if len(raster.shape) == 2:
1560
- out_array = out_array[0]
1561
- else:
1562
- out_array = np.array(out_array)
1563
-
1564
- return raster._return_self_or_copy(out_array, copy)
1565
-
1566
-
1567
- def _slope_2d(array: np.ndarray, res: int, degrees: int) -> np.ndarray:
1568
- gradient_x, gradient_y = np.gradient(array, res, res)
1569
-
1570
- gradient = abs(gradient_x) + abs(gradient_y)
1571
-
1572
- if not degrees:
1573
- return gradient
1574
-
1575
- radians = np.arctan(gradient)
1576
- degrees = np.degrees(radians)
1577
-
1578
- assert np.max(degrees) <= 90
1579
-
1580
- return degrees