yirgacheffe 1.6.1__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.
- yirgacheffe/__init__.py +9 -2
- yirgacheffe/_core.py +10 -12
- yirgacheffe/layers/area.py +4 -4
- yirgacheffe/layers/base.py +99 -62
- yirgacheffe/layers/constant.py +3 -3
- yirgacheffe/layers/group.py +5 -7
- yirgacheffe/layers/h3layer.py +17 -17
- yirgacheffe/layers/rasters.py +14 -15
- yirgacheffe/layers/rescaled.py +12 -9
- yirgacheffe/layers/vectors.py +126 -45
- yirgacheffe/operators.py +152 -39
- yirgacheffe/window.py +41 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.0.dist-info}/METADATA +11 -9
- yirgacheffe-1.7.0.dist-info/RECORD +25 -0
- yirgacheffe-1.6.1.dist-info/RECORD +0 -25
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.0.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.0.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.6.1.dist-info → yirgacheffe-1.7.0.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
|
@@ -5,7 +5,7 @@ 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(
|
|
@@ -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,12 @@
|
|
|
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
|
|
7
10
|
|
|
8
11
|
class YirgacheffeLayer(LayerMathMixin):
|
|
9
12
|
"""The common base class for the different layer types. Most still inherit from RasterLayer as deep down
|
|
@@ -12,13 +15,15 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
12
15
|
|
|
13
16
|
def __init__(self,
|
|
14
17
|
area: Area,
|
|
15
|
-
|
|
16
|
-
projection: str,
|
|
18
|
+
projection: Optional[MapProjection],
|
|
17
19
|
name: Optional[str] = None
|
|
18
20
|
):
|
|
19
|
-
|
|
21
|
+
# This is just to catch code that uses the old private API
|
|
22
|
+
if projection is not None and not isinstance(projection, MapProjection):
|
|
23
|
+
raise TypeError("projection value of wrong type")
|
|
24
|
+
|
|
20
25
|
self._underlying_area = area
|
|
21
|
-
self._active_area =
|
|
26
|
+
self._active_area: Optional[Area] = None
|
|
22
27
|
self._projection = projection
|
|
23
28
|
self._window: Optional[Window] = None
|
|
24
29
|
self.name = name
|
|
@@ -49,16 +54,46 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
49
54
|
raise NotImplementedError("Must be overridden by subclass")
|
|
50
55
|
|
|
51
56
|
@property
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
@deprecation.deprecated(
|
|
58
|
+
deprecated_in="1.7",
|
|
59
|
+
removed_in="2.0",
|
|
60
|
+
current_version=__version__,
|
|
61
|
+
details="Use `map_projection` instead."
|
|
62
|
+
)
|
|
63
|
+
def projection(self) -> Optional[str]:
|
|
64
|
+
if self._projection:
|
|
65
|
+
return self._projection.name
|
|
66
|
+
else:
|
|
67
|
+
return None
|
|
54
68
|
|
|
55
69
|
@property
|
|
70
|
+
@deprecation.deprecated(
|
|
71
|
+
deprecated_in="1.7",
|
|
72
|
+
removed_in="2.0",
|
|
73
|
+
current_version=__version__,
|
|
74
|
+
details="Use `map_projection` instead."
|
|
75
|
+
)
|
|
56
76
|
def pixel_scale(self) -> Optional[PixelScale]:
|
|
57
|
-
|
|
77
|
+
if self._projection:
|
|
78
|
+
return PixelScale(self._projection.xstep, self._projection.ystep)
|
|
79
|
+
else:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def map_projection(self) -> Optional[MapProjection]:
|
|
84
|
+
return self._projection
|
|
58
85
|
|
|
59
86
|
@property
|
|
60
87
|
def area(self) -> Area:
|
|
61
|
-
|
|
88
|
+
if self._active_area is not None:
|
|
89
|
+
return self._active_area
|
|
90
|
+
else:
|
|
91
|
+
return self._underlying_area
|
|
92
|
+
|
|
93
|
+
def _get_operation_area(self, projection: Optional[MapProjection]) -> Area:
|
|
94
|
+
if self._projection is not None and projection is not None and self._projection != projection:
|
|
95
|
+
raise ValueError("Calculation projection does not match layer projection")
|
|
96
|
+
return self.area
|
|
62
97
|
|
|
63
98
|
@property
|
|
64
99
|
def window(self) -> Window:
|
|
@@ -77,8 +112,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
77
112
|
|
|
78
113
|
# This only makes sense (currently) if all layers
|
|
79
114
|
# have the same pixel pitch (modulo desired accuracy)
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
projections = [x.map_projection for x in layers if x.map_projection is not None]
|
|
116
|
+
if not projections:
|
|
117
|
+
raise ValueError("No layers have a projection")
|
|
118
|
+
if not all(projections[0] == x for x in projections[1:]):
|
|
119
|
+
raise ValueError("Not all layers are at the same projectin or pixel scale")
|
|
82
120
|
|
|
83
121
|
intersection = Area(
|
|
84
122
|
left=max(x._underlying_area.left for x in layers),
|
|
@@ -97,8 +135,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
97
135
|
|
|
98
136
|
# This only makes sense (currently) if all layers
|
|
99
137
|
# have the same pixel pitch (modulo desired accuracy)
|
|
100
|
-
|
|
101
|
-
|
|
138
|
+
projections = [x.map_projection for x in layers if x.map_projection is not None]
|
|
139
|
+
if not projections:
|
|
140
|
+
raise ValueError("No layers have a projection")
|
|
141
|
+
if not all(projections[0] == x for x in projections[1:]):
|
|
142
|
+
raise ValueError("Not all layers are at the same projectin or pixel scale")
|
|
102
143
|
|
|
103
144
|
return Area(
|
|
104
145
|
left=min(x._underlying_area.left for x in layers),
|
|
@@ -109,11 +150,11 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
109
150
|
|
|
110
151
|
@property
|
|
111
152
|
def geo_transform(self) -> Tuple[float, float, float, float, float, float]:
|
|
112
|
-
if self.
|
|
113
|
-
raise
|
|
153
|
+
if self._projection is None:
|
|
154
|
+
raise AttributeError("No geo transform for layers without explicit pixel scale")
|
|
114
155
|
return (
|
|
115
|
-
self.
|
|
116
|
-
self.
|
|
156
|
+
self.area.left, self._projection.xstep, 0.0,
|
|
157
|
+
self.area.top, 0.0, self._projection.ystep
|
|
117
158
|
)
|
|
118
159
|
|
|
119
160
|
def check_pixel_scale(self, scale: PixelScale) -> bool:
|
|
@@ -124,21 +165,21 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
124
165
|
almost_equal(our_scale.ystep, scale.ystep)
|
|
125
166
|
|
|
126
167
|
def set_window_for_intersection(self, new_area: Area) -> None:
|
|
127
|
-
if self.
|
|
168
|
+
if self._projection is None:
|
|
128
169
|
raise ValueError("Can not set Window without explicit pixel scale")
|
|
129
170
|
|
|
130
171
|
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.
|
|
172
|
+
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
173
|
+
self._projection.xstep),
|
|
174
|
+
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self._projection.ystep * -1.0),
|
|
175
|
+
self._projection.ystep * -1.0),
|
|
135
176
|
xsize=round_up_pixels(
|
|
136
|
-
(new_area.right - new_area.left) / self.
|
|
137
|
-
self.
|
|
177
|
+
(new_area.right - new_area.left) / self._projection.xstep,
|
|
178
|
+
self._projection.xstep
|
|
138
179
|
),
|
|
139
180
|
ysize=round_up_pixels(
|
|
140
|
-
(new_area.top - new_area.bottom) / (self.
|
|
141
|
-
(self.
|
|
181
|
+
(new_area.top - new_area.bottom) / (self._projection.ystep * -1.0),
|
|
182
|
+
(self._projection.ystep * -1.0)
|
|
142
183
|
),
|
|
143
184
|
)
|
|
144
185
|
if (new_window.xoff < 0) or (new_window.yoff < 0):
|
|
@@ -156,21 +197,21 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
156
197
|
self._active_area = new_area
|
|
157
198
|
|
|
158
199
|
def set_window_for_union(self, new_area: Area) -> None:
|
|
159
|
-
if self.
|
|
200
|
+
if self._projection is None:
|
|
160
201
|
raise ValueError("Can not set Window without explicit pixel scale")
|
|
161
202
|
|
|
162
203
|
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.
|
|
204
|
+
xoff=round_down_pixels((new_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
205
|
+
self._projection.xstep),
|
|
206
|
+
yoff=round_down_pixels((self._underlying_area.top - new_area.top) / (self._projection.ystep * -1.0),
|
|
207
|
+
self._projection.ystep * -1.0),
|
|
167
208
|
xsize=round_up_pixels(
|
|
168
|
-
(new_area.right - new_area.left) / self.
|
|
169
|
-
self.
|
|
209
|
+
(new_area.right - new_area.left) / self._projection.xstep,
|
|
210
|
+
self._projection.xstep
|
|
170
211
|
),
|
|
171
212
|
ysize=round_up_pixels(
|
|
172
|
-
(new_area.top - new_area.bottom) / (self.
|
|
173
|
-
(self.
|
|
213
|
+
(new_area.top - new_area.bottom) / (self._projection.ystep * -1.0),
|
|
214
|
+
(self._projection.ystep * -1.0)
|
|
174
215
|
),
|
|
175
216
|
)
|
|
176
217
|
if (new_window.xoff > 0) or (new_window.yoff > 0):
|
|
@@ -188,14 +229,14 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
188
229
|
self._active_area = new_area
|
|
189
230
|
|
|
190
231
|
def reset_window(self) -> None:
|
|
191
|
-
self._active_area =
|
|
192
|
-
if self.
|
|
193
|
-
abs_xstep, abs_ystep = abs(self.
|
|
232
|
+
self._active_area = None
|
|
233
|
+
if self._projection:
|
|
234
|
+
abs_xstep, abs_ystep = abs(self._projection.xstep), abs(self._projection.ystep)
|
|
194
235
|
self._window = Window(
|
|
195
236
|
xoff=0,
|
|
196
237
|
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.
|
|
238
|
+
xsize=round_up_pixels((self.area.right - self.area.left) / self._projection.xstep, abs_xstep),
|
|
239
|
+
ysize=round_up_pixels((self.area.bottom - self.area.top) / self._projection.ystep, abs_ystep),
|
|
199
240
|
)
|
|
200
241
|
|
|
201
242
|
def offset_window_by_pixels(self, offset: int) -> None:
|
|
@@ -244,25 +285,27 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
244
285
|
def _read_array_for_area(
|
|
245
286
|
self,
|
|
246
287
|
target_area: Area,
|
|
288
|
+
target_projection: MapProjection,
|
|
247
289
|
x: int,
|
|
248
290
|
y: int,
|
|
249
291
|
width: int,
|
|
250
292
|
height: int,
|
|
251
293
|
) -> Any:
|
|
252
|
-
assert self.
|
|
294
|
+
assert self._projection is not None
|
|
295
|
+
assert self._projection == target_projection
|
|
253
296
|
|
|
254
297
|
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.
|
|
298
|
+
xoff=round_down_pixels((target_area.left - self._underlying_area.left) / self._projection.xstep,
|
|
299
|
+
self._projection.xstep),
|
|
300
|
+
yoff=round_down_pixels((self._underlying_area.top - target_area.top) / (self._projection.ystep * -1.0),
|
|
301
|
+
self._projection.ystep * -1.0),
|
|
259
302
|
xsize=round_up_pixels(
|
|
260
|
-
(target_area.right - target_area.left) / self.
|
|
261
|
-
self.
|
|
303
|
+
(target_area.right - target_area.left) / self._projection.xstep,
|
|
304
|
+
self._projection.xstep
|
|
262
305
|
),
|
|
263
306
|
ysize=round_up_pixels(
|
|
264
|
-
(target_area.top - target_area.bottom) / (self.
|
|
265
|
-
(self.
|
|
307
|
+
(target_area.top - target_area.bottom) / (self._projection.ystep * -1.0),
|
|
308
|
+
(self._projection.ystep * -1.0)
|
|
266
309
|
),
|
|
267
310
|
)
|
|
268
311
|
return self._read_array_with_window(x, y, width, height, target_window)
|
|
@@ -290,25 +333,19 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
290
333
|
|
|
291
334
|
def latlng_for_pixel(self, x_coord: int, y_coord: int) -> Tuple[float,float]:
|
|
292
335
|
"""Get geo coords for pixel. This is relative to the set view window."""
|
|
293
|
-
if "WGS 84" not in self.
|
|
336
|
+
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
294
337
|
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
338
|
return (
|
|
299
|
-
(y_coord *
|
|
300
|
-
(x_coord *
|
|
339
|
+
(y_coord * self._projection.ystep) + self.area.top,
|
|
340
|
+
(x_coord * self._projection.xstep) + self.area.left
|
|
301
341
|
)
|
|
302
342
|
|
|
303
343
|
def pixel_for_latlng(self, lat: float, lng: float) -> Tuple[int,int]:
|
|
304
344
|
"""Get pixel for geo coords. This is relative to the set view window.
|
|
305
345
|
Result is rounded down to nearest pixel."""
|
|
306
|
-
if "WGS 84" not in self.
|
|
346
|
+
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
307
347
|
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
348
|
return (
|
|
312
|
-
round_down_pixels((lng - self.area.left) /
|
|
313
|
-
round_down_pixels((lat - self.area.top) /
|
|
349
|
+
round_down_pixels((lng - self.area.left) / self._projection.xstep, abs(self._projection.xstep)),
|
|
350
|
+
round_down_pixels((lat - self.area.top) / self._projection.ystep, abs(self._projection.ystep)),
|
|
314
351
|
)
|
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
|
@@ -7,7 +7,7 @@ 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
|
|
@@ -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
|
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
|
yirgacheffe/layers/rasters.py
CHANGED
|
@@ -7,7 +7,7 @@ import numpy as np
|
|
|
7
7
|
from osgeo import gdal
|
|
8
8
|
|
|
9
9
|
from ..constants import WGS_84_PROJECTION
|
|
10
|
-
from ..window import Area, PixelScale, Window
|
|
10
|
+
from ..window import Area, MapProjection, PixelScale, Window
|
|
11
11
|
from ..rounding import round_up_pixels
|
|
12
12
|
from .base import YirgacheffeLayer
|
|
13
13
|
from ..operators import DataType
|
|
@@ -107,14 +107,14 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
107
107
|
if isinstance(datatype, int):
|
|
108
108
|
datatype = DataType.of_gdal(datatype)
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
if
|
|
110
|
+
projection = layer.map_projection
|
|
111
|
+
if projection is None:
|
|
112
112
|
raise ValueError("Can not work out area without explicit pixel scale")
|
|
113
|
-
abs_xstep, abs_ystep = abs(
|
|
113
|
+
abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
|
|
114
114
|
width = round_up_pixels((area.right - area.left) / abs_xstep, abs_xstep)
|
|
115
115
|
height = round_up_pixels((area.top - area.bottom) / abs_ystep, abs_ystep)
|
|
116
116
|
geo_transform = (
|
|
117
|
-
area.left,
|
|
117
|
+
area.left, projection.xstep, 0.0, area.top, 0.0, projection.ystep
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
if datatype is None:
|
|
@@ -150,7 +150,7 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
150
150
|
options,
|
|
151
151
|
)
|
|
152
152
|
dataset.SetGeoTransform(geo_transform)
|
|
153
|
-
dataset.SetProjection(
|
|
153
|
+
dataset.SetProjection(projection.name)
|
|
154
154
|
if nodata is not None:
|
|
155
155
|
dataset.GetRasterBand(1).SetNoDataValue(nodata)
|
|
156
156
|
|
|
@@ -168,11 +168,11 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
168
168
|
source_dataset = source._dataset
|
|
169
169
|
assert source_dataset is not None
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
assert
|
|
171
|
+
old_projection = source.map_projection
|
|
172
|
+
assert old_projection is not None
|
|
173
173
|
|
|
174
|
-
x_scale =
|
|
175
|
-
y_scale =
|
|
174
|
+
x_scale = old_projection.xstep / new_pixel_scale.xstep
|
|
175
|
+
y_scale = old_projection.ystep / new_pixel_scale.ystep
|
|
176
176
|
new_width = round_up_pixels(source_dataset.RasterXSize * x_scale,
|
|
177
177
|
abs(new_pixel_scale.xstep))
|
|
178
178
|
new_height = round_up_pixels(source_dataset.RasterYSize * y_scale,
|
|
@@ -242,18 +242,17 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
242
242
|
raise ValueError("None is not a valid dataset")
|
|
243
243
|
|
|
244
244
|
transform = dataset.GetGeoTransform()
|
|
245
|
-
|
|
245
|
+
projection = MapProjection(dataset.GetProjection(), transform[1], transform[5])
|
|
246
246
|
area = Area(
|
|
247
247
|
left=transform[0],
|
|
248
248
|
top=transform[3],
|
|
249
|
-
right=transform[0] + (dataset.RasterXSize *
|
|
250
|
-
bottom=transform[3] + (dataset.RasterYSize *
|
|
249
|
+
right=transform[0] + (dataset.RasterXSize * projection.xstep),
|
|
250
|
+
bottom=transform[3] + (dataset.RasterYSize * projection.ystep),
|
|
251
251
|
)
|
|
252
252
|
|
|
253
253
|
super().__init__(
|
|
254
254
|
area,
|
|
255
|
-
|
|
256
|
-
dataset.GetProjection(),
|
|
255
|
+
projection,
|
|
257
256
|
name=name
|
|
258
257
|
)
|
|
259
258
|
|