yirgacheffe 1.4.1__py3-none-any.whl → 1.6.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 +3 -6
- yirgacheffe/_core.py +136 -0
- yirgacheffe/constants.py +6 -0
- yirgacheffe/layers/area.py +10 -3
- yirgacheffe/layers/base.py +46 -10
- yirgacheffe/layers/constant.py +18 -9
- yirgacheffe/layers/group.py +55 -17
- yirgacheffe/layers/h3layer.py +8 -1
- yirgacheffe/layers/rasters.py +42 -12
- yirgacheffe/layers/rescaled.py +11 -3
- yirgacheffe/layers/vectors.py +25 -16
- yirgacheffe/operators.py +85 -21
- yirgacheffe/window.py +27 -0
- {yirgacheffe-1.4.1.dist-info → yirgacheffe-1.6.0.dist-info}/METADATA +115 -56
- yirgacheffe-1.6.0.dist-info/RECORD +25 -0
- yirgacheffe-1.4.1.dist-info/RECORD +0 -24
- {yirgacheffe-1.4.1.dist-info → yirgacheffe-1.6.0.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.4.1.dist-info → yirgacheffe-1.6.0.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.4.1.dist-info → yirgacheffe-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.4.1.dist-info → yirgacheffe-1.6.0.dist-info}/top_level.txt +0 -0
yirgacheffe/__init__.py
CHANGED
|
@@ -5,10 +5,7 @@ try:
|
|
|
5
5
|
except ModuleNotFoundError:
|
|
6
6
|
__version__ = "unknown"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
from ._core import read_raster, read_rasters, read_shape, read_shape_like
|
|
9
|
+
from .constants import WGS_84_PROJECTION
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,'\
|
|
12
|
-
'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
|
|
13
|
-
'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
|
|
14
|
-
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
|
|
11
|
+
gdal.UseExceptions()
|
yirgacheffe/_core.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
from .layers.base import YirgacheffeLayer
|
|
5
|
+
from .layers.group import GroupLayer, TiledGroupLayer
|
|
6
|
+
from .layers.rasters import RasterLayer
|
|
7
|
+
from .layers.vectors import VectorLayer
|
|
8
|
+
from .window import PixelScale
|
|
9
|
+
from .operators import DataType
|
|
10
|
+
|
|
11
|
+
def read_raster(
|
|
12
|
+
filename: Union[Path,str],
|
|
13
|
+
band: int = 1,
|
|
14
|
+
ignore_nodata: bool = False,
|
|
15
|
+
) -> RasterLayer:
|
|
16
|
+
"""Open a raster file (e.g., GeoTIFF).
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
filename : Path
|
|
21
|
+
Path of raster file to open.
|
|
22
|
+
band : int, default=1
|
|
23
|
+
For multi-band rasters, which band to use (defaults to first if not specified)
|
|
24
|
+
ignore_nodata : bool, default=False
|
|
25
|
+
If the GeoTIFF has a NODATA value, don't subsitute that value for NaN
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
RasterLayer
|
|
30
|
+
Returns an layer representing the raster data.
|
|
31
|
+
"""
|
|
32
|
+
return RasterLayer.layer_from_file(filename, band, ignore_nodata)
|
|
33
|
+
|
|
34
|
+
def read_rasters(
|
|
35
|
+
filenames : Union[List[Path],List[str]],
|
|
36
|
+
tiled: bool=False
|
|
37
|
+
) -> GroupLayer:
|
|
38
|
+
"""Open a set of raster files (e.g., GeoTIFFs) as a single layer.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
filenames : List[Path]
|
|
43
|
+
List of paths of raster files to open.
|
|
44
|
+
tiled : bool, default=False
|
|
45
|
+
If you know that the rasters for a regular tileset, then setting this flag allows
|
|
46
|
+
Yirgacheffe to perform certain optimisations that significantly improve performance for
|
|
47
|
+
this use case.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
GroupLayer
|
|
52
|
+
Returns an layer representing the raster data.
|
|
53
|
+
"""
|
|
54
|
+
if not tiled:
|
|
55
|
+
return GroupLayer.layer_from_files(filenames)
|
|
56
|
+
else:
|
|
57
|
+
return TiledGroupLayer.layer_from_files(filenames)
|
|
58
|
+
|
|
59
|
+
def read_shape(
|
|
60
|
+
filename: Union[Path,str],
|
|
61
|
+
scale: Union[PixelScale, Tuple[float,float]],
|
|
62
|
+
projection: str,
|
|
63
|
+
where_filter: Optional[str] = None,
|
|
64
|
+
datatype: Optional[DataType] = None,
|
|
65
|
+
burn_value: Union[int,float,str] = 1,
|
|
66
|
+
) -> VectorLayer:
|
|
67
|
+
"""Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
filename : Path
|
|
72
|
+
Path of raster file to open.
|
|
73
|
+
scale: PixelScale or tuple of float
|
|
74
|
+
The dimensions of each pixel.
|
|
75
|
+
projection: str
|
|
76
|
+
The map projection to use
|
|
77
|
+
where_filter : str, optional
|
|
78
|
+
For use with files with many entries (e.g., GPKG), applies this filter to the data.
|
|
79
|
+
datatype: DataType, default=DataType.Byte
|
|
80
|
+
Specify the data type of the raster data generated.
|
|
81
|
+
burn_value: int or float or str, default=1
|
|
82
|
+
The value of each pixel in the polygon.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
VectorLayer
|
|
87
|
+
Returns an layer representing the vector data.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if not isinstance(scale, PixelScale):
|
|
91
|
+
scale = PixelScale(scale[0], scale[1])
|
|
92
|
+
|
|
93
|
+
return VectorLayer.layer_from_file(
|
|
94
|
+
filename,
|
|
95
|
+
where_filter,
|
|
96
|
+
scale,
|
|
97
|
+
projection,
|
|
98
|
+
datatype,
|
|
99
|
+
burn_value
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def read_shape_like(
|
|
103
|
+
filename: Union[Path,str],
|
|
104
|
+
like: YirgacheffeLayer,
|
|
105
|
+
where_filter: Optional[str] = None,
|
|
106
|
+
datatype: Optional[DataType] = None,
|
|
107
|
+
burn_value: Union[int,float,str] = 1,
|
|
108
|
+
) -> VectorLayer:
|
|
109
|
+
"""Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
filename : Path
|
|
114
|
+
Path of raster file to open.
|
|
115
|
+
like: YirgacheffeLayer
|
|
116
|
+
Another layer that has a projection and pixel scale set. This layer will
|
|
117
|
+
use the same projection and pixel scale as that one.
|
|
118
|
+
where_filter : str, optional
|
|
119
|
+
For use with files with many entries (e.g., GPKG), applies this filter to the data.
|
|
120
|
+
datatype: DataType, default=DataType.Byte
|
|
121
|
+
Specify the data type of the raster data generated.
|
|
122
|
+
burn_value: int or float or str, default=1
|
|
123
|
+
The value of each pixel in the polygon.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
VectorLayer
|
|
128
|
+
Returns an layer representing the vector data.
|
|
129
|
+
"""
|
|
130
|
+
return VectorLayer.layer_from_file_like(
|
|
131
|
+
filename,
|
|
132
|
+
like,
|
|
133
|
+
where_filter,
|
|
134
|
+
datatype,
|
|
135
|
+
burn_value,
|
|
136
|
+
)
|
yirgacheffe/constants.py
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
YSTEP = 512
|
|
2
2
|
MINIMUM_CHUNKS_PER_THREAD = 1
|
|
3
|
+
|
|
4
|
+
# I don't really want this here, but it's just too useful having it exposed
|
|
5
|
+
WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,'\
|
|
6
|
+
'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
|
|
7
|
+
'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
|
|
8
|
+
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
|
yirgacheffe/layers/area.py
CHANGED
|
@@ -56,12 +56,12 @@ class UniformAreaLayer(RasterLayer):
|
|
|
56
56
|
return False
|
|
57
57
|
return True
|
|
58
58
|
|
|
59
|
-
def __init__(self, dataset, name: Optional[str] = None, band: int = 1):
|
|
59
|
+
def __init__(self, dataset, name: Optional[str] = None, band: int = 1, ignore_nodata: bool = False):
|
|
60
60
|
if dataset.RasterXSize > 1:
|
|
61
61
|
raise ValueError("Expected a shrunk dataset")
|
|
62
62
|
self.databand = dataset.GetRasterBand(1).ReadAsArray(0, 0, 1, dataset.RasterYSize)
|
|
63
63
|
|
|
64
|
-
super().__init__(dataset, name, band)
|
|
64
|
+
super().__init__(dataset, name, band, ignore_nodata)
|
|
65
65
|
|
|
66
66
|
transform = dataset.GetGeoTransform()
|
|
67
67
|
|
|
@@ -84,7 +84,14 @@ class UniformAreaLayer(RasterLayer):
|
|
|
84
84
|
)
|
|
85
85
|
self._raster_xsize = self.window.xsize
|
|
86
86
|
|
|
87
|
-
def
|
|
87
|
+
def _read_array_with_window(
|
|
88
|
+
self,
|
|
89
|
+
xoffset: int,
|
|
90
|
+
yoffset: int,
|
|
91
|
+
xsize: int,
|
|
92
|
+
ysize: int,
|
|
93
|
+
window: Window,
|
|
94
|
+
) -> Any:
|
|
88
95
|
if ysize <= 0:
|
|
89
96
|
raise ValueError("Request dimensions must be positive and non-zero")
|
|
90
97
|
offset = window.yoff + yoffset
|
yirgacheffe/layers/base.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import Any,
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Optional, Sequence, Tuple
|
|
3
3
|
|
|
4
4
|
from ..operators import DataType, LayerMathMixin
|
|
5
5
|
from ..rounding import almost_equal, are_pixel_scales_equal_enough, round_up_pixels, round_down_pixels
|
|
@@ -66,8 +66,12 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
66
66
|
raise AttributeError("Layer has no window")
|
|
67
67
|
return self._window
|
|
68
68
|
|
|
69
|
+
@property
|
|
70
|
+
def nodata(self) -> None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
69
73
|
@staticmethod
|
|
70
|
-
def find_intersection(layers:
|
|
74
|
+
def find_intersection(layers: Sequence[YirgacheffeLayer]) -> Area:
|
|
71
75
|
if not layers:
|
|
72
76
|
raise ValueError("Expected list of layers")
|
|
73
77
|
|
|
@@ -87,7 +91,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
87
91
|
return intersection
|
|
88
92
|
|
|
89
93
|
@staticmethod
|
|
90
|
-
def find_union(layers:
|
|
94
|
+
def find_union(layers: Sequence[YirgacheffeLayer]) -> Area:
|
|
91
95
|
if not layers:
|
|
92
96
|
raise ValueError("Expected list of layers")
|
|
93
97
|
|
|
@@ -183,7 +187,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
183
187
|
self._window = new_window
|
|
184
188
|
self._active_area = new_area
|
|
185
189
|
|
|
186
|
-
def reset_window(self):
|
|
190
|
+
def reset_window(self) -> None:
|
|
187
191
|
self._active_area = self._underlying_area
|
|
188
192
|
if self._pixel_scale:
|
|
189
193
|
abs_xstep, abs_ystep = abs(self._pixel_scale.xstep), abs(self._pixel_scale.ystep)
|
|
@@ -194,7 +198,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
194
198
|
ysize=round_up_pixels((self.area.bottom - self.area.top) / self._pixel_scale.ystep, abs_ystep),
|
|
195
199
|
)
|
|
196
200
|
|
|
197
|
-
def offset_window_by_pixels(self, offset: int):
|
|
201
|
+
def offset_window_by_pixels(self, offset: int) -> None:
|
|
198
202
|
"""Grows (if pixels is positive) or shrinks (if pixels is negative) the window for the layer."""
|
|
199
203
|
if offset == 0:
|
|
200
204
|
return
|
|
@@ -227,10 +231,24 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
227
231
|
self._window = new_window
|
|
228
232
|
self._active_area = new_area
|
|
229
233
|
|
|
230
|
-
def
|
|
234
|
+
def _read_array_with_window(
|
|
235
|
+
self,
|
|
236
|
+
_x: int,
|
|
237
|
+
_y: int,
|
|
238
|
+
_xsize: int,
|
|
239
|
+
_ysize: int,
|
|
240
|
+
_window: Window,
|
|
241
|
+
) -> Any:
|
|
231
242
|
raise NotImplementedError("Must be overridden by subclass")
|
|
232
243
|
|
|
233
|
-
def
|
|
244
|
+
def _read_array_for_area(
|
|
245
|
+
self,
|
|
246
|
+
target_area: Area,
|
|
247
|
+
x: int,
|
|
248
|
+
y: int,
|
|
249
|
+
width: int,
|
|
250
|
+
height: int,
|
|
251
|
+
) -> Any:
|
|
234
252
|
assert self._pixel_scale is not None
|
|
235
253
|
|
|
236
254
|
target_window = Window(
|
|
@@ -247,10 +265,28 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
247
265
|
(self._pixel_scale.ystep * -1.0)
|
|
248
266
|
),
|
|
249
267
|
)
|
|
250
|
-
return self.
|
|
268
|
+
return self._read_array_with_window(x, y, width, height, target_window)
|
|
251
269
|
|
|
252
270
|
def read_array(self, x: int, y: int, width: int, height: int) -> Any:
|
|
253
|
-
|
|
271
|
+
"""Reads data from the layer based on the current reference window.
|
|
272
|
+
|
|
273
|
+
Arguments
|
|
274
|
+
---------
|
|
275
|
+
x : int
|
|
276
|
+
X axis offset for reading
|
|
277
|
+
y : int
|
|
278
|
+
Y axis offset for reading
|
|
279
|
+
width : int
|
|
280
|
+
Width of data to read
|
|
281
|
+
height : int
|
|
282
|
+
Height of data to read
|
|
283
|
+
|
|
284
|
+
Results
|
|
285
|
+
-------
|
|
286
|
+
Any
|
|
287
|
+
An array of values from the layer.
|
|
288
|
+
"""
|
|
289
|
+
return self._read_array_with_window(x, y, width, height, self.window)
|
|
254
290
|
|
|
255
291
|
def latlng_for_pixel(self, x_coord: int, y_coord: int) -> Tuple[float,float]:
|
|
256
292
|
"""Get geo coords for pixel. This is relative to the set view window."""
|
yirgacheffe/layers/constant.py
CHANGED
|
@@ -4,19 +4,14 @@ from ..operators import DataType
|
|
|
4
4
|
from ..window import Area, PixelScale, Window
|
|
5
5
|
from .base import YirgacheffeLayer
|
|
6
6
|
from .._backends import backend
|
|
7
|
-
from .. import WGS_84_PROJECTION
|
|
7
|
+
from ..constants import WGS_84_PROJECTION
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ConstantLayer(YirgacheffeLayer):
|
|
11
11
|
"""This is a layer that will return the identity value - can be used when an input layer is
|
|
12
12
|
missing (e.g., area) without having the calculation full of branches."""
|
|
13
13
|
def __init__(self, value: Union[int,float]): # pylint: disable=W0231
|
|
14
|
-
area = Area(
|
|
15
|
-
left = -180.0,
|
|
16
|
-
top = 90.0,
|
|
17
|
-
right = 180.0,
|
|
18
|
-
bottom = -90.0
|
|
19
|
-
)
|
|
14
|
+
area = Area.world()
|
|
20
15
|
super().__init__(area, None, WGS_84_PROJECTION)
|
|
21
16
|
self.value = float(value)
|
|
22
17
|
|
|
@@ -33,8 +28,22 @@ class ConstantLayer(YirgacheffeLayer):
|
|
|
33
28
|
def read_array(self, _x: int, _y: int, width: int, height: int) -> Any:
|
|
34
29
|
return backend.full((height, width), self.value)
|
|
35
30
|
|
|
36
|
-
def
|
|
31
|
+
def _read_array_with_window(
|
|
32
|
+
self,
|
|
33
|
+
_x: int,
|
|
34
|
+
_y: int,
|
|
35
|
+
width: int,
|
|
36
|
+
height: int,
|
|
37
|
+
_window: Window,
|
|
38
|
+
) -> Any:
|
|
37
39
|
return backend.full((height, width), self.value)
|
|
38
40
|
|
|
39
|
-
def
|
|
41
|
+
def _read_array_for_area(
|
|
42
|
+
self,
|
|
43
|
+
_target_area: Area,
|
|
44
|
+
x: int,
|
|
45
|
+
y: int,
|
|
46
|
+
width: int,
|
|
47
|
+
height: int,
|
|
48
|
+
) -> Any:
|
|
40
49
|
return self.read_array(x, y, width, height)
|
yirgacheffe/layers/group.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import copy
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
from typing import Any, List, Optional
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, List, Optional, Union
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
|
-
from
|
|
7
|
+
from numpy import ma
|
|
9
8
|
|
|
9
|
+
from ..operators import DataType
|
|
10
10
|
from ..rounding import are_pixel_scales_equal_enough, round_down_pixels
|
|
11
11
|
from ..window import Area, Window
|
|
12
12
|
from .base import YirgacheffeLayer
|
|
@@ -23,19 +23,25 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
23
23
|
@classmethod
|
|
24
24
|
def layer_from_directory(
|
|
25
25
|
cls,
|
|
26
|
-
directory_path: str,
|
|
26
|
+
directory_path: Union[Path,str],
|
|
27
27
|
name: Optional[str] = None,
|
|
28
28
|
matching: str = "*.tif"
|
|
29
29
|
) -> GroupLayer:
|
|
30
30
|
if directory_path is None:
|
|
31
31
|
raise ValueError("Directory path is None")
|
|
32
|
-
|
|
32
|
+
if isinstance(directory_path, str):
|
|
33
|
+
directory_path = Path(directory_path)
|
|
34
|
+
files = list(directory_path.glob(matching))
|
|
33
35
|
if len(files) < 1:
|
|
34
36
|
raise GroupLayerEmpty(directory_path)
|
|
35
37
|
return cls.layer_from_files(files, name)
|
|
36
38
|
|
|
37
39
|
@classmethod
|
|
38
|
-
def layer_from_files(
|
|
40
|
+
def layer_from_files(
|
|
41
|
+
cls,
|
|
42
|
+
filenames: Union[List[Path],List[str]],
|
|
43
|
+
name: Optional[str] = None
|
|
44
|
+
) -> GroupLayer:
|
|
39
45
|
if filenames is None:
|
|
40
46
|
raise ValueError("filenames argument is None")
|
|
41
47
|
if len(filenames) < 1:
|
|
@@ -43,7 +49,11 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
43
49
|
rasters: List[YirgacheffeLayer] = [RasterLayer.layer_from_file(x) for x in filenames]
|
|
44
50
|
return cls(rasters, name)
|
|
45
51
|
|
|
46
|
-
def __init__(
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
layers: List[YirgacheffeLayer],
|
|
55
|
+
name: Optional[str] = None
|
|
56
|
+
) -> None:
|
|
47
57
|
if not layers:
|
|
48
58
|
raise GroupLayerEmpty("Expected one or more layers")
|
|
49
59
|
if not are_pixel_scales_equal_enough([x.pixel_scale for x in layers]):
|
|
@@ -95,8 +105,14 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
95
105
|
except AttributeError:
|
|
96
106
|
pass # called from Base constructor before we've added the extra field
|
|
97
107
|
|
|
98
|
-
def
|
|
99
|
-
|
|
108
|
+
def _read_array_with_window(
|
|
109
|
+
self,
|
|
110
|
+
xoffset: int,
|
|
111
|
+
yoffset: int,
|
|
112
|
+
xsize: int,
|
|
113
|
+
ysize: int,
|
|
114
|
+
window: Window,
|
|
115
|
+
) -> Any:
|
|
100
116
|
if (xsize <= 0) or (ysize <= 0):
|
|
101
117
|
raise ValueError("Request dimensions must be positive and non-zero")
|
|
102
118
|
|
|
@@ -130,12 +146,15 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
130
146
|
if len(contributing_layers) == 1:
|
|
131
147
|
layer, adjusted_layer_window, intersection = contributing_layers[0]
|
|
132
148
|
if target_window == intersection:
|
|
133
|
-
|
|
149
|
+
data = layer.read_array(
|
|
134
150
|
intersection.xoff - adjusted_layer_window.xoff,
|
|
135
151
|
intersection.yoff - adjusted_layer_window.yoff,
|
|
136
152
|
intersection.xsize,
|
|
137
153
|
intersection.ysize
|
|
138
154
|
)
|
|
155
|
+
if layer.nodata is not None:
|
|
156
|
+
data[data.isnan()] = 0.0
|
|
157
|
+
return data
|
|
139
158
|
|
|
140
159
|
result = np.zeros((ysize, xsize), dtype=float)
|
|
141
160
|
for layer, adjusted_layer_window, intersection in contributing_layers:
|
|
@@ -143,14 +162,26 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
143
162
|
intersection.xoff - adjusted_layer_window.xoff,
|
|
144
163
|
intersection.yoff - adjusted_layer_window.yoff,
|
|
145
164
|
intersection.xsize,
|
|
146
|
-
intersection.ysize
|
|
165
|
+
intersection.ysize,
|
|
147
166
|
)
|
|
148
167
|
result_x_offset = (intersection.xoff - xoffset) - window.xoff
|
|
149
168
|
result_y_offset = (intersection.yoff - yoffset) - window.yoff
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
if layer.nodata is None:
|
|
170
|
+
result[
|
|
171
|
+
result_y_offset:result_y_offset + intersection.ysize,
|
|
172
|
+
result_x_offset:result_x_offset + intersection.xsize
|
|
173
|
+
] = data
|
|
174
|
+
else:
|
|
175
|
+
masked = ma.masked_invalid(data)
|
|
176
|
+
before = result[
|
|
177
|
+
result_y_offset:result_y_offset + intersection.ysize,
|
|
178
|
+
result_x_offset:result_x_offset + intersection.xsize
|
|
179
|
+
]
|
|
180
|
+
merged = ma.where(masked.mask, before, masked)
|
|
181
|
+
result[
|
|
182
|
+
result_y_offset:result_y_offset + intersection.ysize,
|
|
183
|
+
result_x_offset:result_x_offset + intersection.xsize
|
|
184
|
+
] = merged
|
|
154
185
|
|
|
155
186
|
return backend.promote(result)
|
|
156
187
|
|
|
@@ -205,7 +236,14 @@ class TiledGroupLayer(GroupLayer):
|
|
|
205
236
|
* You can have missing tiles, and it'll fill in zeros.
|
|
206
237
|
* The tiles can overlap - e.g., JRC Annual Change tiles all overlap by a few pixels on all edges.
|
|
207
238
|
"""
|
|
208
|
-
def
|
|
239
|
+
def _read_array_with_window(
|
|
240
|
+
self,
|
|
241
|
+
xoffset: int,
|
|
242
|
+
yoffset: int,
|
|
243
|
+
xsize: int,
|
|
244
|
+
ysize: int,
|
|
245
|
+
window: Window,
|
|
246
|
+
) -> Any:
|
|
209
247
|
if (xsize <= 0) or (ysize <= 0):
|
|
210
248
|
raise ValueError("Request dimensions must be positive and non-zero")
|
|
211
249
|
|
yirgacheffe/layers/h3layer.py
CHANGED
|
@@ -82,7 +82,14 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
82
82
|
def datatype(self) -> DataType:
|
|
83
83
|
return DataType.Float64
|
|
84
84
|
|
|
85
|
-
def
|
|
85
|
+
def _read_array_with_window(
|
|
86
|
+
self,
|
|
87
|
+
xoffset: int,
|
|
88
|
+
yoffset: int,
|
|
89
|
+
xsize: int,
|
|
90
|
+
ysize: int,
|
|
91
|
+
window: Window,
|
|
92
|
+
) -> Any:
|
|
86
93
|
assert self._pixel_scale is not None
|
|
87
94
|
|
|
88
95
|
if (xsize <= 0) or (ysize <= 0):
|
yirgacheffe/layers/rasters.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import math
|
|
3
|
-
import
|
|
3
|
+
from pathlib import Path
|
|
4
4
|
from typing import Any, Optional, Tuple, Union
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from osgeo import gdal
|
|
8
8
|
|
|
9
|
-
from .. import WGS_84_PROJECTION
|
|
9
|
+
from ..constants import WGS_84_PROJECTION
|
|
10
10
|
from ..window import Area, PixelScale, Window
|
|
11
11
|
from ..rounding import round_up_pixels
|
|
12
12
|
from .base import YirgacheffeLayer
|
|
@@ -88,7 +88,7 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
88
88
|
@staticmethod
|
|
89
89
|
def empty_raster_layer_like(
|
|
90
90
|
layer: Any,
|
|
91
|
-
filename: Optional[str]=None,
|
|
91
|
+
filename: Optional[Union[Path,str]]=None,
|
|
92
92
|
area: Optional[Area]=None,
|
|
93
93
|
datatype: Optional[Union[int, DataType]]=None,
|
|
94
94
|
compress: bool=True,
|
|
@@ -214,7 +214,12 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
214
214
|
return RasterLayer(dataset)
|
|
215
215
|
|
|
216
216
|
@classmethod
|
|
217
|
-
def layer_from_file(
|
|
217
|
+
def layer_from_file(
|
|
218
|
+
cls,
|
|
219
|
+
filename: Union[Path,str],
|
|
220
|
+
band: int = 1,
|
|
221
|
+
ignore_nodata: bool = False,
|
|
222
|
+
) -> RasterLayer:
|
|
218
223
|
try:
|
|
219
224
|
dataset = gdal.Open(filename, gdal.GA_ReadOnly)
|
|
220
225
|
except RuntimeError as exc:
|
|
@@ -224,9 +229,15 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
224
229
|
_ = dataset.GetRasterBand(band)
|
|
225
230
|
except RuntimeError as exc:
|
|
226
231
|
raise InvalidRasterBand(band) from exc
|
|
227
|
-
return cls(dataset, filename, band)
|
|
228
|
-
|
|
229
|
-
def __init__(
|
|
232
|
+
return cls(dataset, str(filename), band, ignore_nodata)
|
|
233
|
+
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
dataset: gdal.Dataset,
|
|
237
|
+
name: Optional[str] = None,
|
|
238
|
+
band: int = 1,
|
|
239
|
+
ignore_nodata: bool = False,
|
|
240
|
+
) -> None:
|
|
230
241
|
if not dataset:
|
|
231
242
|
raise ValueError("None is not a valid dataset")
|
|
232
243
|
|
|
@@ -252,10 +263,11 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
252
263
|
assert self.window == Window(0, 0, dataset.RasterXSize, dataset.RasterYSize)
|
|
253
264
|
|
|
254
265
|
self._dataset = dataset
|
|
255
|
-
self._dataset_path = dataset.GetDescription()
|
|
266
|
+
self._dataset_path = Path(dataset.GetDescription())
|
|
256
267
|
self._band = band
|
|
257
268
|
self._raster_xsize = dataset.RasterXSize
|
|
258
269
|
self._raster_ysize = dataset.RasterYSize
|
|
270
|
+
self._ignore_nodata = ignore_nodata
|
|
259
271
|
|
|
260
272
|
@property
|
|
261
273
|
def _raster_dimensions(self) -> Tuple[int,int]:
|
|
@@ -275,7 +287,7 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
275
287
|
|
|
276
288
|
def __getstate__(self) -> object:
|
|
277
289
|
# Only support pickling on file backed layers (ideally read only ones...)
|
|
278
|
-
if not
|
|
290
|
+
if not self._dataset_path.exists():
|
|
279
291
|
raise ValueError("Can not pickle layer that is not file backed.")
|
|
280
292
|
odict = self.__dict__.copy()
|
|
281
293
|
self._park()
|
|
@@ -305,7 +317,21 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
305
317
|
assert self._dataset
|
|
306
318
|
return DataType.of_gdal(self._dataset.GetRasterBand(1).DataType)
|
|
307
319
|
|
|
308
|
-
|
|
320
|
+
@property
|
|
321
|
+
def nodata(self) -> Optional[Any]:
|
|
322
|
+
if self._dataset is None:
|
|
323
|
+
self._unpark()
|
|
324
|
+
assert self._dataset
|
|
325
|
+
return self._dataset.GetRasterBand(self._band).GetNoDataValue()
|
|
326
|
+
|
|
327
|
+
def _read_array_with_window(
|
|
328
|
+
self,
|
|
329
|
+
xoffset: int,
|
|
330
|
+
yoffset: int,
|
|
331
|
+
xsize: int,
|
|
332
|
+
ysize: int,
|
|
333
|
+
window: Window,
|
|
334
|
+
) -> Any:
|
|
309
335
|
if self._dataset is None:
|
|
310
336
|
self._unpark()
|
|
311
337
|
assert self._dataset
|
|
@@ -335,7 +361,6 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
335
361
|
if target_window == intersection:
|
|
336
362
|
# The target window is a subset of or equal to the source, so we can just ask for the data
|
|
337
363
|
data = backend.promote(self._dataset.GetRasterBand(self._band).ReadAsArray(*intersection.as_array_args))
|
|
338
|
-
return data
|
|
339
364
|
else:
|
|
340
365
|
# We should read the intersection from the array, and the rest should be zeros
|
|
341
366
|
subset = backend.promote(self._dataset.GetRasterBand(self._band).ReadAsArray(*intersection.as_array_args))
|
|
@@ -350,4 +375,9 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
350
375
|
)
|
|
351
376
|
)).astype(int)
|
|
352
377
|
data = backend.pad(subset, region, mode='constant')
|
|
353
|
-
|
|
378
|
+
|
|
379
|
+
nodata = self.nodata
|
|
380
|
+
if not self._ignore_nodata and nodata is not None:
|
|
381
|
+
data = backend.where(data == nodata, float("nan"), data)
|
|
382
|
+
|
|
383
|
+
return data
|
yirgacheffe/layers/rescaled.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from math import floor, ceil
|
|
3
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Optional, Union
|
|
4
5
|
|
|
5
6
|
from skimage import transform
|
|
6
7
|
from yirgacheffe.operators import DataType
|
|
@@ -17,7 +18,7 @@ class RescaledRasterLayer(YirgacheffeLayer):
|
|
|
17
18
|
@classmethod
|
|
18
19
|
def layer_from_file(
|
|
19
20
|
cls,
|
|
20
|
-
filename: str,
|
|
21
|
+
filename: Union[Path,str],
|
|
21
22
|
pixel_scale: PixelScale,
|
|
22
23
|
band: int = 1,
|
|
23
24
|
nearest_neighbour: bool = True,
|
|
@@ -61,7 +62,14 @@ class RescaledRasterLayer(YirgacheffeLayer):
|
|
|
61
62
|
def datatype(self) -> DataType:
|
|
62
63
|
return self._src.datatype
|
|
63
64
|
|
|
64
|
-
def
|
|
65
|
+
def _read_array_with_window(
|
|
66
|
+
self,
|
|
67
|
+
xoffset: int,
|
|
68
|
+
yoffset: int,
|
|
69
|
+
xsize: int,
|
|
70
|
+
ysize: int,
|
|
71
|
+
window: Window,
|
|
72
|
+
) -> Any:
|
|
65
73
|
|
|
66
74
|
# to avoid aliasing issues, we try to scale to the nearest pixel
|
|
67
75
|
# and recrop when scaling bigger
|