yirgacheffe 1.3.4__py3-none-any.whl → 1.4.1__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.

Potentially problematic release.


This version of yirgacheffe might be problematic. Click here for more details.

yirgacheffe/__init__.py CHANGED
@@ -12,6 +12,3 @@ WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,
12
12
  'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
13
13
  'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
14
14
  'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
15
-
16
- # For legacy reasons [facepalm]
17
- WSG_84_PROJECTION = WGS_84_PROJECTION
@@ -8,6 +8,6 @@ match BACKEND:
8
8
  backend = mlx
9
9
  case "NUMPY":
10
10
  from . import numpy
11
- backend = numpy
11
+ backend = numpy # type: ignore[misc]
12
12
  case _:
13
13
  raise NotImplementedError("Only NUMPY and MLX backends supported")
@@ -1,6 +1,7 @@
1
+ from typing import Callable, Dict
1
2
 
2
3
  import numpy as np
3
- import mlx.core as mx # pylint: disable=E0001,E0611,E0401
4
+ import mlx.core as mx # type: ignore
4
5
  import mlx.nn
5
6
 
6
7
  from .enumeration import operators as op
@@ -182,7 +183,7 @@ def backend_to_dtype(val):
182
183
  def astype_op(data, datatype):
183
184
  return data.astype(dtype_to_backed(datatype))
184
185
 
185
- operator_map = {
186
+ operator_map : Dict[op,Callable] = {
186
187
  op.ADD: mx.array.__add__,
187
188
  op.SUB: mx.array.__sub__,
188
189
  op.MUL: mul_op,
@@ -1,3 +1,4 @@
1
+ from typing import Callable, Dict
1
2
 
2
3
  import numpy as np
3
4
  import torch
@@ -120,7 +121,7 @@ def backend_to_dtype(val):
120
121
  def astype_op(data, datatype):
121
122
  return data.astype(dtype_to_backed(datatype))
122
123
 
123
- operator_map = {
124
+ operator_map : Dict[op,Callable] = {
124
125
  op.ADD: np.ndarray.__add__,
125
126
  op.SUB: np.ndarray.__sub__,
126
127
  op.MUL: np.ndarray.__mul__,
@@ -12,32 +12,3 @@ try:
12
12
  from .h3layer import H3CellLayer
13
13
  except ModuleNotFoundError:
14
14
  pass
15
-
16
- class Layer(RasterLayer):
17
- """A place holder for now, at some point I want to replace Layer with RasterLayer."""
18
-
19
-
20
- class VectorRangeLayer(RasteredVectorLayer):
21
- """Deprecated older name for VectorLayer"""
22
-
23
- def __init__(self, range_vectors: str, where_filter: str, scale: PixelScale, projection: str):
24
- vectors = ogr.Open(range_vectors)
25
- if vectors is None:
26
- raise FileNotFoundError(range_vectors)
27
- layer = vectors.GetLayer()
28
- if where_filter is not None:
29
- layer.SetAttributeFilter(where_filter)
30
- super().__init__(layer, scale, projection)
31
-
32
-
33
- class DynamicVectorRangeLayer(VectorLayer):
34
- """Deprecated older name DynamicVectorLayer"""
35
-
36
- def __init__(self, range_vectors: str, where_filter: str, scale: PixelScale, projection: str):
37
- vectors = ogr.Open(range_vectors)
38
- if vectors is None:
39
- raise FileNotFoundError(range_vectors)
40
- layer = vectors.GetLayer()
41
- if where_filter is not None:
42
- layer.SetAttributeFilter(where_filter)
43
- super().__init__(layer, scale, projection)
@@ -25,7 +25,7 @@ class YirgacheffeLayer(LayerMathMixin):
25
25
 
26
26
  self.reset_window()
27
27
 
28
- def close(self):
28
+ def close(self) -> None:
29
29
  pass
30
30
 
31
31
  def __enter__(self):
@@ -34,6 +34,16 @@ class YirgacheffeLayer(LayerMathMixin):
34
34
  def __exit__(self, exc_type, exc_val, exc_tb):
35
35
  self.close()
36
36
 
37
+ def _park(self) -> None:
38
+ pass
39
+
40
+ def _unpark(self) -> None:
41
+ pass
42
+
43
+ @property
44
+ def _raster_dimensions(self) -> Tuple[int,int]:
45
+ raise AttributeError("Does not have raster")
46
+
37
47
  @property
38
48
  def datatype(self) -> DataType:
39
49
  raise NotImplementedError("Must be overridden by subclass")
@@ -131,9 +141,10 @@ class YirgacheffeLayer(LayerMathMixin):
131
141
  raise ValueError('Window has negative offset')
132
142
  # If there is an underlying raster for this layer, do a sanity check
133
143
  try:
134
- if ((new_window.xoff + new_window.xsize) > self._raster_xsize) or \
135
- ((new_window.yoff + new_window.ysize) > self._raster_ysize):
136
- raise ValueError(f'Window is bigger than dataset: raster is {self._raster_xsize}x{self._raster_ysize}'\
144
+ raster_xsize, raster_ysize = self._raster_dimensions
145
+ if ((new_window.xoff + new_window.xsize) > raster_xsize) or \
146
+ ((new_window.yoff + new_window.ysize) > raster_ysize):
147
+ raise ValueError(f'Window is bigger than dataset: raster is {raster_xsize}x{raster_ysize}'\
137
148
  f', new window is {new_window.xsize - new_window.xoff}x{new_window.ysize - new_window.yoff}')
138
149
  except AttributeError:
139
150
  pass
@@ -162,9 +173,10 @@ class YirgacheffeLayer(LayerMathMixin):
162
173
  raise ValueError('Window has positive offset')
163
174
  # If there is an underlying raster for this layer, do a sanity check
164
175
  try:
165
- if ((new_window.xsize - new_window.xoff) < self._raster_xsize) or \
166
- ((new_window.ysize - new_window.yoff) < self._raster_ysize):
167
- raise ValueError(f'Window is smaller than dataset: raster is {self._raster_xsize}x{self._raster_ysize}'\
176
+ raster_xsize, raster_ysize = self._raster_dimensions
177
+ if ((new_window.xsize - new_window.xoff) < raster_xsize) or \
178
+ ((new_window.ysize - new_window.yoff) <raster_ysize):
179
+ raise ValueError(f'Window is smaller than dataset: raster is {raster_xsize}x{raster_ysize}'\
168
180
  f', new window is {new_window.xsize - new_window.xoff}x{new_window.ysize - new_window.yoff}')
169
181
  except AttributeError:
170
182
  pass
@@ -219,6 +231,7 @@ class YirgacheffeLayer(LayerMathMixin):
219
231
  raise NotImplementedError("Must be overridden by subclass")
220
232
 
221
233
  def read_array_for_area(self, target_area: Area, x: int, y: int, width: int, height: int) -> Any:
234
+ assert self._pixel_scale is not None
222
235
 
223
236
  target_window = Window(
224
237
  xoff=round_down_pixels((target_area.left - self._underlying_area.left) / self._pixel_scale.xstep,
@@ -3,7 +3,7 @@ from typing import Any, Union
3
3
  from ..operators import DataType
4
4
  from ..window import Area, PixelScale, Window
5
5
  from .base import YirgacheffeLayer
6
- from ..backends import backend
6
+ from .._backends import backend
7
7
  from .. import WGS_84_PROJECTION
8
8
 
9
9
 
@@ -1,7 +1,8 @@
1
+ from __future__ import annotations
1
2
  import copy
2
3
  import glob
3
4
  import os
4
- from typing import Any, List, Optional, TypeVar
5
+ from typing import Any, List, Optional
5
6
 
6
7
  import numpy as np
7
8
  from yirgacheffe.operators import DataType
@@ -10,9 +11,8 @@ from ..rounding import are_pixel_scales_equal_enough, round_down_pixels
10
11
  from ..window import Area, Window
11
12
  from .base import YirgacheffeLayer
12
13
  from .rasters import RasterLayer
13
- from ..backends import backend
14
+ from .._backends import backend
14
15
 
15
- GroupLayerT = TypeVar("GroupLayerT", bound="GroupLayer")
16
16
 
17
17
  class GroupLayerEmpty(ValueError):
18
18
  def __init__(self, msg):
@@ -26,7 +26,7 @@ class GroupLayer(YirgacheffeLayer):
26
26
  directory_path: str,
27
27
  name: Optional[str] = None,
28
28
  matching: str = "*.tif"
29
- ) -> GroupLayerT:
29
+ ) -> GroupLayer:
30
30
  if directory_path is None:
31
31
  raise ValueError("Directory path is None")
32
32
  files = [os.path.join(directory_path, x) for x in glob.glob(matching, root_dir=directory_path)]
@@ -35,12 +35,12 @@ class GroupLayer(YirgacheffeLayer):
35
35
  return cls.layer_from_files(files, name)
36
36
 
37
37
  @classmethod
38
- def layer_from_files(cls, filenames: List[str], name: Optional[str] = None) -> GroupLayerT:
38
+ def layer_from_files(cls, filenames: List[str], name: Optional[str] = None) -> GroupLayer:
39
39
  if filenames is None:
40
40
  raise ValueError("filenames argument is None")
41
41
  if len(filenames) < 1:
42
42
  raise GroupLayerEmpty("No files found")
43
- rasters = [RasterLayer.layer_from_file(x) for x in filenames]
43
+ rasters: List[YirgacheffeLayer] = [RasterLayer.layer_from_file(x) for x in filenames]
44
44
  return cls(rasters, name)
45
45
 
46
46
  def __init__(self, layers: List[YirgacheffeLayer], name: Optional[str] = None) -> None:
@@ -65,7 +65,7 @@ class GroupLayer(YirgacheffeLayer):
65
65
  self._underlying_layers.reverse()
66
66
  self.layers = self._underlying_layers
67
67
 
68
- def _park(self):
68
+ def _park(self) -> None:
69
69
  for layer in self.layers:
70
70
  try:
71
71
  layer._park()
@@ -157,7 +157,7 @@ class GroupLayer(YirgacheffeLayer):
157
157
  class TileData:
158
158
  """This class exists just to let me sort the tiles into the correct order for processing."""
159
159
 
160
- def __init__(self, data, x, y):
160
+ def __init__(self, data: Any, x: int, y: int):
161
161
  self.data = data
162
162
  self.x = x
163
163
  self.y = y
@@ -219,7 +219,7 @@ class TiledGroupLayer(GroupLayer):
219
219
  ysize
220
220
  )
221
221
 
222
- partials = []
222
+ partials: List[TileData] = []
223
223
  for layer in self.layers:
224
224
  # Normally this is hidden with set_window_for_...
225
225
  adjusted_layer_window = Window(
@@ -251,7 +251,7 @@ class TiledGroupLayer(GroupLayer):
251
251
  # the "obvious" tile. In which case, we should reject the smaller section. If we have
252
252
  # two tiles at the same offset and one is not a perfect subset of the other then the
253
253
  # tile set we were given is not regularly shaped, and so we should give up.
254
- combed_partials = []
254
+ combed_partials: List[TileData] = []
255
255
  previous_tile = None
256
256
  for tile in sorted_partials:
257
257
  if previous_tile is None:
@@ -276,7 +276,7 @@ class TiledGroupLayer(GroupLayer):
276
276
  expected_next_x = 0
277
277
  expected_next_y = 0
278
278
  data = None
279
- row_chunk = None
279
+ row_chunk: Optional[np.ndarray] = None
280
280
 
281
281
  # Allow for reading off top
282
282
  if combed_partials:
@@ -337,6 +337,7 @@ class TiledGroupLayer(GroupLayer):
337
337
  data = np.vstack((data, row_chunk))
338
338
  expected_next_y += last_y_height
339
339
  else:
340
+ assert last_y_offset is not None
340
341
  diff = expected_next_y - last_y_offset
341
342
  assert diff > 0, f"{expected_next_y} - {last_y_offset} <= 0 (aka {diff})"
342
343
  subdata = np.delete(row_chunk, np.s_[0:diff], 0)
@@ -351,8 +352,10 @@ class TiledGroupLayer(GroupLayer):
351
352
  last_y_height = tile.data.shape[0]
352
353
  expected_next_x = tile.data.shape[1] + tile.x
353
354
 
355
+ assert last_y_offset is not None
354
356
  if (last_y_offset + last_y_height) < ysize:
355
357
  data = np.vstack((data, np.zeros((ysize - (last_y_offset + last_y_height), xsize))))
356
358
 
359
+ assert data is not None
357
360
  assert data.shape == (ysize, xsize)
358
361
  return backend.promote(data)
@@ -1,5 +1,5 @@
1
1
  from math import ceil, floor
2
- from typing import Any
2
+ from typing import Any, Tuple
3
3
 
4
4
  import h3
5
5
  import numpy as np
@@ -8,7 +8,7 @@ from yirgacheffe.operators import DataType
8
8
  from ..rounding import round_up_pixels
9
9
  from ..window import Area, PixelScale, Window
10
10
  from .base import YirgacheffeLayer
11
- from ..backends import backend
11
+ from .._backends import backend
12
12
 
13
13
  class H3CellLayer(YirgacheffeLayer):
14
14
 
@@ -74,12 +74,17 @@ class H3CellLayer(YirgacheffeLayer):
74
74
  (sorted_lats[1] / abs_ystep) * abs_ystep,
75
75
  )
76
76
 
77
+ @property
78
+ def _raster_dimensions(self) -> Tuple[int,int]:
79
+ return (self._raster_xsize, self._raster_ysize)
77
80
 
78
81
  @property
79
82
  def datatype(self) -> DataType:
80
83
  return DataType.Float64
81
84
 
82
85
  def read_array_with_window(self, xoffset: int, yoffset: int, xsize: int, ysize: int, window: Window) -> Any:
86
+ assert self._pixel_scale is not None
87
+
83
88
  if (xsize <= 0) or (ysize <= 0):
84
89
  raise ValueError("Request dimensions must be positive and non-zero")
85
90
 
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
1
2
  import math
2
3
  import os
3
- from typing import Any, Optional, TypeVar, Union
4
+ from typing import Any, Optional, Tuple, Union
4
5
 
5
6
  import numpy as np
6
7
  from osgeo import gdal
@@ -10,10 +11,7 @@ from ..window import Area, PixelScale, Window
10
11
  from ..rounding import round_up_pixels
11
12
  from .base import YirgacheffeLayer
12
13
  from ..operators import DataType
13
- from ..backends import backend
14
-
15
- # Still to early to require Python 3.11 :/
16
- RasterLayerT = TypeVar("RasterLayerT", bound="RasterLayer")
14
+ from .._backends import backend
17
15
 
18
16
  class InvalidRasterBand(Exception):
19
17
  def __init__ (self, band):
@@ -37,7 +35,7 @@ class RasterLayer(YirgacheffeLayer):
37
35
  nbits: Optional[int]=None,
38
36
  threads: Optional[int]=None,
39
37
  bands: int=1
40
- ) -> RasterLayerT:
38
+ ) -> RasterLayer:
41
39
  abs_xstep, abs_ystep = abs(scale.xstep), abs(scale.ystep)
42
40
 
43
41
  # We treat the provided area as aspirational, and we need to align it to pixel boundaries
@@ -50,7 +48,9 @@ class RasterLayer(YirgacheffeLayer):
50
48
 
51
49
  # This used to the the GDAL type, so we support that for legacy reasons
52
50
  if isinstance(datatype, int):
53
- datatype = DataType.of_gdal(datatype)
51
+ datatype_arg = DataType.of_gdal(datatype)
52
+ else:
53
+ datatype_arg = datatype
54
54
 
55
55
  options = []
56
56
  if threads is not None:
@@ -74,7 +74,7 @@ class RasterLayer(YirgacheffeLayer):
74
74
  round_up_pixels((pixel_friendly_area.right - pixel_friendly_area.left) / abs_xstep, abs_xstep),
75
75
  round_up_pixels((pixel_friendly_area.top - pixel_friendly_area.bottom) / abs_ystep, abs_ystep),
76
76
  bands,
77
- datatype.to_gdal(),
77
+ datatype_arg.to_gdal(),
78
78
  options
79
79
  )
80
80
  dataset.SetGeoTransform([
@@ -96,7 +96,7 @@ class RasterLayer(YirgacheffeLayer):
96
96
  nbits: Optional[int]=None,
97
97
  threads: Optional[int]=None,
98
98
  bands: int=1
99
- ) -> RasterLayerT:
99
+ ) -> RasterLayer:
100
100
  width = layer.window.xsize
101
101
  height = layer.window.ysize
102
102
  if area is None:
@@ -117,6 +117,13 @@ class RasterLayer(YirgacheffeLayer):
117
117
  area.left, scale.xstep, 0.0, area.top, 0.0, scale.ystep
118
118
  )
119
119
 
120
+ if datatype is None:
121
+ datatype_arg = layer.datatype
122
+ elif isinstance(datatype, int):
123
+ datatype_arg = DataType.of_gdal(datatype)
124
+ else:
125
+ datatype_arg = datatype
126
+
120
127
  options = []
121
128
  if threads is not None:
122
129
  options.append(f"NUM_THREADS={threads}")
@@ -139,7 +146,7 @@ class RasterLayer(YirgacheffeLayer):
139
146
  width,
140
147
  height,
141
148
  bands,
142
- (datatype if datatype is not None else layer.datatype).to_gdal(),
149
+ (datatype_arg).to_gdal(),
143
150
  options,
144
151
  )
145
152
  dataset.SetGeoTransform(geo_transform)
@@ -152,13 +159,15 @@ class RasterLayer(YirgacheffeLayer):
152
159
  @classmethod
153
160
  def scaled_raster_from_raster(
154
161
  cls,
155
- source: RasterLayerT,
162
+ source: RasterLayer,
156
163
  new_pixel_scale: PixelScale,
157
164
  filename: Optional[str]=None,
158
165
  compress: bool=True,
159
166
  algorithm: int=gdal.GRA_NearestNeighbour,
160
- ) -> RasterLayerT:
167
+ ) -> RasterLayer:
161
168
  source_dataset = source._dataset
169
+ assert source_dataset is not None
170
+
162
171
  old_pixel_scale = source.pixel_scale
163
172
  assert old_pixel_scale
164
173
 
@@ -205,7 +214,7 @@ class RasterLayer(YirgacheffeLayer):
205
214
  return RasterLayer(dataset)
206
215
 
207
216
  @classmethod
208
- def layer_from_file(cls, filename: str, band: int = 1) -> RasterLayerT:
217
+ def layer_from_file(cls, filename: str, band: int = 1) -> RasterLayer:
209
218
  try:
210
219
  dataset = gdal.Open(filename, gdal.GA_ReadOnly)
211
220
  except RuntimeError as exc:
@@ -248,6 +257,10 @@ class RasterLayer(YirgacheffeLayer):
248
257
  self._raster_xsize = dataset.RasterXSize
249
258
  self._raster_ysize = dataset.RasterYSize
250
259
 
260
+ @property
261
+ def _raster_dimensions(self) -> Tuple[int,int]:
262
+ return (self._raster_xsize, self._raster_ysize)
263
+
251
264
  def close(self):
252
265
  try:
253
266
  if self._dataset:
@@ -274,10 +287,8 @@ class RasterLayer(YirgacheffeLayer):
274
287
  self._unpark()
275
288
 
276
289
  def _park(self):
277
- try:
290
+ if self._dataset is not None:
278
291
  self._dataset.Close()
279
- except AttributeError:
280
- pass
281
292
  self._dataset = None
282
293
 
283
294
  def _unpark(self):
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  from math import floor, ceil
2
3
  from typing import Any, Optional
3
4
 
@@ -6,7 +7,7 @@ from yirgacheffe.operators import DataType
6
7
 
7
8
  from ..window import PixelScale, Window
8
9
  from .rasters import RasterLayer, YirgacheffeLayer
9
- from ..backends import backend
10
+ from .._backends import backend
10
11
 
11
12
 
12
13
  class RescaledRasterLayer(YirgacheffeLayer):
@@ -20,7 +21,7 @@ class RescaledRasterLayer(YirgacheffeLayer):
20
21
  pixel_scale: PixelScale,
21
22
  band: int = 1,
22
23
  nearest_neighbour: bool = True,
23
- ):
24
+ ) -> RescaledRasterLayer:
24
25
  src = RasterLayer.layer_from_file(filename, band=band)
25
26
  return RescaledRasterLayer(src, pixel_scale, nearest_neighbour, src.name)
26
27
 
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import os
2
3
  from math import ceil, floor
3
4
  from typing import Any, Optional, Tuple, Union
@@ -9,9 +10,9 @@ from ..operators import DataType
9
10
  from ..window import Area, PixelScale
10
11
  from .base import YirgacheffeLayer
11
12
  from .rasters import RasterLayer
12
- from ..backends import backend
13
+ from .._backends import backend
13
14
 
14
- def _validate_burn_value(burn_value: Any, layer: ogr.Layer) -> int: # pylint: disable=R0911
15
+ def _validate_burn_value(burn_value: Any, layer: ogr.Layer) -> DataType: # pylint: disable=R0911
15
16
  if isinstance(burn_value, str):
16
17
  # burn value is field name, so validate it
17
18
  index = layer.FindFieldIndex(burn_value, True)
@@ -23,32 +24,32 @@ def _validate_burn_value(burn_value: Any, layer: ogr.Layer) -> int: # pylint: di
23
24
  field = definition.GetFieldDefn(index)
24
25
  typename = field.GetTypeName()
25
26
  if typename == "Integer":
26
- return gdal.GDT_Int64
27
+ return DataType.Int64
27
28
  elif typename == "Real":
28
- return gdal.GDT_Float64
29
+ return DataType.Float64
29
30
  else:
30
31
  raise ValueError(f"Can't set datatype {typename} for burn value {burn_value}")
31
32
  elif isinstance(burn_value, int):
32
33
  if 0 <= burn_value <= 255:
33
- return gdal.GDT_Byte
34
+ return DataType.Byte
34
35
  else:
35
36
  unsigned = burn_value > 0
36
37
  if unsigned:
37
38
  if burn_value < (pow(2, 16)):
38
- return gdal.GDT_UInt16
39
+ return DataType.UInt16
39
40
  elif burn_value < (pow(2, 32)):
40
- return gdal.GDT_UInt32
41
+ return DataType.UInt32
41
42
  else:
42
- return gdal.GDT_UInt64
43
+ return DataType.UInt64
43
44
  else:
44
45
  if abs(burn_value) < (pow(2, 15)):
45
- return gdal.GDT_Int16
46
+ return DataType.Int16
46
47
  elif abs(burn_value) < (pow(2, 31)):
47
- return gdal.GDT_Int32
48
+ return DataType.Int32
48
49
  else:
49
- return gdal.GDT_Int64
50
+ return DataType.Int64
50
51
  elif isinstance(burn_value, float):
51
- return gdal.GDT_Float64
52
+ return DataType.Float64
52
53
  else:
53
54
  raise ValueError(f"data type of burn value {burn_value} not supported")
54
55
 
@@ -59,7 +60,7 @@ class RasteredVectorLayer(RasterLayer):
59
60
  VectorLayer."""
60
61
 
61
62
  @classmethod
62
- def layer_from_file(
63
+ def layer_from_file( # type: ignore[override] # pylint: disable=W0221
63
64
  cls,
64
65
  filename: str,
65
66
  where_filter: Optional[str],
@@ -67,7 +68,7 @@ class RasteredVectorLayer(RasterLayer):
67
68
  projection: str,
68
69
  datatype: Optional[Union[int, DataType]] = None,
69
70
  burn_value: Union[int,float,str] = 1,
70
- ): # pylint: disable=W0221
71
+ ) -> RasteredVectorLayer:
71
72
  vectors = ogr.Open(filename)
72
73
  if vectors is None:
73
74
  raise FileNotFoundError(filename)
@@ -77,16 +78,17 @@ class RasteredVectorLayer(RasterLayer):
77
78
 
78
79
  estimated_datatype = _validate_burn_value(burn_value, layer)
79
80
  if datatype is None:
80
- datatype = estimated_datatype
81
-
82
- if isinstance(datatype, int):
83
- datatype = DataType.of_gdal(datatype)
81
+ datatype_arg: DataType = estimated_datatype
82
+ elif isinstance(datatype, int):
83
+ datatype_arg = DataType.of_gdal(datatype)
84
+ else:
85
+ datatype_arg = datatype
84
86
 
85
87
  vector_layer = RasteredVectorLayer(
86
88
  layer,
87
89
  scale,
88
90
  projection,
89
- datatype=datatype,
91
+ datatype=datatype_arg,
90
92
  burn_value=burn_value
91
93
  )
92
94
 
@@ -108,10 +110,12 @@ class RasteredVectorLayer(RasterLayer):
108
110
  raise ValueError('No layer provided')
109
111
  self.layer = layer
110
112
 
111
- self._original = None
113
+ self._original: Optional[Any] = None
112
114
 
113
115
  if isinstance(datatype, int):
114
- datatype = DataType.of_gdal(datatype)
116
+ datatype_arg = DataType.of_gdal(datatype)
117
+ else:
118
+ datatype_arg = datatype
115
119
 
116
120
  # work out region for mask
117
121
  envelopes = []
@@ -142,7 +146,7 @@ class RasteredVectorLayer(RasterLayer):
142
146
  round((area.right - area.left) / abs_xstep),
143
147
  round((area.top - area.bottom) / abs_ystep),
144
148
  1,
145
- datatype.to_gdal(),
149
+ datatype_arg.to_gdal(),
146
150
  []
147
151
  )
148
152
  if not dataset:
@@ -173,7 +177,7 @@ class VectorLayer(YirgacheffeLayer):
173
177
  where_filter: Optional[str]=None,
174
178
  datatype: Optional[Union[int, DataType]] = None,
175
179
  burn_value: Union[int,float,str] = 1,
176
- ):
180
+ ) -> VectorLayer:
177
181
  if other_layer is None:
178
182
  raise ValueError("like layer can not be None")
179
183
  if other_layer.pixel_scale is None:
@@ -208,7 +212,6 @@ class VectorLayer(YirgacheffeLayer):
208
212
  vector_layer._filter = where_filter
209
213
  return vector_layer
210
214
 
211
-
212
215
  @classmethod
213
216
  def layer_from_file(
214
217
  cls,
@@ -219,7 +222,7 @@ class VectorLayer(YirgacheffeLayer):
219
222
  datatype: Optional[Union[int, DataType]] = None,
220
223
  burn_value: Union[int,float,str] = 1,
221
224
  anchor: Tuple[float,float] = (0.0, 0.0)
222
- ):
225
+ ) -> VectorLayer:
223
226
  vectors = ogr.Open(filename)
224
227
  if vectors is None:
225
228
  raise FileNotFoundError(filename)
@@ -232,14 +235,16 @@ class VectorLayer(YirgacheffeLayer):
232
235
  datatype = estimated_datatype
233
236
 
234
237
  if isinstance(datatype, int):
235
- datatype = DataType.of_gdal(datatype)
238
+ datatype_arg = DataType.of_gdal(datatype)
239
+ else:
240
+ datatype_arg = datatype
236
241
 
237
242
  vector_layer = VectorLayer(
238
243
  layer,
239
244
  scale,
240
245
  projection,
241
246
  name=filename,
242
- datatype=datatype,
247
+ datatype=datatype_arg,
243
248
  burn_value=burn_value,
244
249
  anchor=anchor
245
250
  )
@@ -277,8 +282,8 @@ class VectorLayer(YirgacheffeLayer):
277
282
  self.burn_value = burn_value
278
283
 
279
284
  self._original = None
280
- self._dataset_path = None
281
- self._filter = None
285
+ self._dataset_path: Optional[str] = None
286
+ self._filter: Optional[str] = None
282
287
 
283
288
  # work out region for mask
284
289
  envelopes = []
@@ -318,7 +323,7 @@ class VectorLayer(YirgacheffeLayer):
318
323
 
319
324
  def __getstate__(self) -> object:
320
325
  # Only support pickling on file backed layers (ideally read only ones...)
321
- if not os.path.isfile(self._dataset_path):
326
+ if self._dataset_path is None or not os.path.isfile(self._dataset_path):
322
327
  raise ValueError("Can not pickle layer that is not file backed.")
323
328
  odict = self.__dict__.copy()
324
329
  del odict['_original']
@@ -353,6 +358,8 @@ class VectorLayer(YirgacheffeLayer):
353
358
  return self._datatype
354
359
 
355
360
  def read_array_for_area(self, target_area: Area, x: int, y: int, width: int, height: int) -> Any:
361
+ assert self._pixel_scale is not None
362
+
356
363
  if self._original is None:
357
364
  self._unpark()
358
365
  if (width <= 0) or (height <= 0):
yirgacheffe/operators.py CHANGED
@@ -7,18 +7,18 @@ import types
7
7
  from enum import Enum
8
8
  from multiprocessing import Semaphore, Process
9
9
  from multiprocessing.managers import SharedMemoryManager
10
- from typing import Optional
10
+ from typing import Callable, Optional
11
11
 
12
12
  import numpy as np
13
13
  from osgeo import gdal
14
- from dill import dumps, loads
14
+ from dill import dumps, loads # type: ignore
15
15
 
16
16
  from . import constants
17
17
  from .rounding import are_pixel_scales_equal_enough, round_up_pixels, round_down_pixels
18
18
  from .window import Area, PixelScale, Window
19
- from .backends import backend
20
- from .backends.enumeration import operators as op
21
- from .backends.enumeration import dtype as DataType
19
+ from ._backends import backend
20
+ from ._backends.enumeration import operators as op
21
+ from ._backends.enumeration import dtype as DataType
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
  logger.setLevel(logging.WARNING)
@@ -320,7 +320,7 @@ class LayerOperation(LayerMathMixin):
320
320
  else:
321
321
  self.other = None
322
322
 
323
- def __str__(self):
323
+ def __str__(self) -> str:
324
324
  try:
325
325
  return f"({self.lhs} {self.operator} {self.rhs})"
326
326
  except AttributeError:
@@ -329,7 +329,7 @@ class LayerOperation(LayerMathMixin):
329
329
  except AttributeError:
330
330
  return str(self.lhs)
331
331
 
332
- def __len__(self):
332
+ def __len__(self) -> int:
333
333
  return len(self.lhs)
334
334
 
335
335
  def __getstate__(self) -> object:
@@ -339,22 +339,22 @@ class LayerOperation(LayerMathMixin):
339
339
  del odict['operator']
340
340
  return odict
341
341
 
342
- def __setstate__(self, state):
342
+ def __setstate__(self, state) -> None:
343
343
  if 'operator_dill' in state:
344
344
  state['operator'] = loads(state['operator_dill'])
345
345
  del state['operator_dill']
346
346
  self.__dict__.update(state)
347
347
 
348
348
  @property
349
- def area(self) -> Area:
349
+ def area(self) -> Optional[Area]:
350
350
  # The type().__name__ here is to avoid a circular import dependancy
351
351
  lhs_area = self.lhs.area if not type(self.lhs).__name__ == "ConstantLayer" else None
352
352
  try:
353
- rhs_area = self.rhs.area if not type(self.rhs).__name__ == "ConstantLayer" else None
353
+ rhs_area = self.rhs.area if not type(self.rhs).__name__ == "ConstantLayer" else None # type: ignore[return-value] # pylint: disable=C0301
354
354
  except AttributeError:
355
355
  rhs_area = None
356
356
  try:
357
- other_area = self.other.area if not type(self.other).__name__ == "ConstantLayer" else None
357
+ other_area = self.other.area if not type(self.other).__name__ == "ConstantLayer" else None # type: ignore[return-value] # pylint: disable=C0301
358
358
  except AttributeError:
359
359
  other_area = None
360
360
 
@@ -391,6 +391,8 @@ class LayerOperation(LayerMathMixin):
391
391
  right=max(x.right for x in all_areas),
392
392
  bottom=min(x.bottom for x in all_areas)
393
393
  )
394
+ case _:
395
+ assert False, "Should not be reached"
394
396
 
395
397
  @property
396
398
  def pixel_scale(self) -> PixelScale:
@@ -409,6 +411,7 @@ class LayerOperation(LayerMathMixin):
409
411
  def window(self) -> Window:
410
412
  pixel_scale = self.pixel_scale
411
413
  area = self.area
414
+ assert area is not None
412
415
 
413
416
  return Window(
414
417
  xoff=round_down_pixels(area.left / pixel_scale.xstep, pixel_scale.xstep),
@@ -450,7 +453,7 @@ class LayerOperation(LayerMathMixin):
450
453
  return lhs_data
451
454
 
452
455
  try:
453
- operator = backend.operator_map[self.operator]
456
+ operator: Callable = backend.operator_map[self.operator]
454
457
  except KeyError:
455
458
  # Handles things like `numpy_apply` where a custom operator is provided
456
459
  operator = self.operator
yirgacheffe/window.py CHANGED
@@ -2,12 +2,36 @@ import math
2
2
  import sys
3
3
  from collections import namedtuple
4
4
  from dataclasses import dataclass
5
- from typing import List, Optional
5
+ from typing import List, Optional, Tuple
6
6
 
7
7
  PixelScale = namedtuple('PixelScale', ['xstep', 'ystep'])
8
8
 
9
9
  @dataclass
10
10
  class Area:
11
+ """Class to hold a geospatial area of data in the given projection.
12
+
13
+ Parameters
14
+ ----------
15
+ left : float
16
+ Left most point in the projection space
17
+ top : float
18
+ Top most point in the projection space
19
+ right : float
20
+ Right most point in the projection space
21
+ bottom : float
22
+ Bottom most point in the projection space
23
+
24
+ Attributes
25
+ ----------
26
+ left : float
27
+ Left most point in the projection space
28
+ top : float
29
+ Top most point in the projection space
30
+ right : float
31
+ Right most point in the projection space
32
+ bottom : float
33
+ Bottom most point in the projection space
34
+ """
11
35
  left: float
12
36
  top: float
13
37
  right: float
@@ -23,6 +47,21 @@ class Area:
23
47
  math.isclose(self.bottom, other.bottom, abs_tol=1e-09)
24
48
 
25
49
  def grow(self, offset: float):
50
+ """Expand the area in all directions by the given amount.
51
+
52
+ Generates a new area that is an expanded version of the current area.
53
+
54
+ Parameters
55
+ ----------
56
+ offset : float
57
+ The amount by which to grow the area
58
+
59
+ Returns
60
+ -------
61
+ Area
62
+ A new area of the expanded size.
63
+
64
+ """
26
65
  return Area(
27
66
  left=self.left - offset,
28
67
  top=self.top + offset,
@@ -31,6 +70,18 @@ class Area:
31
70
  )
32
71
 
33
72
  def overlaps(self, other) -> bool:
73
+ """Check if this area overlaps with another area.
74
+
75
+ Parameters
76
+ ----------
77
+ other : Area
78
+ The other area to compare this area with
79
+
80
+ Returns
81
+ -------
82
+ bool
83
+ True if the two areas intersect, otherwise false.
84
+ """
34
85
  return (
35
86
  (self.left <= other.left <= self.right) or
36
87
  (self.left <= other.right <= self.right) or
@@ -45,13 +96,38 @@ class Area:
45
96
 
46
97
  @dataclass
47
98
  class Window:
99
+ """Class to hold the pixel dimensions of data in the given projection.
100
+
101
+ Parameters
102
+ ----------
103
+ xoff : int
104
+ X axis offset
105
+ yoff : int
106
+ Y axis offset
107
+ xsize : int
108
+ Width of data in pixels
109
+ bottom : float
110
+ Height of data in pixels
111
+
112
+ Attributes
113
+ ----------
114
+ xoff : int
115
+ X axis offset
116
+ yoff : int
117
+ Y axis offset
118
+ xsize : int
119
+ Width of data in pixels
120
+ bottom : float
121
+ Height of data in pixels
122
+ """
48
123
  xoff: int
49
124
  yoff: int
50
125
  xsize: int
51
126
  ysize: int
52
127
 
53
128
  @property
54
- def as_array_args(self):
129
+ def as_array_args(self) -> Tuple[int,...]:
130
+ """A tuple containing xoff, yoff, xsize, and ysize."""
55
131
  return (self.xoff, self.yoff, self.xsize, self.ysize)
56
132
 
57
133
  def __lt__(self, other) -> bool:
@@ -87,6 +163,21 @@ class Window:
87
163
  ((self.yoff + self.ysize) >= (other.yoff + other.ysize))
88
164
 
89
165
  def grow(self, pixels: int):
166
+ """Expand the area in all directions by the given amount.
167
+
168
+ Generates a new window that is an expanded version of the current window.
169
+
170
+ Parameters
171
+ ----------
172
+ pixels : int
173
+ The amount by which to grow the window in pixels
174
+
175
+ Returns
176
+ -------
177
+ Window
178
+ A new window of the expanded size.
179
+
180
+ """
90
181
  return Window(
91
182
  xoff=self.xoff - pixels,
92
183
  yoff=self.xoff - pixels,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.3.4
3
+ Version: 1.4.1
4
4
  Summary: Abstraction of gdal datasets for doing basic math operations
5
5
  Author-email: Michael Dales <mwd24@cam.ac.uk>
6
6
  License-Expression: ISC
@@ -13,6 +13,7 @@ Requires-Dist: numpy
13
13
  Requires-Dist: gdal[numpy]
14
14
  Requires-Dist: scikit-image
15
15
  Requires-Dist: torch
16
+ Requires-Dist: dill
16
17
  Provides-Extra: dev
17
18
  Requires-Dist: mypy; extra == "dev"
18
19
  Requires-Dist: pylint; extra == "dev"
@@ -0,0 +1,24 @@
1
+ yirgacheffe/__init__.py,sha256=nrVZPE_4DgReqmqEOfUaXJiRtxjOt-OJSIVi51Z5k98,587
2
+ yirgacheffe/constants.py,sha256=WccPcISG1FqL_Kw1tI72BJGjHy6RvEcGEx_I9RK776U,42
3
+ yirgacheffe/operators.py,sha256=j5Ts9dxqcxq_bL7Mw-n6vTg0SP5rKpqseVcJTD8PaAY,29363
4
+ yirgacheffe/rounding.py,sha256=ggBG4lMyLMtHLW3dBxr3gBCcF2qhRrY5etZiFGlIoqA,2258
5
+ yirgacheffe/window.py,sha256=PVh9EIg1PMcUENjC2yahmrolGWaJP4OeM44uKqQ6I0U,7504
6
+ yirgacheffe/_backends/__init__.py,sha256=jN-2iRrHStnPI6cNL7XhwhsROtI0EaGfIrbF5c-ECV0,334
7
+ yirgacheffe/_backends/enumeration.py,sha256=pADawllxpW_hW-IVVvZpHWIKzvEMs9aaqfkZRD1zjnY,1003
8
+ yirgacheffe/_backends/mlx.py,sha256=2vOTMqHbQbeqt81Eq_8hxWDXZHaPsDpbXkALRVGEnnw,6130
9
+ yirgacheffe/_backends/numpy.py,sha256=cYO628s4-5K_-Bp3CrnHegzYSZfkt2QC8iE9oOOMtvA,4069
10
+ yirgacheffe/layers/__init__.py,sha256=mYKjw5YTcMNv_hMy7a6K4yRzIuNUbR8WuBTw4WIAmSk,435
11
+ yirgacheffe/layers/area.py,sha256=yIRXzeeLi3MMyuh4LG_VgZrKNWe5xwZgDGdgaoYRpP0,3805
12
+ yirgacheffe/layers/base.py,sha256=l2spGsDsv2DaUqkK-nuJCC97fw0kVWaLntvvLqZgJS4,11959
13
+ yirgacheffe/layers/constant.py,sha256=LWQuGGuUkJirLoQomZHs44oksmMKcvNGM8pOuRk9vHo,1427
14
+ yirgacheffe/layers/group.py,sha256=Fys5BvYmWRQMc95YE8Gy6bxxZRqvHUU7gILFE_X-CpY,15330
15
+ yirgacheffe/layers/h3layer.py,sha256=kWDqs6fIkHLR7gDL2E6F5y9-XxT6ws4M3Tj_7qF090U,9863
16
+ yirgacheffe/layers/rasters.py,sha256=I9bRuqb0W8EEvdSq3rAeKNmezIRK-yajFaHtDFR81sw,12630
17
+ yirgacheffe/layers/rescaled.py,sha256=v3ZXo3aNNDP4b8O6QnQHgrhBu0GslTgiZQmy6O8vb8k,3004
18
+ yirgacheffe/layers/vectors.py,sha256=3QWjg0E9deZyLIBp-BewgDGY90Ufqd6OcUA7jzJXJPo,15515
19
+ yirgacheffe-1.4.1.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
20
+ yirgacheffe-1.4.1.dist-info/METADATA,sha256=TZlmFtOF9UJXDm3jkJSWwb-pRjbKx2fF8Bh9kXK40Ns,20516
21
+ yirgacheffe-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ yirgacheffe-1.4.1.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
23
+ yirgacheffe-1.4.1.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
24
+ yirgacheffe-1.4.1.dist-info/RECORD,,
yirgacheffe/h3layer.py DELETED
@@ -1,2 +0,0 @@
1
- # for legacy compatibility
2
- from .layers.h3layer import H3CellLayer # pylint: disable=W0611
@@ -1,25 +0,0 @@
1
- yirgacheffe/__init__.py,sha256=U5AoPk_iWreSCexG2ID-tmSXiJz4_9Lvzbf42DMvT7k,658
2
- yirgacheffe/constants.py,sha256=WccPcISG1FqL_Kw1tI72BJGjHy6RvEcGEx_I9RK776U,42
3
- yirgacheffe/h3layer.py,sha256=MT2hm6n64hzHSeRPvjn-CwErru937ntKXbEU7CIlPSU,91
4
- yirgacheffe/operators.py,sha256=KIMYmRl0uJG9LYnmX_uwHhGNfCNOEZlfaXZU7kD1aL4,29081
5
- yirgacheffe/rounding.py,sha256=ggBG4lMyLMtHLW3dBxr3gBCcF2qhRrY5etZiFGlIoqA,2258
6
- yirgacheffe/window.py,sha256=0XZdwD4mz0bRU9eBhFY1Xk1hQt6FqCKp3BnUgxZup3c,5224
7
- yirgacheffe/backends/__init__.py,sha256=149-fg1PVXC36cgyuSZsU8SYOm65fzUmYN_MHZtEyrY,313
8
- yirgacheffe/backends/enumeration.py,sha256=pADawllxpW_hW-IVVvZpHWIKzvEMs9aaqfkZRD1zjnY,1003
9
- yirgacheffe/backends/mlx.py,sha256=3HSKU7ZU846trdxeVhPj2OPkFWrCxfbjB65-NpnmANQ,6097
10
- yirgacheffe/backends/numpy.py,sha256=qQYvff1oHIuGUimV04rcFyPxnwEf0acvVd3ijeom4T4,4015
11
- yirgacheffe/layers/__init__.py,sha256=GR_TJlhPKDK1212CG2T99X2FdBlNo_G8q2zQ6nJbiO4,1534
12
- yirgacheffe/layers/area.py,sha256=yIRXzeeLi3MMyuh4LG_VgZrKNWe5xwZgDGdgaoYRpP0,3805
13
- yirgacheffe/layers/base.py,sha256=vVxumZgFLTYXFtVxJEiBVV1QqtQWa5kwL10ckUF-ykE,11617
14
- yirgacheffe/layers/constant.py,sha256=5YWSdr48Cw3DhryLBx45PNnPi4NzcVuQVin_YmOSWTg,1426
15
- yirgacheffe/layers/group.py,sha256=2hRta24Mn5JwfDOB4-gJfroa_lVJSUFgSZ8FMeWy_FM,15127
16
- yirgacheffe/layers/h3layer.py,sha256=Cyrw_6nXc1a_Twsb0FfexKhHAbegKXStL3J5LLk2AX8,9687
17
- yirgacheffe/layers/rasters.py,sha256=8mAbH49RtKIoA3h9mxWNbPzo8NJWDSfoRPT7fpkTJ4I,12328
18
- yirgacheffe/layers/rescaled.py,sha256=kWJlu7DuUB3nRDt3VtbJKiqBDQb2Ba8xzIIXTvOGdK8,2945
19
- yirgacheffe/layers/vectors.py,sha256=JOqM7Ym53ZdxWKpBJac-opPp2N_ynMfet1Xtsi85dpM,15087
20
- yirgacheffe-1.3.4.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
21
- yirgacheffe-1.3.4.dist-info/METADATA,sha256=tKSbYq3ZonA4FTn9AeYFgAB9AcZOnMBdky7oZ_Vcz60,20496
22
- yirgacheffe-1.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- yirgacheffe-1.3.4.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
24
- yirgacheffe-1.3.4.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
25
- yirgacheffe-1.3.4.dist-info/RECORD,,
File without changes