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

@@ -6,7 +6,7 @@ from typing import Any, Optional, Union
6
6
  from skimage import transform
7
7
  from yirgacheffe.operators import DataType
8
8
 
9
- from ..window import PixelScale, Window
9
+ from ..window import MapProjection, PixelScale, Window
10
10
  from .rasters import RasterLayer, YirgacheffeLayer
11
11
  from .._backends import backend
12
12
 
@@ -24,30 +24,33 @@ class RescaledRasterLayer(YirgacheffeLayer):
24
24
  nearest_neighbour: bool = True,
25
25
  ) -> RescaledRasterLayer:
26
26
  src = RasterLayer.layer_from_file(filename, band=band)
27
- return RescaledRasterLayer(src, pixel_scale, nearest_neighbour, src.name)
27
+ source_projection = src.map_projection
28
+ if source_projection is None:
29
+ raise ValueError("Source raster must have projection and scale")
30
+ target_projection = MapProjection(source_projection.name, pixel_scale.xstep, pixel_scale.ystep)
31
+ return RescaledRasterLayer(src, target_projection, nearest_neighbour, src.name)
28
32
 
29
33
  def __init__(
30
34
  self,
31
35
  src: RasterLayer,
32
- pixel_scale: PixelScale,
36
+ target_projection: MapProjection,
33
37
  nearest_neighbour: bool = True,
34
38
  name: Optional[str] = None,
35
39
  ):
36
40
  super().__init__(
37
41
  src.area,
38
- pixel_scale=pixel_scale,
39
- projection=src.projection,
42
+ target_projection,
40
43
  name=name
41
44
  )
42
45
 
43
46
  self._src = src
44
47
  self._nearest_neighbour = nearest_neighbour
45
48
 
46
- src_pixel_scale = src.pixel_scale
47
- assert src_pixel_scale # from raster we should always have one
49
+ src_projection = src.map_projection
50
+ assert src_projection # from raster we should always have one
48
51
 
49
- self._x_scale = src_pixel_scale.xstep / pixel_scale.xstep
50
- self._y_scale = src_pixel_scale.ystep / pixel_scale.ystep
52
+ self._x_scale = src_projection.xstep / target_projection.xstep
53
+ self._y_scale = src_projection.ystep / target_projection.ystep
51
54
 
52
55
  def close(self):
53
56
  self._src.close()
@@ -7,7 +7,7 @@ from typing_extensions import NotRequired
7
7
  from osgeo import gdal, ogr
8
8
 
9
9
  from ..operators import DataType
10
- from ..window import Area, PixelScale
10
+ from ..window import Area, MapProjection, PixelScale
11
11
  from .base import YirgacheffeLayer
12
12
  from .rasters import RasterLayer
13
13
  from .._backends import backend
@@ -84,10 +84,11 @@ class RasteredVectorLayer(RasterLayer):
84
84
  else:
85
85
  datatype_arg = datatype
86
86
 
87
+ map_projection = MapProjection(projection, scale.xstep, scale.ystep)
88
+
87
89
  vector_layer = RasteredVectorLayer(
88
90
  layer,
89
- scale,
90
- projection,
91
+ map_projection,
91
92
  datatype=datatype_arg,
92
93
  burn_value=burn_value
93
94
  )
@@ -101,8 +102,7 @@ class RasteredVectorLayer(RasterLayer):
101
102
  def __init__(
102
103
  self,
103
104
  layer: ogr.Layer,
104
- scale: PixelScale,
105
- projection: str,
105
+ projection: MapProjection,
106
106
  datatype: Union[int, DataType] = DataType.Byte,
107
107
  burn_value: Union[int,float,str] = 1,
108
108
  ):
@@ -132,7 +132,7 @@ class RasteredVectorLayer(RasterLayer):
132
132
  # Get the area, but scale it to the pixel resolution that we're using. Note that
133
133
  # the pixel scale GDAL uses can have -ve values, but those will mess up the
134
134
  # ceil/floor math, so we use absolute versions when trying to round.
135
- abs_xstep, abs_ystep = abs(scale.xstep), abs(scale.ystep)
135
+ abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
136
136
  area = Area(
137
137
  left=floor(min(x[0] for x in envelopes) / abs_xstep) * abs_xstep,
138
138
  top=ceil(max(x[3] for x in envelopes) / abs_ystep) * abs_ystep,
@@ -152,8 +152,8 @@ class RasteredVectorLayer(RasterLayer):
152
152
  if not dataset:
153
153
  raise MemoryError('Failed to create memory mask')
154
154
 
155
- dataset.SetProjection(projection)
156
- dataset.SetGeoTransform([area.left, scale.xstep, 0.0, area.top, 0.0, scale.ystep])
155
+ dataset.SetProjection(projection.name)
156
+ dataset.SetGeoTransform([area.left, projection.xstep, 0.0, area.top, 0.0, projection.ystep])
157
157
  if isinstance(burn_value, (int, float)):
158
158
  gdal.RasterizeLayer(dataset, [1], self.layer, burn_values=[burn_value], options=["ALL_TOUCHED=TRUE"])
159
159
  elif isinstance(burn_value, str):
@@ -180,8 +180,9 @@ class VectorLayer(YirgacheffeLayer):
180
180
  ) -> VectorLayer:
181
181
  if other_layer is None:
182
182
  raise ValueError("like layer can not be None")
183
- if other_layer.pixel_scale is None:
184
- raise ValueError("Reference layer must have pixel scale")
183
+ map_projection = other_layer.map_projection
184
+ if map_projection is None:
185
+ raise ValueError("Reference layer must have projectione")
185
186
 
186
187
  vectors = ogr.Open(filename)
187
188
  if vectors is None:
@@ -196,8 +197,7 @@ class VectorLayer(YirgacheffeLayer):
196
197
 
197
198
  vector_layer = VectorLayer(
198
199
  layer,
199
- other_layer.pixel_scale,
200
- other_layer.projection,
200
+ map_projection,
201
201
  name=str(filename),
202
202
  datatype=datatype if datatype is not None else other_layer.datatype,
203
203
  burn_value=burn_value,
@@ -217,8 +217,34 @@ class VectorLayer(YirgacheffeLayer):
217
217
  cls,
218
218
  filename: Union[Path,str],
219
219
  where_filter: Optional[str],
220
- scale: PixelScale,
221
- projection: str,
220
+ scale: Optional[PixelScale],
221
+ projection: Optional[str],
222
+ datatype: Optional[Union[int, DataType]] = None,
223
+ burn_value: Union[int,float,str] = 1,
224
+ anchor: Tuple[float,float] = (0.0, 0.0)
225
+ ) -> VectorLayer:
226
+ # In 2.0 we need to remove this and migrate to the MapProjection version
227
+ if (projection is None) ^ (scale is None):
228
+ raise ValueError("Either both projection and scale must be provide, or neither")
229
+ if projection is not None and scale is not None:
230
+ map_projection = MapProjection(projection, scale.xstep, scale.ystep)
231
+ else:
232
+ map_projection = None
233
+ return cls._future_layer_from_file(
234
+ filename,
235
+ where_filter,
236
+ map_projection,
237
+ datatype,
238
+ burn_value,
239
+ anchor
240
+ )
241
+
242
+ @classmethod
243
+ def _future_layer_from_file(
244
+ cls,
245
+ filename: Union[Path,str],
246
+ where_filter: Optional[str],
247
+ projection: Optional[MapProjection],
222
248
  datatype: Optional[Union[int, DataType]] = None,
223
249
  burn_value: Union[int,float,str] = 1,
224
250
  anchor: Tuple[float,float] = (0.0, 0.0)
@@ -243,7 +269,6 @@ class VectorLayer(YirgacheffeLayer):
243
269
 
244
270
  vector_layer = VectorLayer(
245
271
  layer,
246
- scale,
247
272
  projection,
248
273
  name=str(filename),
249
274
  datatype=datatype_arg,
@@ -262,8 +287,7 @@ class VectorLayer(YirgacheffeLayer):
262
287
  def __init__(
263
288
  self,
264
289
  layer: ogr.Layer,
265
- scale: PixelScale,
266
- projection: str,
290
+ projection: Optional[MapProjection],
267
291
  name: Optional[str] = None,
268
292
  datatype: Union[int,DataType] = DataType.Byte,
269
293
  burn_value: Union[int,float,str] = 1,
@@ -286,6 +310,7 @@ class VectorLayer(YirgacheffeLayer):
286
310
  self._original = None
287
311
  self._dataset_path: Optional[Path] = None
288
312
  self._filter: Optional[str] = None
313
+ self._anchor: Tuple[float,float] = (0.0, 0.0)
289
314
 
290
315
  # work out region for mask
291
316
  envelopes = []
@@ -298,30 +323,84 @@ class VectorLayer(YirgacheffeLayer):
298
323
  feature = layer.GetNextFeature()
299
324
  if len(envelopes) == 0:
300
325
  raise ValueError('No geometry found')
301
-
302
- # Get the area, but scale it to the pixel resolution that we're using. Note that
303
- # the pixel scale GDAL uses can have -ve values, but those will mess up the
304
- # ceil/floor math, so we use absolute versions when trying to round.
305
- abs_xstep, abs_ystep = abs(scale.xstep), abs(scale.ystep)
306
-
307
- # Lacking any other reference, we will make the raster align with
308
- # (0.0, 0.0), if sometimes we want to align with an existing raster, so if
309
- # an anchor is specified, ensure we use that as our pixel space alignment
310
- x_anchor = anchor[0]
311
- y_anchor = anchor[1]
312
- left_shift = x_anchor - abs_xstep
313
- right_shift = x_anchor
314
- top_shift = y_anchor
315
- bottom_shift = y_anchor - abs_ystep
316
-
326
+ self._anchor = anchor
327
+ self._envelopes = envelopes
328
+
329
+ # if projection is not None:
330
+ # # Get the area, but scale it to the pixel resolution that we're using. Note that
331
+ # # the pixel scale GDAL uses can have -ve values, but those will mess up the
332
+ # # ceil/floor math, so we use absolute versions when trying to round.
333
+ # abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
334
+ #
335
+ # # Lacking any other reference, we will make the raster align with
336
+ # # (0.0, 0.0), if sometimes we want to align with an existing raster, so if
337
+ # # an anchor is specified, ensure we use that as our pixel space alignment
338
+ # x_anchor = anchor[0]
339
+ # y_anchor = anchor[1]
340
+ # left_shift = x_anchor - abs_xstep
341
+ # right_shift = x_anchor
342
+ # top_shift = y_anchor
343
+ # bottom_shift = y_anchor - abs_ystep
344
+ #
345
+ # area = Area(
346
+ # left=(floor((min(x[0] for x in envelopes) - left_shift) / abs_xstep) * abs_xstep) + left_shift,
347
+ # top=(ceil((max(x[3] for x in envelopes) - top_shift) / abs_ystep) * abs_ystep) + top_shift,
348
+ # right=(ceil((max(x[1] for x in envelopes) - right_shift) / abs_xstep) * abs_xstep) + right_shift,
349
+ # bottom=(floor((min(x[2] for x in envelopes) - bottom_shift) / abs_ystep) * abs_ystep) + bottom_shift,
350
+ # )
351
+ # else:
352
+ # If we don't have a projection just go with the idealised area
317
353
  area = Area(
318
- left=(floor((min(x[0] for x in envelopes) - left_shift) / abs_xstep) * abs_xstep) + left_shift,
319
- top=(ceil((max(x[3] for x in envelopes) - top_shift) / abs_ystep) * abs_ystep) + top_shift,
320
- right=(ceil((max(x[1] for x in envelopes) - right_shift) / abs_xstep) * abs_xstep) + right_shift,
321
- bottom=(floor((min(x[2] for x in envelopes) - bottom_shift) / abs_ystep) * abs_ystep) + bottom_shift,
354
+ left=floor(min(x[0] for x in envelopes)),
355
+ top=ceil(max(x[3] for x in envelopes)),
356
+ right=ceil(max(x[1] for x in envelopes)),
357
+ bottom=floor(min(x[2] for x in envelopes)),
322
358
  )
323
359
 
324
- super().__init__(area, scale, projection)
360
+ super().__init__(area, projection)
361
+
362
+
363
+ def _get_operation_area(self, projection: Optional[MapProjection]=None) -> Area:
364
+ if self._projection is not None and projection is not None and self._projection != projection:
365
+ raise ValueError("Calculation projection does not match layer projection")
366
+
367
+ target_projection = projection if projection is not None else self._projection
368
+
369
+ if target_projection is None:
370
+ if self._active_area is not None:
371
+ return self._active_area
372
+ else:
373
+ return self._underlying_area
374
+ else:
375
+ # Get the area, but scale it to the pixel resolution that we're using. Note that
376
+ # the pixel scale GDAL uses can have -ve values, but those will mess up the
377
+ # ceil/floor math, so we use absolute versions when trying to round.
378
+ abs_xstep, abs_ystep = abs(target_projection.xstep), abs(target_projection.ystep)
379
+
380
+ # Lacking any other reference, we will make the raster align with
381
+ # (0.0, 0.0), if sometimes we want to align with an existing raster, so if
382
+ # an anchor is specified, ensure we use that as our pixel space alignment
383
+ x_anchor = self._anchor[0]
384
+ y_anchor = self._anchor[1]
385
+ left_shift = x_anchor - abs_xstep
386
+ right_shift = x_anchor
387
+ top_shift = y_anchor
388
+ bottom_shift = y_anchor - abs_ystep
389
+
390
+ envelopes = self._envelopes
391
+ return Area(
392
+ left=(floor((min(x[0] for x in envelopes) - left_shift) / abs_xstep) * abs_xstep) + left_shift,
393
+ top=(ceil((max(x[3] for x in envelopes) - top_shift) / abs_ystep) * abs_ystep) + top_shift,
394
+ right=(ceil((max(x[1] for x in envelopes) - right_shift) / abs_xstep) * abs_xstep) + right_shift,
395
+ bottom=(floor((min(x[2] for x in envelopes) - bottom_shift) / abs_ystep) * abs_ystep) + bottom_shift,
396
+ )
397
+
398
+ @property
399
+ def area(self) -> Area:
400
+ if self._active_area is not None:
401
+ return self._active_area
402
+ else:
403
+ return self._get_operation_area()
325
404
 
326
405
  def __getstate__(self) -> object:
327
406
  # Only support pickling on file backed layers (ideally read only ones...)
@@ -362,12 +441,14 @@ class VectorLayer(YirgacheffeLayer):
362
441
  def _read_array_for_area(
363
442
  self,
364
443
  target_area: Area,
444
+ target_projection: Optional[MapProjection],
365
445
  x: int,
366
446
  y: int,
367
447
  width: int,
368
448
  height: int,
369
449
  ) -> Any:
370
- assert self._pixel_scale is not None
450
+ projection = target_projection if target_projection is not None else self._projection
451
+ assert projection is not None
371
452
 
372
453
  if self._original is None:
373
454
  self._unpark()
@@ -387,14 +468,14 @@ class VectorLayer(YirgacheffeLayer):
387
468
  if not dataset:
388
469
  raise MemoryError('Failed to create memory mask')
389
470
 
390
- dataset.SetProjection(self._projection)
471
+ dataset.SetProjection(projection.name)
391
472
  dataset.SetGeoTransform([
392
- target_area.left + (x * self._pixel_scale.xstep),
393
- self._pixel_scale.xstep,
473
+ target_area.left + (x * projection.xstep),
474
+ projection.xstep,
394
475
  0.0,
395
- target_area.top + (y * self._pixel_scale.ystep),
476
+ target_area.top + (y * projection.ystep),
396
477
  0.0,
397
- self._pixel_scale.ystep
478
+ projection.ystep
398
479
  ])
399
480
  if isinstance(self.burn_value, (int, float)):
400
481
  gdal.RasterizeLayer(dataset, [1], self.layer, burn_values=[self.burn_value], options=["ALL_TOUCHED=TRUE"])
@@ -410,4 +491,4 @@ class VectorLayer(YirgacheffeLayer):
410
491
  assert NotRequired
411
492
 
412
493
  def read_array(self, x: int, y: int, width: int, height: int) -> Any:
413
- return self._read_array_for_area(self._active_area, x, y, width, height)
494
+ return self._read_array_for_area(self.area, None, x, y, width, height)