yirgacheffe 1.7.8__py3-none-any.whl → 1.8.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.
- yirgacheffe/__init__.py +1 -0
- yirgacheffe/_backends/mlx.py +6 -4
- yirgacheffe/_backends/numpy.py +6 -4
- yirgacheffe/_core.py +76 -96
- yirgacheffe/_operators.py +41 -29
- yirgacheffe/layers/area.py +6 -3
- yirgacheffe/layers/base.py +20 -27
- yirgacheffe/layers/constant.py +4 -2
- yirgacheffe/layers/group.py +11 -11
- yirgacheffe/layers/h3layer.py +4 -2
- yirgacheffe/layers/rasters.py +18 -18
- yirgacheffe/layers/rescaled.py +3 -3
- yirgacheffe/layers/vectors.py +36 -37
- yirgacheffe/rounding.py +4 -3
- yirgacheffe/window.py +49 -96
- {yirgacheffe-1.7.8.dist-info → yirgacheffe-1.8.0.dist-info}/METADATA +13 -6
- yirgacheffe-1.8.0.dist-info/RECORD +27 -0
- yirgacheffe-1.7.8.dist-info/RECORD +0 -27
- {yirgacheffe-1.7.8.dist-info → yirgacheffe-1.8.0.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.7.8.dist-info → yirgacheffe-1.8.0.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.7.8.dist-info → yirgacheffe-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.7.8.dist-info → yirgacheffe-1.8.0.dist-info}/top_level.txt +0 -0
yirgacheffe/__init__.py
CHANGED
yirgacheffe/_backends/mlx.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
import mlx.core as mx # type: ignore
|
|
@@ -131,7 +133,7 @@ def conv2d_op(data, weights):
|
|
|
131
133
|
return res[0]
|
|
132
134
|
|
|
133
135
|
|
|
134
|
-
def
|
|
136
|
+
def dtype_to_backend(dt):
|
|
135
137
|
match dt:
|
|
136
138
|
case dtype.Float32:
|
|
137
139
|
return mx.float32
|
|
@@ -182,9 +184,9 @@ def backend_to_dtype(val):
|
|
|
182
184
|
raise ValueError
|
|
183
185
|
|
|
184
186
|
def astype_op(data, datatype):
|
|
185
|
-
return data.astype(
|
|
187
|
+
return data.astype(dtype_to_backend(datatype))
|
|
186
188
|
|
|
187
|
-
operator_map
|
|
189
|
+
operator_map: dict[op, Callable] = {
|
|
188
190
|
op.ADD: mx.array.__add__,
|
|
189
191
|
op.SUB: mx.array.__sub__,
|
|
190
192
|
op.MUL: mul_op,
|
yirgacheffe/_backends/numpy.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
import torch
|
|
@@ -67,7 +69,7 @@ def conv2d_op(data, weights):
|
|
|
67
69
|
res = conv(preped_data)
|
|
68
70
|
return res.detach().numpy()[0][0]
|
|
69
71
|
|
|
70
|
-
def
|
|
72
|
+
def dtype_to_backend(dt):
|
|
71
73
|
match dt:
|
|
72
74
|
case dtype.Float32:
|
|
73
75
|
return np.float32
|
|
@@ -120,9 +122,9 @@ def backend_to_dtype(val):
|
|
|
120
122
|
raise ValueError
|
|
121
123
|
|
|
122
124
|
def astype_op(data, datatype):
|
|
123
|
-
return data.astype(
|
|
125
|
+
return data.astype(dtype_to_backend(datatype))
|
|
124
126
|
|
|
125
|
-
operator_map
|
|
127
|
+
operator_map: dict[op, Callable] = {
|
|
126
128
|
op.ADD: np.ndarray.__add__,
|
|
127
129
|
op.SUB: np.ndarray.__sub__,
|
|
128
130
|
op.MUL: np.ndarray.__mul__,
|
yirgacheffe/_core.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pathlib import Path
|
|
2
|
-
from typing import
|
|
4
|
+
from typing import Sequence
|
|
3
5
|
|
|
4
6
|
from .layers.area import UniformAreaLayer
|
|
5
7
|
from .layers.base import YirgacheffeLayer
|
|
@@ -11,30 +13,29 @@ from .window import MapProjection
|
|
|
11
13
|
from ._backends.enumeration import dtype as DataType
|
|
12
14
|
|
|
13
15
|
def read_raster(
|
|
14
|
-
filename:
|
|
16
|
+
filename: Path | str,
|
|
15
17
|
band: int = 1,
|
|
16
18
|
ignore_nodata: bool = False,
|
|
17
19
|
) -> RasterLayer:
|
|
18
20
|
"""Open a raster file (e.g., GeoTIFF).
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Returns an layer representing the raster data.
|
|
22
|
+
Args:
|
|
23
|
+
filename: Path of raster file to open.
|
|
24
|
+
band: For multi-band rasters, which band to use (defaults to first if not specified).
|
|
25
|
+
ignore_nodata: If the GeoTIFF has a NODATA value, don't substitute that value for NaN.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
An layer representing the raster data.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
>>> import yirgacheffe as yg
|
|
32
|
+
>>> with yg.read_raster('test.tif') as layer:
|
|
33
|
+
... total = layer.sum()
|
|
33
34
|
"""
|
|
34
35
|
return RasterLayer.layer_from_file(filename, band, ignore_nodata)
|
|
35
36
|
|
|
36
37
|
def read_narrow_raster(
|
|
37
|
-
filename:
|
|
38
|
+
filename: Path | str,
|
|
38
39
|
band: int = 1,
|
|
39
40
|
ignore_nodata: bool = False,
|
|
40
41
|
) -> RasterLayer:
|
|
@@ -44,41 +45,35 @@ def read_narrow_raster(
|
|
|
44
45
|
(e.g., a WGS84 map projection). For that case you can use this to load a raster that is 1 pixel wide and have
|
|
45
46
|
it automatically expanded to act like a global raster in calculations.
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
If the GeoTIFF has a NODATA value, don't subsitute that value for NaN
|
|
55
|
-
|
|
56
|
-
Returns
|
|
57
|
-
-------
|
|
58
|
-
RasterLayer
|
|
59
|
-
Returns an layer representing the raster data.
|
|
48
|
+
Args:
|
|
49
|
+
filename: Path of raster file to open.
|
|
50
|
+
band: For multi-band rasters, which band to use (defaults to first if not specified).
|
|
51
|
+
ignore_nodata: If the GeoTIFF has a NODATA value, don't substitute that value for NaN.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
An layer representing the raster data.
|
|
60
55
|
"""
|
|
61
56
|
return UniformAreaLayer.layer_from_file(filename, band, ignore_nodata)
|
|
62
57
|
|
|
63
58
|
def read_rasters(
|
|
64
|
-
filenames : Sequence[
|
|
59
|
+
filenames : Sequence[Path | str],
|
|
65
60
|
tiled: bool=False
|
|
66
61
|
) -> GroupLayer:
|
|
67
62
|
"""Open a set of raster files (e.g., GeoTIFFs) as a single layer.
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
Args:
|
|
65
|
+
filenames: List of paths of raster files to open.
|
|
66
|
+
tiled: If you know that the rasters for a regular tileset, then setting this flag allows
|
|
67
|
+
Yirgacheffe to perform certain optimisations that significantly improve performance for
|
|
68
|
+
this use case.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
An layer representing the raster data.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
>>> import yirgacheffe as yg
|
|
75
|
+
>>> with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif']) as all_tiles:
|
|
76
|
+
... ...
|
|
82
77
|
"""
|
|
83
78
|
if not tiled:
|
|
84
79
|
return GroupLayer.layer_from_files(filenames)
|
|
@@ -86,31 +81,28 @@ def read_rasters(
|
|
|
86
81
|
return TiledGroupLayer.layer_from_files(filenames)
|
|
87
82
|
|
|
88
83
|
def read_shape(
|
|
89
|
-
filename:
|
|
90
|
-
projection:
|
|
91
|
-
where_filter:
|
|
92
|
-
datatype:
|
|
93
|
-
burn_value:
|
|
84
|
+
filename: Path | str,
|
|
85
|
+
projection: MapProjection | tuple[str, tuple[float, float]] | None = None,
|
|
86
|
+
where_filter: str | None = None,
|
|
87
|
+
datatype: DataType | None = None,
|
|
88
|
+
burn_value: int | float | str = 1,
|
|
94
89
|
) -> VectorLayer:
|
|
95
90
|
"""Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
The
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-------
|
|
112
|
-
VectorLayer
|
|
113
|
-
Returns an layer representing the vector data.
|
|
92
|
+
Args:
|
|
93
|
+
filename: Path of vector file to open.
|
|
94
|
+
projection: The map projection to use.
|
|
95
|
+
where_filter: For use with files with many entries (e.g., GPKG), applies this filter to the data.
|
|
96
|
+
datatype: Specify the data type of the raster data generated.
|
|
97
|
+
burn_value: The value of each pixel in the polygon.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
An layer representing the vector data.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
>>> import yirgacheffe as yg
|
|
104
|
+
>>> with yg.read_shape('range.gpkg') as layer:
|
|
105
|
+
... ...
|
|
114
106
|
"""
|
|
115
107
|
|
|
116
108
|
if projection is not None:
|
|
@@ -127,32 +119,24 @@ def read_shape(
|
|
|
127
119
|
)
|
|
128
120
|
|
|
129
121
|
def read_shape_like(
|
|
130
|
-
filename:
|
|
122
|
+
filename: Path | str,
|
|
131
123
|
like: YirgacheffeLayer,
|
|
132
|
-
where_filter:
|
|
133
|
-
datatype:
|
|
134
|
-
burn_value:
|
|
124
|
+
where_filter: str | None = None,
|
|
125
|
+
datatype: DataType | None = None,
|
|
126
|
+
burn_value: int | float | str = 1,
|
|
135
127
|
) -> VectorLayer:
|
|
136
128
|
"""Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
|
|
137
129
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Specify the data type of the raster data generated.
|
|
149
|
-
burn_value: int or float or str, default=1
|
|
150
|
-
The value of each pixel in the polygon.
|
|
151
|
-
|
|
152
|
-
Returns
|
|
153
|
-
-------
|
|
154
|
-
VectorLayer
|
|
155
|
-
Returns an layer representing the vector data.
|
|
130
|
+
Args:
|
|
131
|
+
filename: Path of vector file to open.
|
|
132
|
+
like: Another layer that has a projection and pixel scale set. This layer will
|
|
133
|
+
use the same projection and pixel scale as that one.
|
|
134
|
+
where_filter: For use with files with many entries (e.g., GPKG), applies this filter to the data.
|
|
135
|
+
datatype: Specify the data type of the raster data generated.
|
|
136
|
+
burn_value: The value of each pixel in the polygon.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
An layer representing the vector data.
|
|
156
140
|
"""
|
|
157
141
|
return VectorLayer.layer_from_file_like(
|
|
158
142
|
filename,
|
|
@@ -162,7 +146,7 @@ def read_shape_like(
|
|
|
162
146
|
burn_value,
|
|
163
147
|
)
|
|
164
148
|
|
|
165
|
-
def constant(value:
|
|
149
|
+
def constant(value: int | float) -> ConstantLayer:
|
|
166
150
|
"""Generate a layer that has the same value in all pixels regardless of scale, projection, and area.
|
|
167
151
|
|
|
168
152
|
Generally this should not be necessary unless you must have the constant as the first term in an
|
|
@@ -170,14 +154,10 @@ def constant(value: Union[int,float]) -> ConstantLayer:
|
|
|
170
154
|
constant is the first term in the expression it must be wrapped by this call otherwise Python will
|
|
171
155
|
not know that it should be part of the Yirgacheffe expression.
|
|
172
156
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
value : int or float
|
|
176
|
-
The value to be in each pixel of the expression term.
|
|
157
|
+
Args:
|
|
158
|
+
value: The value to be in each pixel of the expression term.
|
|
177
159
|
|
|
178
|
-
Returns
|
|
179
|
-
|
|
180
|
-
ConstantLayer
|
|
181
|
-
Returns a constant layer of the provided value.
|
|
160
|
+
Returns:
|
|
161
|
+
A constant layer of the provided value.
|
|
182
162
|
"""
|
|
183
163
|
return ConstantLayer(value)
|
yirgacheffe/_operators.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
import math
|
|
3
5
|
import multiprocessing
|
|
@@ -12,7 +14,7 @@ from enum import Enum
|
|
|
12
14
|
from multiprocessing import Semaphore, Process
|
|
13
15
|
from multiprocessing.managers import SharedMemoryManager
|
|
14
16
|
from pathlib import Path
|
|
15
|
-
from typing import Callable
|
|
17
|
+
from typing import Callable
|
|
16
18
|
|
|
17
19
|
import deprecation
|
|
18
20
|
import numpy as np
|
|
@@ -26,6 +28,8 @@ from .window import Area, PixelScale, MapProjection, Window
|
|
|
26
28
|
from ._backends import backend
|
|
27
29
|
from ._backends.enumeration import operators as op
|
|
28
30
|
from ._backends.enumeration import dtype as DataType
|
|
31
|
+
from ._backends.numpy import dtype_to_backend as dtype_to_numpy
|
|
32
|
+
from ._backends.numpy import backend_to_dtype as numpy_to_dtype
|
|
29
33
|
|
|
30
34
|
logger = logging.getLogger(__name__)
|
|
31
35
|
logger.setLevel(logging.WARNING)
|
|
@@ -47,6 +51,11 @@ class LayerConstant:
|
|
|
47
51
|
def _eval(self, _area, _projection, _index, _step, _target_window):
|
|
48
52
|
return self.val
|
|
49
53
|
|
|
54
|
+
@property
|
|
55
|
+
def datatype(self) -> DataType:
|
|
56
|
+
numpy_type = np.result_type(self.val)
|
|
57
|
+
return numpy_to_dtype(numpy_type)
|
|
58
|
+
|
|
50
59
|
@property
|
|
51
60
|
def area(self) -> Area:
|
|
52
61
|
return Area.world()
|
|
@@ -258,10 +267,10 @@ class LayerMathMixin:
|
|
|
258
267
|
|
|
259
268
|
def to_geotiff(
|
|
260
269
|
self,
|
|
261
|
-
filename:
|
|
270
|
+
filename: Path | str,
|
|
262
271
|
and_sum: bool = False,
|
|
263
|
-
parallelism:
|
|
264
|
-
) ->
|
|
272
|
+
parallelism: int | bool | None = None
|
|
273
|
+
) -> float | None:
|
|
265
274
|
return LayerOperation(self).to_geotiff(filename, and_sum, parallelism)
|
|
266
275
|
|
|
267
276
|
def sum(self):
|
|
@@ -391,7 +400,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
391
400
|
def area(self) -> Area:
|
|
392
401
|
return self._get_operation_area(self.map_projection)
|
|
393
402
|
|
|
394
|
-
def _get_operation_area(self, projection:
|
|
403
|
+
def _get_operation_area(self, projection: MapProjection | None) -> Area:
|
|
395
404
|
lhs_area = self.lhs._get_operation_area(projection)
|
|
396
405
|
try:
|
|
397
406
|
rhs_area = self.rhs._get_operation_area(projection)
|
|
@@ -475,8 +484,16 @@ class LayerOperation(LayerMathMixin):
|
|
|
475
484
|
|
|
476
485
|
@property
|
|
477
486
|
def datatype(self) -> DataType:
|
|
478
|
-
|
|
479
|
-
|
|
487
|
+
internal_types: list[DataType] = [
|
|
488
|
+
self.lhs.datatype
|
|
489
|
+
]
|
|
490
|
+
if self.rhs is not None:
|
|
491
|
+
internal_types.append(self.rhs.datatype)
|
|
492
|
+
if self.other is not None:
|
|
493
|
+
internal_types.append(self.other.datatype)
|
|
494
|
+
internal_types_as_numpy_types = [dtype_to_numpy(x) for x in internal_types]
|
|
495
|
+
coerced_type = np.result_type(*internal_types_as_numpy_types)
|
|
496
|
+
return numpy_to_dtype(coerced_type)
|
|
480
497
|
|
|
481
498
|
@property
|
|
482
499
|
@deprecation.deprecated(
|
|
@@ -496,7 +513,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
496
513
|
return projection
|
|
497
514
|
|
|
498
515
|
@property
|
|
499
|
-
def map_projection(self) ->
|
|
516
|
+
def map_projection(self) -> MapProjection | None:
|
|
500
517
|
try:
|
|
501
518
|
projection = self.lhs.map_projection
|
|
502
519
|
except AttributeError:
|
|
@@ -515,7 +532,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
515
532
|
projection: MapProjection,
|
|
516
533
|
index: int,
|
|
517
534
|
step: int,
|
|
518
|
-
target_window:
|
|
535
|
+
target_window: Window | None = None
|
|
519
536
|
):
|
|
520
537
|
|
|
521
538
|
if self.buffer_padding:
|
|
@@ -592,7 +609,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
592
609
|
res = chunk_max
|
|
593
610
|
return res
|
|
594
611
|
|
|
595
|
-
def save(self, destination_layer, and_sum=False, callback=None, band=1) ->
|
|
612
|
+
def save(self, destination_layer, and_sum=False, callback=None, band=1) -> float | None:
|
|
596
613
|
"""
|
|
597
614
|
Calling save will write the output of the operation to the provied layer.
|
|
598
615
|
If you provide sum as true it will additionall compute the sum and return that.
|
|
@@ -708,7 +725,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
708
725
|
callback=None,
|
|
709
726
|
parallelism=None,
|
|
710
727
|
band=1
|
|
711
|
-
) ->
|
|
728
|
+
) -> float | None:
|
|
712
729
|
assert (destination_layer is not None) or and_sum
|
|
713
730
|
try:
|
|
714
731
|
computation_window = self.window
|
|
@@ -748,7 +765,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
748
765
|
or (computation_window.ysize != destination_window.ysize):
|
|
749
766
|
raise ValueError("Destination raster window size does not match input raster window size.")
|
|
750
767
|
|
|
751
|
-
np_type_map
|
|
768
|
+
np_type_map: dict[int, np.dtype] = {
|
|
752
769
|
gdal.GDT_Byte: np.dtype('byte'),
|
|
753
770
|
gdal.GDT_Float32: np.dtype('float32'),
|
|
754
771
|
gdal.GDT_Float64: np.dtype('float64'),
|
|
@@ -867,7 +884,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
867
884
|
callback=None,
|
|
868
885
|
parallelism=None,
|
|
869
886
|
band=1
|
|
870
|
-
) ->
|
|
887
|
+
) -> float | None:
|
|
871
888
|
if destination_layer is None:
|
|
872
889
|
raise ValueError("Layer is required")
|
|
873
890
|
return self._parallel_save(destination_layer, and_sum, callback, parallelism, band)
|
|
@@ -877,25 +894,20 @@ class LayerOperation(LayerMathMixin):
|
|
|
877
894
|
|
|
878
895
|
def to_geotiff(
|
|
879
896
|
self,
|
|
880
|
-
filename:
|
|
897
|
+
filename: Path | str,
|
|
881
898
|
and_sum: bool = False,
|
|
882
|
-
parallelism:
|
|
883
|
-
) ->
|
|
899
|
+
parallelism: int | bool | None = None
|
|
900
|
+
) -> float | None:
|
|
884
901
|
"""Saves a calculation to a raster file, optionally also returning the sum of pixels.
|
|
885
902
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
will pick a sensible value.
|
|
895
|
-
|
|
896
|
-
Returns
|
|
897
|
-
-------
|
|
898
|
-
float, optional
|
|
903
|
+
Args:
|
|
904
|
+
filename: Path of the raster to save the result to.
|
|
905
|
+
and_sum: If true then the function will also calculate the sum of the raster as it goes and return
|
|
906
|
+
that value.
|
|
907
|
+
parallelism: If passed, attempt to use multiple CPU cores up to the number provided, or if set to True,
|
|
908
|
+
yirgacheffe will pick a sensible value.
|
|
909
|
+
|
|
910
|
+
Returns:
|
|
899
911
|
Either returns None, or the sum of the pixels in the resulting raster if `and_sum` was specified.
|
|
900
912
|
"""
|
|
901
913
|
|
yirgacheffe/layers/area.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from math import ceil, floor
|
|
2
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
4
7
|
import numpy
|
|
5
8
|
from osgeo import gdal
|
|
@@ -18,7 +21,7 @@ class UniformAreaLayer(RasterLayer):
|
|
|
18
21
|
"""
|
|
19
22
|
|
|
20
23
|
@staticmethod
|
|
21
|
-
def generate_narrow_area_projection(source_filename: str, target_filename: str) -> None:
|
|
24
|
+
def generate_narrow_area_projection(source_filename: Path | str, target_filename: Path | str) -> None:
|
|
22
25
|
source = gdal.Open(source_filename, gdal.GA_ReadOnly)
|
|
23
26
|
if source is None:
|
|
24
27
|
raise FileNotFoundError(source_filename)
|
|
@@ -56,7 +59,7 @@ class UniformAreaLayer(RasterLayer):
|
|
|
56
59
|
return False
|
|
57
60
|
return True
|
|
58
61
|
|
|
59
|
-
def __init__(self, dataset, name:
|
|
62
|
+
def __init__(self, dataset, name: str | None = None, band: int = 1, ignore_nodata: bool = False):
|
|
60
63
|
if dataset.RasterXSize > 1:
|
|
61
64
|
raise ValueError("Expected a shrunk dataset")
|
|
62
65
|
self.databand = dataset.GetRasterBand(1).ReadAsArray(0, 0, 1, dataset.RasterYSize)
|
yirgacheffe/layers/base.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, Sequence
|
|
3
3
|
|
|
4
4
|
import deprecation
|
|
5
5
|
|
|
@@ -17,17 +17,17 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
17
17
|
|
|
18
18
|
def __init__(self,
|
|
19
19
|
area: Area,
|
|
20
|
-
projection:
|
|
21
|
-
name:
|
|
20
|
+
projection: MapProjection | None,
|
|
21
|
+
name: str | None = None
|
|
22
22
|
):
|
|
23
23
|
# This is just to catch code that uses the old private API
|
|
24
24
|
if projection is not None and not isinstance(projection, MapProjection):
|
|
25
25
|
raise TypeError("projection value of wrong type")
|
|
26
26
|
|
|
27
27
|
self._underlying_area = area
|
|
28
|
-
self._active_area:
|
|
28
|
+
self._active_area: Area | None = None
|
|
29
29
|
self._projection = projection
|
|
30
|
-
self._window:
|
|
30
|
+
self._window: Window | None = None
|
|
31
31
|
self.name = name
|
|
32
32
|
|
|
33
33
|
self.reset_window()
|
|
@@ -48,7 +48,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
|
-
def _raster_dimensions(self) ->
|
|
51
|
+
def _raster_dimensions(self) -> tuple[int, int]:
|
|
52
52
|
raise AttributeError("Does not have raster")
|
|
53
53
|
|
|
54
54
|
@property
|
|
@@ -62,7 +62,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
62
62
|
current_version=__version__,
|
|
63
63
|
details="Use `map_projection` instead."
|
|
64
64
|
)
|
|
65
|
-
def projection(self) ->
|
|
65
|
+
def projection(self) -> str | None:
|
|
66
66
|
if self._projection:
|
|
67
67
|
return self._projection.name
|
|
68
68
|
else:
|
|
@@ -75,14 +75,14 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
75
75
|
current_version=__version__,
|
|
76
76
|
details="Use `map_projection` instead."
|
|
77
77
|
)
|
|
78
|
-
def pixel_scale(self) ->
|
|
78
|
+
def pixel_scale(self) -> PixelScale | None:
|
|
79
79
|
if self._projection:
|
|
80
80
|
return PixelScale(self._projection.xstep, self._projection.ystep)
|
|
81
81
|
else:
|
|
82
82
|
return None
|
|
83
83
|
|
|
84
84
|
@property
|
|
85
|
-
def map_projection(self) ->
|
|
85
|
+
def map_projection(self) -> MapProjection | None:
|
|
86
86
|
return self._projection
|
|
87
87
|
|
|
88
88
|
@property
|
|
@@ -92,7 +92,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
92
92
|
else:
|
|
93
93
|
return self._underlying_area
|
|
94
94
|
|
|
95
|
-
def _get_operation_area(self, projection:
|
|
95
|
+
def _get_operation_area(self, projection: MapProjection | None = None) -> Area:
|
|
96
96
|
if self._projection is not None and projection is not None and self._projection != projection:
|
|
97
97
|
raise ValueError("Calculation projection does not match layer projection")
|
|
98
98
|
return self.area
|
|
@@ -153,7 +153,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
@property
|
|
156
|
-
def geo_transform(self) ->
|
|
156
|
+
def geo_transform(self) -> tuple[float, float, float, float, float, float]:
|
|
157
157
|
if self._projection is None:
|
|
158
158
|
raise AttributeError("No geo transform for layers without explicit pixel scale")
|
|
159
159
|
return (
|
|
@@ -320,26 +320,19 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
320
320
|
def read_array(self, x: int, y: int, width: int, height: int) -> Any:
|
|
321
321
|
"""Reads data from the layer based on the current reference window.
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
Width of data to read
|
|
331
|
-
height : int
|
|
332
|
-
Height of data to read
|
|
333
|
-
|
|
334
|
-
Results
|
|
335
|
-
-------
|
|
336
|
-
Any
|
|
323
|
+
Args:
|
|
324
|
+
x: X axis offset for reading
|
|
325
|
+
y: Y axis offset for reading
|
|
326
|
+
width: Width of data to read
|
|
327
|
+
height: Height of data to read
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
337
330
|
An array of values from the layer.
|
|
338
331
|
"""
|
|
339
332
|
res = self._read_array(x, y, width, height)
|
|
340
333
|
return backend.demote_array(res)
|
|
341
334
|
|
|
342
|
-
def latlng_for_pixel(self, x_coord: int, y_coord: int) ->
|
|
335
|
+
def latlng_for_pixel(self, x_coord: int, y_coord: int) -> tuple[float, float]:
|
|
343
336
|
"""Get geo coords for pixel. This is relative to the set view window."""
|
|
344
337
|
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
345
338
|
raise NotImplementedError("Not yet supported for other projections")
|
|
@@ -348,7 +341,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
348
341
|
(x_coord * self._projection.xstep) + self.area.left
|
|
349
342
|
)
|
|
350
343
|
|
|
351
|
-
def pixel_for_latlng(self, lat: float, lng: float) ->
|
|
344
|
+
def pixel_for_latlng(self, lat: float, lng: float) -> tuple[int, int]:
|
|
352
345
|
"""Get pixel for geo coords. This is relative to the set view window.
|
|
353
346
|
Result is rounded down to nearest pixel."""
|
|
354
347
|
if self._projection is None or "WGS 84" not in self._projection.name:
|
yirgacheffe/layers/constant.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
2
4
|
|
|
3
5
|
from ..window import Area, MapProjection, PixelScale, Window
|
|
4
6
|
from .base import YirgacheffeLayer
|
|
@@ -8,7 +10,7 @@ from .._backends.enumeration import dtype as DataType
|
|
|
8
10
|
class ConstantLayer(YirgacheffeLayer):
|
|
9
11
|
"""This is a layer that will return the identity value - can be used when an input layer is
|
|
10
12
|
missing (e.g., area) without having the calculation full of branches."""
|
|
11
|
-
def __init__(self, value:
|
|
13
|
+
def __init__(self, value: int | float): # pylint: disable=W0231
|
|
12
14
|
area = Area.world()
|
|
13
15
|
super().__init__(area, None)
|
|
14
16
|
self.value = float(value)
|