yirgacheffe 1.6.1__py3-none-any.whl → 1.7.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.
- yirgacheffe/__init__.py +9 -2
- yirgacheffe/_core.py +12 -14
- yirgacheffe/layers/area.py +4 -4
- yirgacheffe/layers/base.py +105 -63
- yirgacheffe/layers/constant.py +3 -3
- yirgacheffe/layers/group.py +13 -15
- yirgacheffe/layers/h3layer.py +17 -17
- yirgacheffe/layers/rasters.py +14 -15
- yirgacheffe/layers/rescaled.py +12 -9
- yirgacheffe/layers/vectors.py +130 -45
- yirgacheffe/operators.py +152 -39
- yirgacheffe/window.py +41 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.1.dist-info}/METADATA +11 -9
- yirgacheffe-1.7.1.dist-info/RECORD +25 -0
- yirgacheffe-1.6.1.dist-info/RECORD +0 -25
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.1.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.1.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.1.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.1.dist-info}/top_level.txt +0 -0
yirgacheffe/__init__.py
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
1
3
|
from osgeo import gdal
|
|
4
|
+
import tomli as tomllib
|
|
5
|
+
|
|
2
6
|
try:
|
|
3
7
|
from importlib import metadata
|
|
4
|
-
__version__ = metadata.version(__name__)
|
|
8
|
+
__version__: str = metadata.version(__name__)
|
|
5
9
|
except ModuleNotFoundError:
|
|
6
|
-
|
|
10
|
+
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
|
|
11
|
+
with open(pyproject_path, "rb") as f:
|
|
12
|
+
pyproject_data = tomllib.load(f)
|
|
13
|
+
__version__ = pyproject_data["project"]["version"]
|
|
7
14
|
|
|
8
15
|
from ._core import read_raster, read_rasters, read_shape, read_shape_like
|
|
9
16
|
from .constants import WGS_84_PROJECTION
|
yirgacheffe/_core.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional, Sequence, Tuple, Union
|
|
3
3
|
|
|
4
4
|
from .layers.base import YirgacheffeLayer
|
|
5
5
|
from .layers.group import GroupLayer, TiledGroupLayer
|
|
6
6
|
from .layers.rasters import RasterLayer
|
|
7
7
|
from .layers.vectors import VectorLayer
|
|
8
|
-
from .window import
|
|
8
|
+
from .window import MapProjection
|
|
9
9
|
from .operators import DataType
|
|
10
10
|
|
|
11
11
|
def read_raster(
|
|
@@ -32,7 +32,7 @@ def read_raster(
|
|
|
32
32
|
return RasterLayer.layer_from_file(filename, band, ignore_nodata)
|
|
33
33
|
|
|
34
34
|
def read_rasters(
|
|
35
|
-
filenames : Union[
|
|
35
|
+
filenames : Sequence[Union[Path,str]],
|
|
36
36
|
tiled: bool=False
|
|
37
37
|
) -> GroupLayer:
|
|
38
38
|
"""Open a set of raster files (e.g., GeoTIFFs) as a single layer.
|
|
@@ -58,8 +58,7 @@ def read_rasters(
|
|
|
58
58
|
|
|
59
59
|
def read_shape(
|
|
60
60
|
filename: Union[Path,str],
|
|
61
|
-
|
|
62
|
-
projection: str,
|
|
61
|
+
projection: Union[Optional[MapProjection],Optional[Tuple[str,Tuple[float,float]]]]=None,
|
|
63
62
|
where_filter: Optional[str] = None,
|
|
64
63
|
datatype: Optional[DataType] = None,
|
|
65
64
|
burn_value: Union[int,float,str] = 1,
|
|
@@ -70,10 +69,8 @@ def read_shape(
|
|
|
70
69
|
----------
|
|
71
70
|
filename : Path
|
|
72
71
|
Path of raster file to open.
|
|
73
|
-
|
|
74
|
-
The
|
|
75
|
-
projection: str
|
|
76
|
-
The map projection to use
|
|
72
|
+
projection: MapProjection or tuple, optional
|
|
73
|
+
The map projection to use,
|
|
77
74
|
where_filter : str, optional
|
|
78
75
|
For use with files with many entries (e.g., GPKG), applies this filter to the data.
|
|
79
76
|
datatype: DataType, default=DataType.Byte
|
|
@@ -87,16 +84,17 @@ def read_shape(
|
|
|
87
84
|
Returns an layer representing the vector data.
|
|
88
85
|
"""
|
|
89
86
|
|
|
90
|
-
if not
|
|
91
|
-
|
|
87
|
+
if projection is not None:
|
|
88
|
+
if not isinstance(projection, MapProjection):
|
|
89
|
+
projection_name, scale_tuple = projection
|
|
90
|
+
projection = MapProjection(projection_name, scale_tuple[0], scale_tuple[1])
|
|
92
91
|
|
|
93
|
-
return VectorLayer.
|
|
92
|
+
return VectorLayer._future_layer_from_file(
|
|
94
93
|
filename,
|
|
95
94
|
where_filter,
|
|
96
|
-
scale,
|
|
97
95
|
projection,
|
|
98
96
|
datatype,
|
|
99
|
-
burn_value
|
|
97
|
+
burn_value,
|
|
100
98
|
)
|
|
101
99
|
|
|
102
100
|
def read_shape_like(
|
yirgacheffe/layers/area.py
CHANGED
|
@@ -65,13 +65,13 @@ class UniformAreaLayer(RasterLayer):
|
|
|
65
65
|
|
|
66
66
|
transform = dataset.GetGeoTransform()
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
assert
|
|
68
|
+
projection = self.map_projection
|
|
69
|
+
assert projection is not None # from raster we should always have one
|
|
70
70
|
|
|
71
71
|
self._underlying_area = Area(
|
|
72
|
-
floor(-180 /
|
|
72
|
+
floor(-180 / projection.xstep) * projection.xstep,
|
|
73
73
|
self.area.top,
|
|
74
|
-
ceil(180 /
|
|
74
|
+
ceil(180 / projection.xstep) * projection.xstep,
|
|
75
75
|
self.area.bottom
|
|
76
76
|
)
|
|
77
77
|
self._active_area = self._underlying_area
|
yirgacheffe/layers/base.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Any, Optional, Sequence, Tuple
|
|
3
3
|
|
|
4
|
+
import deprecation
|
|
5
|
+
|
|
6
|
+
from .. import __version__
|
|
4
7
|
from ..operators import DataType, LayerMathMixin
|
|
5
|
-
from ..rounding import almost_equal,
|
|
6
|
-
from ..window import Area, PixelScale, Window
|
|
8
|
+
from ..rounding import almost_equal, round_up_pixels, round_down_pixels
|
|
9
|
+
from ..window import Area, MapProjection, PixelScale, Window
|
|
10
|
+
from .._backends import backend
|
|
7
11
|
|
|
8
12
|
class YirgacheffeLayer(LayerMathMixin):
|
|
9
13
|
"""The common base class for the different layer types. Most still inherit from RasterLayer as deep down
|
|
@@ -12,13 +16,15 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
12
16
|
|
|
13
17
|
def __init__(self,
|
|
14
18
|
area: Area,
|
|
15
|
-
|
|
16
|
-
projection: str,
|
|
19
|
+
projection: Optional[MapProjection],
|
|
17
20
|
name: Optional[str] = None
|
|
18
21
|
):
|
|
19
|
-
|
|
22
|
+
# This is just to catch code that uses the old private API
|
|
23
|
+
if projection is not None and not isinstance(projection, MapProjection):
|
|
24
|
+
raise TypeError("projection value of wrong type")
|
|
25
|
+
|
|
20
26
|
self._underlying_area = area
|
|
21
|
-
self._active_area =
|
|
27
|
+
self._active_area: Optional[Area] = None
|
|
22
28
|
self._projection = projection
|
|
23
29
|
self._window: Optional[Window] = None
|
|
24
30
|
self.name = name
|
|
@@ -49,16 +55,46 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
49
55
|
raise NotImplementedError("Must be overridden by subclass")
|
|
50
56
|
|
|
51
57
|
@property
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
@deprecation.deprecated(
|
|
59
|
+
deprecated_in="1.7",
|
|
60
|
+
removed_in="2.0",
|
|
61
|
+
current_version=__version__,
|
|
62
|
+
details="Use `map_projection` instead."
|
|
63
|
+
)
|
|
64
|
+
def projection(self) -> Optional[str]:
|
|
65
|
+
if self._projection:
|
|
66
|
+
return self._projection.name
|
|
67
|
+
else:
|
|
68
|
+
return None
|
|
54
69
|
|
|
55
70
|
@property
|
|
71
|
+
@deprecation.deprecated(
|
|
72
|
+
deprecated_in="1.7",
|
|
73
|
+
removed_in="2.0",
|
|
74
|
+
current_version=__version__,
|
|
75
|
+
details="Use `map_projection` instead."
|
|
76
|
+
)
|
|
56
77
|
def pixel_scale(self) -> Optional[PixelScale]:
|
|
57
|
-
|
|
78
|
+
if self._projection:
|
|
79
|
+
return PixelScale(self._projection.xstep, self._projection.ystep)
|
|
80
|
+
else:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def map_projection(self) -> Optional[MapProjection]:
|
|
85
|
+
return self._projection
|
|
58
86
|
|
|
59
87
|
@property
|
|
60
88
|
def area(self) -> Area:
|
|
61
|
-
|
|
89
|
+
if self._active_area is not None:
|
|
90
|
+
return self._active_area
|
|
91
|
+
else:
|
|
92
|
+
return self._underlying_area
|
|
93
|
+
|
|
94
|
+
def _get_operation_area(self, projection: Optional[MapProjection]) -> Area:
|
|
95
|
+
if self._projection is not None and projection is not None and self._projection != projection:
|
|
96
|
+
raise ValueError("Calculation projection does not match layer projection")
|
|
97
|
+
return self.area
|
|
62
98
|
|
|
63
99
|
@property
|
|
64
100
|
def window(self) -> Window:
|
|
@@ -77,8 +113,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
77
113
|
|
|
78
114
|
# This only makes sense (currently) if all layers
|
|
79
115
|
# have the same pixel pitch (modulo desired accuracy)
|
|
80
|
-
|
|
81
|
-
|
|
116
|
+
projections = [x.map_projection for x in layers if x.map_projection is not None]
|
|
117
|
+
if not projections:
|
|
118
|
+
raise ValueError("No layers have a projection")
|
|
119
|
+
if not all(projections[0] == x for x in projections[1:]):
|
|
120
|
+
raise ValueError("Not all layers are at the same projectin or pixel scale")
|
|
82
121
|
|
|
83
122
|
intersection = Area(
|
|
84
123
|
left=max(x._underlying_area.left for x in layers),
|
|
@@ -97,8 +136,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
97
136
|
|
|
98
137
|
# This only makes sense (currently) if all layers
|
|
99
138
|
# have the same pixel pitch (modulo desired accuracy)
|
|
100
|
-
|
|
101
|
-
|
|
139
|
+
projections = [x.map_projection for x in layers if x.map_projection is not None]
|
|
140
|
+
if not projections:
|
|
141
|
+
raise ValueError("No layers have a projection")
|
|
142
|
+
if not all(projections[0] == x for x in projections[1:]):
|
|
143
|
+
raise ValueError("Not all layers are at the same projectin or pixel scale")
|
|
102
144
|
|
|
103
145
|
return Area(
|
|
104
146
|
left=min(x._underlying_area.left for x in layers),
|
|
@@ -109,11 +151,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
109
151
|
|
|
110
152
|
@property
|
|
111
153
|
def geo_transform(self) -> Tuple[float, float, float, float, float, float]:
|
|
112
|
-
if self.
|
|
113
|
-
raise
|
|
154
|
+
if self._projection is None:
|
|
155
|
+
raise AttributeError("No geo transform for layers without explicit pixel scale")
|
|
114
156
|
return (
|
|
115
|
-
self.
|
|
116
|
-
self.
|
|
157
|
+
self.area.left, self._projection.xstep, 0.0,
|
|
158
|
+
self.area.top, 0.0, self._projection.ystep
|
|
117
159
|
)
|
|
118
160
|
|
|
119
161
|
def check_pixel_scale(self, scale: PixelScale) -> bool:
|
|
@@ -124,21 +166,21 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
124
166
|
almost_equal(our_scale.ystep, scale.ystep)
|
|
125
167
|
|
|
126
168
|
def set_window_for_intersection(self, new_area: Area) -> None:
|
|
127
|
-
if self.
|
|
169
|
+
if self._projection is None:
|
|
128
170
|
raise ValueError("Can not set Window without explicit pixel scale")
|
|
129
171
|
|
|
130
172
|
new_window = Window(
|
|
131
|
-
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self.
|
|
132
|
-
self.
|
|
133
|
-
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self.
|
|
134
|
-
self.
|
|
173
|
+
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
174
|
+
self._projection.xstep),
|
|
175
|
+
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self._projection.ystep * -1.0),
|
|
176
|
+
self._projection.ystep * -1.0),
|
|
135
177
|
xsize=round_up_pixels(
|
|
136
|
-
(new_area.right - new_area.left) / self.
|
|
137
|
-
self.
|
|
178
|
+
(new_area.right - new_area.left) / self._projection.xstep,
|
|
179
|
+
self._projection.xstep
|
|
138
180
|
),
|
|
139
181
|
ysize=round_up_pixels(
|
|
140
|
-
(new_area.top - new_area.bottom) / (self.
|
|
141
|
-
(self.
|
|
182
|
+
(new_area.top - new_area.bottom) / (self._projection.ystep * -1.0),
|
|
183
|
+
(self._projection.ystep * -1.0)
|
|
142
184
|
),
|
|
143
185
|
)
|
|
144
186
|
if (new_window.xoff < 0) or (new_window.yoff < 0):
|
|
@@ -156,21 +198,21 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
156
198
|
self._active_area = new_area
|
|
157
199
|
|
|
158
200
|
def set_window_for_union(self, new_area: Area) -> None:
|
|
159
|
-
if self.
|
|
201
|
+
if self._projection is None:
|
|
160
202
|
raise ValueError("Can not set Window without explicit pixel scale")
|
|
161
203
|
|
|
162
204
|
new_window = Window(
|
|
163
|
-
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self.
|
|
164
|
-
self.
|
|
165
|
-
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self.
|
|
166
|
-
self.
|
|
205
|
+
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
206
|
+
self._projection.xstep),
|
|
207
|
+
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self._projection.ystep * -1.0),
|
|
208
|
+
self._projection.ystep * -1.0),
|
|
167
209
|
xsize=round_up_pixels(
|
|
168
|
-
(new_area.right - new_area.left) / self.
|
|
169
|
-
self.
|
|
210
|
+
(new_area.right - new_area.left) / self._projection.xstep,
|
|
211
|
+
self._projection.xstep
|
|
170
212
|
),
|
|
171
213
|
ysize=round_up_pixels(
|
|
172
|
-
(new_area.top - new_area.bottom) / (self.
|
|
173
|
-
(self.
|
|
214
|
+
(new_area.top - new_area.bottom) / (self._projection.ystep * -1.0),
|
|
215
|
+
(self._projection.ystep * -1.0)
|
|
174
216
|
),
|
|
175
217
|
)
|
|
176
218
|
if (new_window.xoff > 0) or (new_window.yoff > 0):
|
|
@@ -188,14 +230,14 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
188
230
|
self._active_area = new_area
|
|
189
231
|
|
|
190
232
|
def reset_window(self) -> None:
|
|
191
|
-
self._active_area =
|
|
192
|
-
if self.
|
|
193
|
-
abs_xstep, abs_ystep = abs(self.
|
|
233
|
+
self._active_area = None
|
|
234
|
+
if self._projection:
|
|
235
|
+
abs_xstep, abs_ystep = abs(self._projection.xstep), abs(self._projection.ystep)
|
|
194
236
|
self._window = Window(
|
|
195
237
|
xoff=0,
|
|
196
238
|
yoff=0,
|
|
197
|
-
xsize=round_up_pixels((self.area.right - self.area.left) / self.
|
|
198
|
-
ysize=round_up_pixels((self.area.bottom - self.area.top) / self.
|
|
239
|
+
xsize=round_up_pixels((self.area.right - self.area.left) / self._projection.xstep, abs_xstep),
|
|
240
|
+
ysize=round_up_pixels((self.area.bottom - self.area.top) / self._projection.ystep, abs_ystep),
|
|
199
241
|
)
|
|
200
242
|
|
|
201
243
|
def offset_window_by_pixels(self, offset: int) -> None:
|
|
@@ -244,29 +286,34 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
244
286
|
def _read_array_for_area(
|
|
245
287
|
self,
|
|
246
288
|
target_area: Area,
|
|
289
|
+
target_projection: MapProjection,
|
|
247
290
|
x: int,
|
|
248
291
|
y: int,
|
|
249
292
|
width: int,
|
|
250
293
|
height: int,
|
|
251
294
|
) -> Any:
|
|
252
|
-
assert self.
|
|
295
|
+
assert self._projection is not None
|
|
296
|
+
assert self._projection == target_projection
|
|
253
297
|
|
|
254
298
|
target_window = Window(
|
|
255
|
-
xoff=round_down_pixels((target_area.left - self._underlying_area.left) / self.
|
|
256
|
-
self.
|
|
257
|
-
yoff=round_down_pixels((self._underlying_area.top - target_area.top) / (self.
|
|
258
|
-
self.
|
|
299
|
+
xoff=round_down_pixels((target_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
300
|
+
self._projection.xstep),
|
|
301
|
+
yoff=round_down_pixels((self._underlying_area.top - target_area.top) / (self._projection.ystep * -1.0),
|
|
302
|
+
self._projection.ystep * -1.0),
|
|
259
303
|
xsize=round_up_pixels(
|
|
260
|
-
(target_area.right - target_area.left) / self.
|
|
261
|
-
self.
|
|
304
|
+
(target_area.right - target_area.left) / self._projection.xstep,
|
|
305
|
+
self._projection.xstep
|
|
262
306
|
),
|
|
263
307
|
ysize=round_up_pixels(
|
|
264
|
-
(target_area.top - target_area.bottom) / (self.
|
|
265
|
-
(self.
|
|
308
|
+
(target_area.top - target_area.bottom) / (self._projection.ystep * -1.0),
|
|
309
|
+
(self._projection.ystep * -1.0)
|
|
266
310
|
),
|
|
267
311
|
)
|
|
268
312
|
return self._read_array_with_window(x, y, width, height, target_window)
|
|
269
313
|
|
|
314
|
+
def _read_array(self, x: int, y: int, width: int, height: int) -> Any:
|
|
315
|
+
return self._read_array_with_window(x, y, width, height, self.window)
|
|
316
|
+
|
|
270
317
|
def read_array(self, x: int, y: int, width: int, height: int) -> Any:
|
|
271
318
|
"""Reads data from the layer based on the current reference window.
|
|
272
319
|
|
|
@@ -286,29 +333,24 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
286
333
|
Any
|
|
287
334
|
An array of values from the layer.
|
|
288
335
|
"""
|
|
289
|
-
|
|
336
|
+
res = self._read_array(x, y, width, height)
|
|
337
|
+
return backend.demote_array(res)
|
|
290
338
|
|
|
291
339
|
def latlng_for_pixel(self, x_coord: int, y_coord: int) -> Tuple[float,float]:
|
|
292
340
|
"""Get geo coords for pixel. This is relative to the set view window."""
|
|
293
|
-
if "WGS 84" not in self.
|
|
341
|
+
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
294
342
|
raise NotImplementedError("Not yet supported for other projections")
|
|
295
|
-
pixel_scale = self.pixel_scale
|
|
296
|
-
if pixel_scale is None:
|
|
297
|
-
raise ValueError("Layer has no pixel scale")
|
|
298
343
|
return (
|
|
299
|
-
(y_coord *
|
|
300
|
-
(x_coord *
|
|
344
|
+
(y_coord * self._projection.ystep) + self.area.top,
|
|
345
|
+
(x_coord * self._projection.xstep) + self.area.left
|
|
301
346
|
)
|
|
302
347
|
|
|
303
348
|
def pixel_for_latlng(self, lat: float, lng: float) -> Tuple[int,int]:
|
|
304
349
|
"""Get pixel for geo coords. This is relative to the set view window.
|
|
305
350
|
Result is rounded down to nearest pixel."""
|
|
306
|
-
if "WGS 84" not in self.
|
|
351
|
+
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
307
352
|
raise NotImplementedError("Not yet supported for other projections")
|
|
308
|
-
pixel_scale = self.pixel_scale
|
|
309
|
-
if pixel_scale is None:
|
|
310
|
-
raise ValueError("Layer has no pixel scale")
|
|
311
353
|
return (
|
|
312
|
-
round_down_pixels((lng - self.area.left) /
|
|
313
|
-
round_down_pixels((lat - self.area.top) /
|
|
354
|
+
round_down_pixels((lng - self.area.left) / self._projection.xstep, abs(self._projection.xstep)),
|
|
355
|
+
round_down_pixels((lat - self.area.top) / self._projection.ystep, abs(self._projection.ystep)),
|
|
314
356
|
)
|
yirgacheffe/layers/constant.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from typing import Any, Union
|
|
2
2
|
|
|
3
3
|
from ..operators import DataType
|
|
4
|
-
from ..window import Area, PixelScale, Window
|
|
4
|
+
from ..window import Area, MapProjection, PixelScale, Window
|
|
5
5
|
from .base import YirgacheffeLayer
|
|
6
6
|
from .._backends import backend
|
|
7
|
-
from ..constants import WGS_84_PROJECTION
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class ConstantLayer(YirgacheffeLayer):
|
|
@@ -12,7 +11,7 @@ class ConstantLayer(YirgacheffeLayer):
|
|
|
12
11
|
missing (e.g., area) without having the calculation full of branches."""
|
|
13
12
|
def __init__(self, value: Union[int,float]): # pylint: disable=W0231
|
|
14
13
|
area = Area.world()
|
|
15
|
-
super().__init__(area, None
|
|
14
|
+
super().__init__(area, None)
|
|
16
15
|
self.value = float(value)
|
|
17
16
|
|
|
18
17
|
@property
|
|
@@ -41,6 +40,7 @@ class ConstantLayer(YirgacheffeLayer):
|
|
|
41
40
|
def _read_array_for_area(
|
|
42
41
|
self,
|
|
43
42
|
_target_area: Area,
|
|
43
|
+
_target_projection: MapProjection,
|
|
44
44
|
x: int,
|
|
45
45
|
y: int,
|
|
46
46
|
width: int,
|
yirgacheffe/layers/group.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import copy
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, List, Optional, Union
|
|
4
|
+
from typing import Any, List, Optional, Sequence, Union
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from numpy import ma
|
|
8
8
|
|
|
9
9
|
from ..operators import DataType
|
|
10
|
-
from ..rounding import
|
|
10
|
+
from ..rounding import round_down_pixels
|
|
11
11
|
from ..window import Area, Window
|
|
12
12
|
from .base import YirgacheffeLayer
|
|
13
13
|
from .rasters import RasterLayer
|
|
@@ -39,14 +39,14 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
39
39
|
@classmethod
|
|
40
40
|
def layer_from_files(
|
|
41
41
|
cls,
|
|
42
|
-
filenames: Union[
|
|
42
|
+
filenames: Sequence[Union[Path,str]],
|
|
43
43
|
name: Optional[str] = None
|
|
44
44
|
) -> GroupLayer:
|
|
45
45
|
if filenames is None:
|
|
46
46
|
raise ValueError("filenames argument is None")
|
|
47
|
-
if len(filenames) < 1:
|
|
48
|
-
raise GroupLayerEmpty("No files found")
|
|
49
47
|
rasters: List[YirgacheffeLayer] = [RasterLayer.layer_from_file(x) for x in filenames]
|
|
48
|
+
if len(rasters) < 1:
|
|
49
|
+
raise GroupLayerEmpty("No files found")
|
|
50
50
|
return cls(rasters, name)
|
|
51
51
|
|
|
52
52
|
def __init__(
|
|
@@ -56,17 +56,15 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
56
56
|
) -> None:
|
|
57
57
|
if not layers:
|
|
58
58
|
raise GroupLayerEmpty("Expected one or more layers")
|
|
59
|
-
if not
|
|
60
|
-
raise ValueError("Not all layers are
|
|
61
|
-
if not all(x.projection == layers[0].projection for x in layers):
|
|
62
|
-
raise ValueError("Not all layers are the same projection")
|
|
59
|
+
if not all(x.map_projection == layers[0].map_projection for x in layers):
|
|
60
|
+
raise ValueError("Not all layers are the same projection/scale")
|
|
63
61
|
for layer in layers:
|
|
64
|
-
if layer._active_area
|
|
62
|
+
if layer._active_area is not None:
|
|
65
63
|
raise ValueError("Layers can not currently be constrained")
|
|
66
64
|
|
|
67
65
|
# area/window are superset of all tiles
|
|
68
66
|
union = YirgacheffeLayer.find_union(layers)
|
|
69
|
-
super().__init__(union, layers[0].
|
|
67
|
+
super().__init__(union, layers[0].map_projection, name=name)
|
|
70
68
|
|
|
71
69
|
# We store them in reverse order so that from the user's perspective
|
|
72
70
|
# the first layer in the list will be the most important in terms
|
|
@@ -146,7 +144,7 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
146
144
|
if len(contributing_layers) == 1:
|
|
147
145
|
layer, adjusted_layer_window, intersection = contributing_layers[0]
|
|
148
146
|
if target_window == intersection:
|
|
149
|
-
data = layer.
|
|
147
|
+
data = layer._read_array(
|
|
150
148
|
intersection.xoff - adjusted_layer_window.xoff,
|
|
151
149
|
intersection.yoff - adjusted_layer_window.yoff,
|
|
152
150
|
intersection.xsize,
|
|
@@ -158,11 +156,11 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
158
156
|
|
|
159
157
|
result = np.zeros((ysize, xsize), dtype=float)
|
|
160
158
|
for layer, adjusted_layer_window, intersection in contributing_layers:
|
|
161
|
-
data = layer.
|
|
159
|
+
data = layer._read_array(
|
|
162
160
|
intersection.xoff - adjusted_layer_window.xoff,
|
|
163
161
|
intersection.yoff - adjusted_layer_window.yoff,
|
|
164
162
|
intersection.xsize,
|
|
165
|
-
intersection.ysize
|
|
163
|
+
intersection.ysize
|
|
166
164
|
)
|
|
167
165
|
result_x_offset = (intersection.xoff - xoffset) - window.xoff
|
|
168
166
|
result_y_offset = (intersection.yoff - yoffset) - window.yoff
|
|
@@ -271,7 +269,7 @@ class TiledGroupLayer(GroupLayer):
|
|
|
271
269
|
intersection = Window.find_intersection_no_throw([target_window, adjusted_layer_window])
|
|
272
270
|
if intersection is None:
|
|
273
271
|
continue
|
|
274
|
-
data = layer.
|
|
272
|
+
data = layer._read_array(
|
|
275
273
|
intersection.xoff - adjusted_layer_window.xoff,
|
|
276
274
|
intersection.yoff - adjusted_layer_window.yoff,
|
|
277
275
|
intersection.xsize,
|
yirgacheffe/layers/h3layer.py
CHANGED
|
@@ -6,13 +6,13 @@ import numpy as np
|
|
|
6
6
|
from yirgacheffe.operators import DataType
|
|
7
7
|
|
|
8
8
|
from ..rounding import round_up_pixels
|
|
9
|
-
from ..window import Area,
|
|
9
|
+
from ..window import Area, MapProjection, Window
|
|
10
10
|
from .base import YirgacheffeLayer
|
|
11
11
|
from .._backends import backend
|
|
12
12
|
|
|
13
13
|
class H3CellLayer(YirgacheffeLayer):
|
|
14
14
|
|
|
15
|
-
def __init__(self, cell_id: str,
|
|
15
|
+
def __init__(self, cell_id: str, projection: MapProjection):
|
|
16
16
|
if not h3.is_valid_cell(cell_id):
|
|
17
17
|
raise ValueError(f"{cell_id} is not a valid H3 cell identifier")
|
|
18
18
|
self.cell_id = cell_id
|
|
@@ -20,7 +20,7 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
20
20
|
|
|
21
21
|
self.cell_boundary = h3.cell_to_boundary(cell_id)
|
|
22
22
|
|
|
23
|
-
abs_xstep, abs_ystep = abs(
|
|
23
|
+
abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
|
|
24
24
|
area = Area(
|
|
25
25
|
left=(floor(min(x[1] for x in self.cell_boundary) / abs_xstep) * abs_xstep),
|
|
26
26
|
top=(ceil(max(x[0] for x in self.cell_boundary) / abs_ystep) * abs_ystep),
|
|
@@ -56,12 +56,12 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
56
56
|
bottom=area.bottom,
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
-
super().__init__(area,
|
|
59
|
+
super().__init__(area, projection)
|
|
60
60
|
self._window = Window(
|
|
61
61
|
xoff=0,
|
|
62
62
|
yoff=0,
|
|
63
|
-
xsize=round_up_pixels((area.right - area.left) /
|
|
64
|
-
ysize=round_up_pixels((area.bottom - area.top) /
|
|
63
|
+
xsize=round_up_pixels((area.right - area.left) / projection.xstep, abs_xstep),
|
|
64
|
+
ysize=round_up_pixels((area.bottom - area.top) / projection.ystep, abs_ystep),
|
|
65
65
|
)
|
|
66
66
|
self._raster_xsize = self.window.xsize
|
|
67
67
|
self._raster_ysize = self.window.ysize
|
|
@@ -90,7 +90,7 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
90
90
|
ysize: int,
|
|
91
91
|
window: Window,
|
|
92
92
|
) -> Any:
|
|
93
|
-
assert self.
|
|
93
|
+
assert self._projection is not None
|
|
94
94
|
|
|
95
95
|
if (xsize <= 0) or (ysize <= 0):
|
|
96
96
|
raise ValueError("Request dimensions must be positive and non-zero")
|
|
@@ -119,8 +119,8 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
119
119
|
|
|
120
120
|
subset = np.zeros((intersection.ysize, intersection.xsize))
|
|
121
121
|
|
|
122
|
-
start_x = self.
|
|
123
|
-
start_y = self.
|
|
122
|
+
start_x = self.area.left + ((intersection.xoff - self.window.xoff) * self._projection.xstep)
|
|
123
|
+
start_y = self.area.top + ((intersection.yoff - self.window.yoff) * self._projection.ystep)
|
|
124
124
|
|
|
125
125
|
for ypixel in range(intersection.ysize):
|
|
126
126
|
# The latlng_to_cell is quite expensive, so ideally we want to avoid
|
|
@@ -144,7 +144,7 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
144
144
|
# only a tiny number of cells that have convex edges, so in future it'd be
|
|
145
145
|
# interesting to see if we can infer when that is.
|
|
146
146
|
|
|
147
|
-
lat = start_y + (ypixel * self.
|
|
147
|
+
lat = start_y + (ypixel * self._projection.ystep)
|
|
148
148
|
|
|
149
149
|
if self._raster_safe_bounds[0] < lat < self._raster_safe_bounds[1]:
|
|
150
150
|
# In "safe" zone, try to be clever
|
|
@@ -152,14 +152,14 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
152
152
|
right_most = -1
|
|
153
153
|
|
|
154
154
|
for xpixel in range(intersection.xsize):
|
|
155
|
-
lng = start_x + (xpixel * self.
|
|
155
|
+
lng = start_x + (xpixel * self._projection.xstep)
|
|
156
156
|
this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
|
|
157
157
|
if this_cell == self.cell_id:
|
|
158
158
|
left_most = xpixel
|
|
159
159
|
break
|
|
160
160
|
|
|
161
161
|
for xpixel in range(intersection.xsize - 1, left_most - 1, -1):
|
|
162
|
-
lng = start_x + (xpixel * self.
|
|
162
|
+
lng = start_x + (xpixel * self._projection.xstep)
|
|
163
163
|
this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
|
|
164
164
|
if this_cell == self.cell_id:
|
|
165
165
|
right_most = xpixel
|
|
@@ -170,7 +170,7 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
170
170
|
else:
|
|
171
171
|
# Not in safe zone, be diligent.
|
|
172
172
|
for xpixel in range(intersection.xsize):
|
|
173
|
-
lng = start_x + (xpixel * self.
|
|
173
|
+
lng = start_x + (xpixel * self._projection.xstep)
|
|
174
174
|
this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
|
|
175
175
|
if this_cell == self.cell_id:
|
|
176
176
|
subset[ypixel][xpixel] = 1.0
|
|
@@ -196,19 +196,19 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
196
196
|
left = min(x[1] for x in self.cell_boundary if x[1] > 0.0)
|
|
197
197
|
right = max(x[1] for x in self.cell_boundary if x[1] < 0.0) + 360.0
|
|
198
198
|
max_width_projection = right - left
|
|
199
|
-
max_width = ceil(max_width_projection / self.
|
|
199
|
+
max_width = ceil(max_width_projection / self._projection.xstep)
|
|
200
200
|
|
|
201
201
|
for ypixel in range(yoffset, yoffset + ysize):
|
|
202
|
-
lat = self.
|
|
202
|
+
lat = self.area.top + (ypixel * self._projection.ystep)
|
|
203
203
|
|
|
204
204
|
for xpixel in range(xoffset, min(xoffset + xsize, max_width)):
|
|
205
|
-
lng = self.
|
|
205
|
+
lng = self.area.left + (xpixel * self._projection.xstep)
|
|
206
206
|
this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
|
|
207
207
|
if this_cell == self.cell_id:
|
|
208
208
|
res[ypixel - yoffset][xpixel - xoffset] = 1.0
|
|
209
209
|
|
|
210
210
|
for xpixel in range(xoffset + xsize - 1, xoffset + xsize - max_width, -1):
|
|
211
|
-
lng = self.
|
|
211
|
+
lng = self.area.left + (xpixel * self._projection.xstep)
|
|
212
212
|
this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
|
|
213
213
|
if this_cell == self.cell_id:
|
|
214
214
|
res[ypixel - yoffset][xpixel - xoffset] = 1.0
|