yirgacheffe 1.9.5__py3-none-any.whl → 1.10.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/_backends/enumeration.py +7 -0
- yirgacheffe/_backends/mlx.py +7 -0
- yirgacheffe/_backends/numpy.py +11 -0
- yirgacheffe/{_operators.py → _operators/__init__.py} +257 -78
- yirgacheffe/_operators/cse.py +66 -0
- yirgacheffe/layers/base.py +6 -1
- yirgacheffe/layers/constant.py +4 -0
- yirgacheffe/layers/group.py +4 -0
- yirgacheffe/layers/h3layer.py +9 -0
- yirgacheffe/layers/rasters.py +12 -0
- yirgacheffe/layers/rescaled.py +11 -0
- yirgacheffe/layers/vectors.py +13 -2
- yirgacheffe/window.py +6 -3
- {yirgacheffe-1.9.5.dist-info → yirgacheffe-1.10.0.dist-info}/METADATA +2 -1
- yirgacheffe-1.10.0.dist-info/RECORD +28 -0
- yirgacheffe-1.9.5.dist-info/RECORD +0 -27
- {yirgacheffe-1.9.5.dist-info → yirgacheffe-1.10.0.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.9.5.dist-info → yirgacheffe-1.10.0.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.9.5.dist-info → yirgacheffe-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.9.5.dist-info → yirgacheffe-1.10.0.dist-info}/top_level.txt +0 -0
|
@@ -42,6 +42,13 @@ class operators(Enum):
|
|
|
42
42
|
ROUND = 34
|
|
43
43
|
CEIL = 35
|
|
44
44
|
ISNAN = 36
|
|
45
|
+
RADD = 37
|
|
46
|
+
RSUB = 38
|
|
47
|
+
RMUL = 39
|
|
48
|
+
RTRUEDIV = 40
|
|
49
|
+
RFLOORDIV = 41
|
|
50
|
+
RREMAINDER = 42
|
|
51
|
+
RPOW = 43
|
|
45
52
|
|
|
46
53
|
class dtype(Enum):
|
|
47
54
|
"""Represents the type of data returned by a layer.
|
yirgacheffe/_backends/mlx.py
CHANGED
|
@@ -222,4 +222,11 @@ operator_map: dict[op, Callable] = {
|
|
|
222
222
|
op.ROUND: mx.round,
|
|
223
223
|
op.CEIL: mx.ceil,
|
|
224
224
|
op.ISNAN: mx.isnan,
|
|
225
|
+
op.RADD: mx.array.__radd__,
|
|
226
|
+
op.RSUB: mx.array.__rsub__,
|
|
227
|
+
op.RMUL: mx.array.__rmul__,
|
|
228
|
+
op.RTRUEDIV: mx.array.__rtruediv__,
|
|
229
|
+
op.RFLOORDIV: mx.array.__rfloordiv__,
|
|
230
|
+
op.RREMAINDER: mx.array.__rmod__,
|
|
231
|
+
op.RPOW: mx.array.__rpow__,
|
|
225
232
|
}
|
yirgacheffe/_backends/numpy.py
CHANGED
|
@@ -160,4 +160,15 @@ operator_map: dict[op, Callable] = {
|
|
|
160
160
|
op.ROUND: np.round,
|
|
161
161
|
op.CEIL: np.ceil,
|
|
162
162
|
op.ISNAN: np.isnan,
|
|
163
|
+
# Pylint doesn't recognise the reverse operators, despite them
|
|
164
|
+
# clearly existing on np.ndarray, so we have to disable the checks
|
|
165
|
+
# pylint: disable=no-member
|
|
166
|
+
op.RADD: np.ndarray.__radd__,
|
|
167
|
+
op.RSUB: np.ndarray.__rsub__,
|
|
168
|
+
op.RMUL: np.ndarray.__rmul__,
|
|
169
|
+
op.RTRUEDIV: np.ndarray.__rtruediv__,
|
|
170
|
+
op.RFLOORDIV: np.ndarray.__rfloordiv__,
|
|
171
|
+
op.RREMAINDER: np.ndarray.__rmod__,
|
|
172
|
+
op.RPOW: np.ndarray.__rpow__,
|
|
173
|
+
# pylint: enable=no-member
|
|
163
174
|
}
|
|
@@ -13,9 +13,11 @@ import types
|
|
|
13
13
|
from collections.abc import Callable
|
|
14
14
|
from contextlib import ExitStack
|
|
15
15
|
from enum import Enum
|
|
16
|
-
from multiprocessing import
|
|
16
|
+
from multiprocessing import Process, Semaphore
|
|
17
|
+
from multiprocessing.synchronize import Semaphore as SemaphoreType
|
|
17
18
|
from multiprocessing.managers import SharedMemoryManager
|
|
18
19
|
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
19
21
|
|
|
20
22
|
import deprecation
|
|
21
23
|
import numpy as np
|
|
@@ -24,14 +26,15 @@ from osgeo import gdal
|
|
|
24
26
|
from dill import dumps, loads # type: ignore
|
|
25
27
|
from pyproj import Transformer
|
|
26
28
|
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from
|
|
29
|
+
from .. import constants, __version__
|
|
30
|
+
from ..rounding import round_up_pixels, round_down_pixels
|
|
31
|
+
from ..window import Area, PixelScale, MapProjection, Window
|
|
32
|
+
from .._backends import backend
|
|
33
|
+
from .._backends.enumeration import operators as op
|
|
34
|
+
from .._backends.enumeration import dtype as DataType
|
|
35
|
+
from .._backends.numpy import dtype_to_backend as dtype_to_numpy
|
|
36
|
+
from .._backends.numpy import backend_to_dtype as numpy_to_dtype
|
|
37
|
+
from .cse import CSECacheTable
|
|
35
38
|
|
|
36
39
|
logger = logging.getLogger(__name__)
|
|
37
40
|
logger.setLevel(logging.WARNING)
|
|
@@ -50,9 +53,13 @@ class LayerConstant:
|
|
|
50
53
|
def __str__(self) -> str:
|
|
51
54
|
return str(self.val)
|
|
52
55
|
|
|
53
|
-
def _eval(self, _area, _projection, _index, _step, _target_window):
|
|
56
|
+
def _eval(self, _cse_cache, _area, _projection, _index, _step, _target_window):
|
|
54
57
|
return self.val
|
|
55
58
|
|
|
59
|
+
@property
|
|
60
|
+
def _cse_hash(self) -> int | None:
|
|
61
|
+
return hash(self.val)
|
|
62
|
+
|
|
56
63
|
@property
|
|
57
64
|
def datatype(self) -> DataType:
|
|
58
65
|
numpy_type = np.result_type(self.val)
|
|
@@ -70,24 +77,45 @@ class LayerMathMixin:
|
|
|
70
77
|
def __add__(self, other):
|
|
71
78
|
return LayerOperation(self, op.ADD, other, window_op=WindowOperation.UNION)
|
|
72
79
|
|
|
80
|
+
def __radd__(self, other):
|
|
81
|
+
return LayerOperation(self, op.RADD, other, window_op=WindowOperation.UNION)
|
|
82
|
+
|
|
73
83
|
def __sub__(self, other):
|
|
74
84
|
return LayerOperation(self, op.SUB, other, window_op=WindowOperation.UNION)
|
|
75
85
|
|
|
86
|
+
def __rsub__(self, other):
|
|
87
|
+
return LayerOperation(self, op.RSUB, other, window_op=WindowOperation.UNION)
|
|
88
|
+
|
|
76
89
|
def __mul__(self, other):
|
|
77
90
|
return LayerOperation(self, op.MUL, other, window_op=WindowOperation.INTERSECTION)
|
|
78
91
|
|
|
92
|
+
def __rmul__(self, other):
|
|
93
|
+
return LayerOperation(self, op.RMUL, other, window_op=WindowOperation.INTERSECTION)
|
|
94
|
+
|
|
79
95
|
def __truediv__(self, other):
|
|
80
96
|
return LayerOperation(self, op.TRUEDIV, other, window_op=WindowOperation.INTERSECTION)
|
|
81
97
|
|
|
98
|
+
def __rtruediv__(self, other):
|
|
99
|
+
return LayerOperation(self, op.RTRUEDIV, other, window_op=WindowOperation.INTERSECTION)
|
|
100
|
+
|
|
82
101
|
def __floordiv__(self, other):
|
|
83
102
|
return LayerOperation(self, op.FLOORDIV, other, window_op=WindowOperation.INTERSECTION)
|
|
84
103
|
|
|
104
|
+
def __rfloordiv__(self, other):
|
|
105
|
+
return LayerOperation(self, op.RFLOORDIV, other, window_op=WindowOperation.INTERSECTION)
|
|
106
|
+
|
|
85
107
|
def __mod__(self, other):
|
|
86
108
|
return LayerOperation(self, op.REMAINDER, other, window_op=WindowOperation.INTERSECTION)
|
|
87
109
|
|
|
110
|
+
def __rmod__(self, other):
|
|
111
|
+
return LayerOperation(self, op.RREMAINDER, other, window_op=WindowOperation.INTERSECTION)
|
|
112
|
+
|
|
88
113
|
def __pow__(self, other):
|
|
89
114
|
return LayerOperation(self, op.POW, other, window_op=WindowOperation.UNION)
|
|
90
115
|
|
|
116
|
+
def __rpow__(self, other):
|
|
117
|
+
return LayerOperation(self, op.RPOW, other, window_op=WindowOperation.UNION)
|
|
118
|
+
|
|
91
119
|
def __eq__(self, other):
|
|
92
120
|
return LayerOperation(self, op.EQ, other, window_op=WindowOperation.INTERSECTION)
|
|
93
121
|
|
|
@@ -114,17 +142,22 @@ class LayerMathMixin:
|
|
|
114
142
|
|
|
115
143
|
def _eval(
|
|
116
144
|
self,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
145
|
+
cse_cache: CSECacheTable,
|
|
146
|
+
area: Area,
|
|
147
|
+
projection: MapProjection,
|
|
148
|
+
index: int,
|
|
149
|
+
step: int,
|
|
150
|
+
target_window: Window,
|
|
122
151
|
):
|
|
152
|
+
cache_data = cse_cache.get_data(self._cse_hash, target_window)
|
|
153
|
+
if cache_data is not None:
|
|
154
|
+
return cache_data
|
|
155
|
+
|
|
123
156
|
try:
|
|
124
157
|
window = self.window if target_window is None else target_window
|
|
125
|
-
|
|
158
|
+
result = self._read_array_for_area(area, projection, 0, index, window.xsize, step)
|
|
126
159
|
except AttributeError:
|
|
127
|
-
|
|
160
|
+
result = self._read_array_for_area(
|
|
128
161
|
area,
|
|
129
162
|
projection,
|
|
130
163
|
0,
|
|
@@ -133,6 +166,9 @@ class LayerMathMixin:
|
|
|
133
166
|
step
|
|
134
167
|
)
|
|
135
168
|
|
|
169
|
+
cse_cache.set_data(self._cse_hash, target_window, result)
|
|
170
|
+
return result
|
|
171
|
+
|
|
136
172
|
def nan_to_num(self, nan=0, posinf=None, neginf=None):
|
|
137
173
|
return LayerOperation(
|
|
138
174
|
self,
|
|
@@ -149,7 +185,7 @@ class LayerMathMixin:
|
|
|
149
185
|
self,
|
|
150
186
|
op.ISIN,
|
|
151
187
|
window_op=WindowOperation.NONE,
|
|
152
|
-
test_elements=test_elements,
|
|
188
|
+
test_elements=tuple(test_elements),
|
|
153
189
|
)
|
|
154
190
|
|
|
155
191
|
def isnan(self):
|
|
@@ -252,10 +288,10 @@ class LayerMathMixin:
|
|
|
252
288
|
weights=weights.astype(np.float32),
|
|
253
289
|
)
|
|
254
290
|
|
|
255
|
-
def numpy_apply(self, func, other=None):
|
|
291
|
+
def numpy_apply(self, func: Callable, other=None):
|
|
256
292
|
return LayerOperation(self, func, other)
|
|
257
293
|
|
|
258
|
-
def shader_apply(self, func, other=None):
|
|
294
|
+
def shader_apply(self, func: Callable, other=None):
|
|
259
295
|
return ShaderStyleOperation(self, func, other)
|
|
260
296
|
|
|
261
297
|
def save(self, destination_layer, and_sum=False, callback=None, band=1):
|
|
@@ -347,9 +383,16 @@ class LayerMathMixin:
|
|
|
347
383
|
def area(self) -> Area:
|
|
348
384
|
raise NotImplementedError("Must be overridden by subclass")
|
|
349
385
|
|
|
386
|
+
@property
|
|
387
|
+
def _cse_hash(self) -> int | None:
|
|
388
|
+
raise NotImplementedError("Must be overridden by subclass")
|
|
389
|
+
|
|
350
390
|
def read_array(self, _x, _y, _w, _h):
|
|
351
391
|
raise NotImplementedError("Must be overridden by subclass")
|
|
352
392
|
|
|
393
|
+
def _read_array_for_area(self, _target_area, _target_projection, _x, _y, _w, _h):
|
|
394
|
+
raise NotImplementedError("Must be overridden by subclass")
|
|
395
|
+
|
|
353
396
|
def show(self, ax=None, max_pixels: int | None =1000, **kwargs):
|
|
354
397
|
"""Display data using matplotlib.
|
|
355
398
|
|
|
@@ -424,12 +467,12 @@ class LayerOperation(LayerMathMixin):
|
|
|
424
467
|
|
|
425
468
|
def __init__(
|
|
426
469
|
self,
|
|
427
|
-
lhs,
|
|
428
|
-
operator=None,
|
|
429
|
-
rhs=None,
|
|
430
|
-
other=None,
|
|
431
|
-
window_op=WindowOperation.NONE,
|
|
432
|
-
buffer_padding=0,
|
|
470
|
+
lhs: Any,
|
|
471
|
+
operator: op | Callable | None = None,
|
|
472
|
+
rhs: Any = None,
|
|
473
|
+
other: Any = None,
|
|
474
|
+
window_op: WindowOperation = WindowOperation.NONE,
|
|
475
|
+
buffer_padding: int = 0,
|
|
433
476
|
**kwargs
|
|
434
477
|
):
|
|
435
478
|
self.ystep = constants.YSTEP
|
|
@@ -446,7 +489,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
446
489
|
|
|
447
490
|
if rhs is not None:
|
|
448
491
|
if backend.isscalar(rhs):
|
|
449
|
-
self.rhs = LayerConstant(rhs)
|
|
492
|
+
self.rhs: Any = LayerConstant(rhs)
|
|
450
493
|
elif isinstance(rhs, (backend.array_t)):
|
|
451
494
|
if rhs.shape == ():
|
|
452
495
|
self.rhs = LayerConstant(rhs.item())
|
|
@@ -461,7 +504,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
461
504
|
|
|
462
505
|
if other is not None:
|
|
463
506
|
if backend.isscalar(other):
|
|
464
|
-
self.other = LayerConstant(other)
|
|
507
|
+
self.other: Any = LayerConstant(other)
|
|
465
508
|
elif isinstance(other, (backend.array_t)):
|
|
466
509
|
if other.shape == ():
|
|
467
510
|
self.rhs = LayerConstant(other.item())
|
|
@@ -474,6 +517,13 @@ class LayerOperation(LayerMathMixin):
|
|
|
474
517
|
else:
|
|
475
518
|
self.other = None
|
|
476
519
|
|
|
520
|
+
# this is expensive, so don't do it all the time
|
|
521
|
+
self._cse_hash_cache = self._calc_cse_hash()
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def _cse_hash(self) -> int | None:
|
|
525
|
+
return self._cse_hash_cache
|
|
526
|
+
|
|
477
527
|
def __str__(self) -> str:
|
|
478
528
|
try:
|
|
479
529
|
return f"({self.lhs} {self.operator} {self.rhs})"
|
|
@@ -483,9 +533,6 @@ class LayerOperation(LayerMathMixin):
|
|
|
483
533
|
except AttributeError:
|
|
484
534
|
return str(self.lhs)
|
|
485
535
|
|
|
486
|
-
def __len__(self) -> int:
|
|
487
|
-
return len(self.lhs)
|
|
488
|
-
|
|
489
536
|
def __getstate__(self) -> object:
|
|
490
537
|
odict = self.__dict__.copy()
|
|
491
538
|
if isinstance(self.operator, types.LambdaType):
|
|
@@ -499,6 +546,35 @@ class LayerOperation(LayerMathMixin):
|
|
|
499
546
|
del state['operator_dill']
|
|
500
547
|
self.__dict__.update(state)
|
|
501
548
|
|
|
549
|
+
@property
|
|
550
|
+
def _children(self) -> list:
|
|
551
|
+
return [x for x in [self.lhs, self.rhs, self.other] if x is not None]
|
|
552
|
+
|
|
553
|
+
def _calc_cse_hash(self) -> int | None:
|
|
554
|
+
# If we can't hash any of the child nodes then we can't hash this node, as if we can't store
|
|
555
|
+
# their results in the cache, we can't know this result is stable
|
|
556
|
+
child_hashes = [x._cse_hash for x in self._children]
|
|
557
|
+
if any(x is None for x in child_hashes):
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
# This really should be recursive
|
|
561
|
+
def _make_hashable(value):
|
|
562
|
+
if isinstance(value, (list, tuple, set)):
|
|
563
|
+
return tuple(value)
|
|
564
|
+
if isinstance(value, np.ndarray):
|
|
565
|
+
return id(value)
|
|
566
|
+
else:
|
|
567
|
+
return value
|
|
568
|
+
frozen_kwargs = tuple(sorted((k, _make_hashable(v)) for (k, v) in self.kwargs.items()))
|
|
569
|
+
|
|
570
|
+
terms = [self.operator, self.window_op, frozen_kwargs, self.buffer_padding] + child_hashes
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
return hash(tuple(terms))
|
|
574
|
+
except TypeError:
|
|
575
|
+
# This is assumed to be because kwargs contains something unhashable
|
|
576
|
+
return None
|
|
577
|
+
|
|
502
578
|
@property
|
|
503
579
|
def area(self) -> Area:
|
|
504
580
|
return self._get_operation_area(self.map_projection)
|
|
@@ -546,7 +622,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
546
622
|
)
|
|
547
623
|
return union
|
|
548
624
|
case _:
|
|
549
|
-
|
|
625
|
+
raise RuntimeError("Should not be reached")
|
|
550
626
|
|
|
551
627
|
@property
|
|
552
628
|
@deprecation.deprecated(
|
|
@@ -632,15 +708,35 @@ class LayerOperation(LayerMathMixin):
|
|
|
632
708
|
pass
|
|
633
709
|
return projection
|
|
634
710
|
|
|
711
|
+
def pretty_print(self, prefix="", is_last=True):
|
|
712
|
+
kwargs_str = ", ".join(f"{k}={v}" for k, v in self.kwargs.items())
|
|
713
|
+
label = f"{self.operator}({kwargs_str})" if kwargs_str else self.operator
|
|
714
|
+
label = f"{label} - {self._cse_hash}"
|
|
715
|
+
|
|
716
|
+
connector = "└── " if is_last else "├── "
|
|
717
|
+
print(f"{prefix}{connector}{label}")
|
|
718
|
+
|
|
719
|
+
extension = " " if is_last else "│ "
|
|
720
|
+
new_prefix = prefix + extension
|
|
721
|
+
|
|
722
|
+
children = self._children
|
|
723
|
+
for i, child in enumerate(children):
|
|
724
|
+
child_is_last = i == len(children) - 1
|
|
725
|
+
if hasattr(child, 'pretty_print'):
|
|
726
|
+
child.pretty_print(new_prefix, child_is_last)
|
|
727
|
+
else:
|
|
728
|
+
child_connector = "└── " if child_is_last else "├── "
|
|
729
|
+
print(f"{new_prefix}{child_connector}{repr(child)}")
|
|
730
|
+
|
|
635
731
|
def _eval(
|
|
636
732
|
self,
|
|
733
|
+
cse_cache: CSECacheTable,
|
|
637
734
|
area: Area,
|
|
638
735
|
projection: MapProjection,
|
|
639
736
|
index: int,
|
|
640
737
|
step: int,
|
|
641
|
-
target_window: Window
|
|
738
|
+
target_window: Window,
|
|
642
739
|
):
|
|
643
|
-
|
|
644
740
|
if self.buffer_padding:
|
|
645
741
|
if target_window:
|
|
646
742
|
target_window = target_window.grow(self.buffer_padding)
|
|
@@ -648,30 +744,36 @@ class LayerOperation(LayerMathMixin):
|
|
|
648
744
|
# The index doesn't need updating because we updated area/window
|
|
649
745
|
step += (2 * self.buffer_padding)
|
|
650
746
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
return lhs_data
|
|
655
|
-
|
|
656
|
-
try:
|
|
657
|
-
operator: Callable = backend.operator_map[self.operator]
|
|
658
|
-
except KeyError:
|
|
659
|
-
# Handles things like `numpy_apply` where a custom operator is provided
|
|
660
|
-
operator = self.operator
|
|
747
|
+
cache_data = cse_cache.get_data(self._cse_hash, target_window)
|
|
748
|
+
if cache_data is not None:
|
|
749
|
+
return cache_data
|
|
661
750
|
|
|
662
|
-
|
|
663
|
-
assert self.rhs is not None
|
|
664
|
-
rhs_data = self.rhs._eval(area, projection, index, step, target_window)
|
|
665
|
-
other_data = self.other._eval(area, projection, index, step, target_window)
|
|
666
|
-
return operator(lhs_data, rhs_data, other_data, **self.kwargs)
|
|
751
|
+
lhs_data = self.lhs._eval(cse_cache, area, projection, index, step, target_window)
|
|
667
752
|
|
|
668
|
-
if self.
|
|
669
|
-
|
|
670
|
-
|
|
753
|
+
if self.operator is None:
|
|
754
|
+
result = lhs_data
|
|
755
|
+
else:
|
|
756
|
+
if isinstance(self.operator, op):
|
|
757
|
+
operator = backend.operator_map[self.operator]
|
|
758
|
+
else:
|
|
759
|
+
# Handles things like `numpy_apply` where a custom operator is provided
|
|
760
|
+
operator = self.operator
|
|
761
|
+
|
|
762
|
+
if self.other is not None:
|
|
763
|
+
assert self.rhs is not None
|
|
764
|
+
rhs_data = self.rhs._eval(cse_cache, area, projection, index, step, target_window)
|
|
765
|
+
other_data = self.other._eval(cse_cache, area, projection, index, step, target_window)
|
|
766
|
+
result = operator(lhs_data, rhs_data, other_data, **self.kwargs)
|
|
767
|
+
elif self.rhs is not None:
|
|
768
|
+
rhs_data = self.rhs._eval(cse_cache, area, projection, index, step, target_window)
|
|
769
|
+
result = operator(lhs_data, rhs_data, **self.kwargs)
|
|
770
|
+
else:
|
|
771
|
+
result = operator(lhs_data, **self.kwargs)
|
|
671
772
|
|
|
672
|
-
|
|
773
|
+
cse_cache.set_data(self._cse_hash, target_window, result)
|
|
774
|
+
return result
|
|
673
775
|
|
|
674
|
-
def sum(self):
|
|
776
|
+
def sum(self) -> float:
|
|
675
777
|
# The result accumulator is float64, and for precision reasons
|
|
676
778
|
# we force the sum to be done in float64 also. Otherwise we
|
|
677
779
|
# see variable results depending on chunk size, as different parts
|
|
@@ -679,40 +781,77 @@ class LayerOperation(LayerMathMixin):
|
|
|
679
781
|
res = 0.0
|
|
680
782
|
computation_window = self.window
|
|
681
783
|
projection = self.map_projection
|
|
784
|
+
if projection is None:
|
|
785
|
+
raise ValueError("No map projection")
|
|
786
|
+
|
|
787
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
788
|
+
|
|
682
789
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
790
|
+
cse_cache.reset_cache()
|
|
683
791
|
step=self.ystep
|
|
684
792
|
if yoffset+step > computation_window.ysize:
|
|
685
793
|
step = computation_window.ysize - yoffset
|
|
686
|
-
chunk = self._eval(
|
|
794
|
+
chunk = self._eval(
|
|
795
|
+
cse_cache,
|
|
796
|
+
self._get_operation_area(projection),
|
|
797
|
+
projection,
|
|
798
|
+
yoffset,
|
|
799
|
+
step,
|
|
800
|
+
computation_window
|
|
801
|
+
)
|
|
687
802
|
res += backend.sum_op(chunk)
|
|
688
803
|
return res
|
|
689
804
|
|
|
690
|
-
def min(self):
|
|
691
|
-
res =
|
|
805
|
+
def min(self) -> float:
|
|
806
|
+
res = float('inf')
|
|
692
807
|
computation_window = self.window
|
|
693
808
|
projection = self.map_projection
|
|
809
|
+
if projection is None:
|
|
810
|
+
raise ValueError("No map projection")
|
|
811
|
+
|
|
812
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
813
|
+
|
|
694
814
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
815
|
+
cse_cache.reset_cache()
|
|
695
816
|
step=self.ystep
|
|
696
817
|
if yoffset+step > computation_window.ysize:
|
|
697
818
|
step = computation_window.ysize - yoffset
|
|
698
|
-
chunk = self._eval(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
819
|
+
chunk = self._eval(
|
|
820
|
+
cse_cache,
|
|
821
|
+
self._get_operation_area(projection),
|
|
822
|
+
projection,
|
|
823
|
+
yoffset,
|
|
824
|
+
step,
|
|
825
|
+
computation_window
|
|
826
|
+
)
|
|
827
|
+
chunk_min = float(backend.min_op(chunk))
|
|
828
|
+
res = min(res, chunk_min)
|
|
702
829
|
return res
|
|
703
830
|
|
|
704
|
-
def max(self):
|
|
705
|
-
res =
|
|
831
|
+
def max(self) -> float:
|
|
832
|
+
res = float('-inf')
|
|
706
833
|
computation_window = self.window
|
|
707
834
|
projection = self.map_projection
|
|
835
|
+
if projection is None:
|
|
836
|
+
raise ValueError("No map projection")
|
|
837
|
+
|
|
838
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
839
|
+
|
|
708
840
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
841
|
+
cse_cache.reset_cache()
|
|
709
842
|
step=self.ystep
|
|
710
843
|
if yoffset+step > computation_window.ysize:
|
|
711
844
|
step = computation_window.ysize - yoffset
|
|
712
|
-
chunk = self._eval(
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
845
|
+
chunk = self._eval(
|
|
846
|
+
cse_cache,
|
|
847
|
+
self._get_operation_area(projection),
|
|
848
|
+
projection,
|
|
849
|
+
yoffset,
|
|
850
|
+
step,
|
|
851
|
+
computation_window
|
|
852
|
+
)
|
|
853
|
+
chunk_max = float(backend.max_op(chunk))
|
|
854
|
+
res = max(res, chunk_max)
|
|
716
855
|
return res
|
|
717
856
|
|
|
718
857
|
def save(self, destination_layer, and_sum=False, callback=None, band=1) -> float | None:
|
|
@@ -757,13 +896,18 @@ class LayerOperation(LayerMathMixin):
|
|
|
757
896
|
|
|
758
897
|
total = 0.0
|
|
759
898
|
|
|
899
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
900
|
+
|
|
760
901
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
902
|
+
|
|
903
|
+
cse_cache.reset_cache()
|
|
904
|
+
|
|
761
905
|
if callback:
|
|
762
906
|
callback(yoffset / computation_window.ysize)
|
|
763
907
|
step = self.ystep
|
|
764
908
|
if yoffset + step > computation_window.ysize:
|
|
765
909
|
step = computation_window.ysize - yoffset
|
|
766
|
-
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
910
|
+
chunk = self._eval(cse_cache, computation_area, projection, yoffset, step, computation_window)
|
|
767
911
|
if isinstance(chunk, (float, int)):
|
|
768
912
|
chunk = backend.full((step, destination_window.xsize), chunk)
|
|
769
913
|
band.WriteArray(
|
|
@@ -778,11 +922,29 @@ class LayerOperation(LayerMathMixin):
|
|
|
778
922
|
|
|
779
923
|
return total if and_sum else None
|
|
780
924
|
|
|
781
|
-
def _parallel_worker(
|
|
782
|
-
|
|
925
|
+
def _parallel_worker(
|
|
926
|
+
self,
|
|
927
|
+
index : int,
|
|
928
|
+
shared_mem,
|
|
929
|
+
sem : SemaphoreType,
|
|
930
|
+
np_dtype : type,
|
|
931
|
+
width : int,
|
|
932
|
+
input_queue : multiprocessing.Queue,
|
|
933
|
+
output_queue : multiprocessing.Queue,
|
|
934
|
+
computation_window : Window,
|
|
935
|
+
):
|
|
936
|
+
# The hashing of python objects isn't stable across processes in general, so we have to do
|
|
937
|
+
# the cache build once per worker
|
|
938
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
939
|
+
|
|
940
|
+
arr = np.ndarray((self.ystep, width), dtype=np_dtype, buffer=shared_mem.buf) # type: ignore[var-annotated]
|
|
783
941
|
projection = self.map_projection
|
|
942
|
+
# TODO: the `save` method does more sanity checking that parallel save!
|
|
943
|
+
assert projection is not None
|
|
784
944
|
try:
|
|
785
945
|
while True:
|
|
946
|
+
cse_cache.reset_cache()
|
|
947
|
+
|
|
786
948
|
# We acquire the lock so we know we have somewhere to put the
|
|
787
949
|
# result before we take work. This is because in practice
|
|
788
950
|
# it seems the writing to GeoTIFF is the bottleneck, and
|
|
@@ -798,7 +960,14 @@ class LayerOperation(LayerMathMixin):
|
|
|
798
960
|
break
|
|
799
961
|
yoffset, step = task
|
|
800
962
|
|
|
801
|
-
result = self._eval(
|
|
963
|
+
result = self._eval(
|
|
964
|
+
cse_cache,
|
|
965
|
+
self._get_operation_area(projection),
|
|
966
|
+
projection,
|
|
967
|
+
yoffset,
|
|
968
|
+
step,
|
|
969
|
+
computation_window,
|
|
970
|
+
)
|
|
802
971
|
backend.eval_op(result)
|
|
803
972
|
|
|
804
973
|
arr[:step] = backend.demote_array(result)
|
|
@@ -844,7 +1013,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
844
1013
|
elif and_sum:
|
|
845
1014
|
return self.sum()
|
|
846
1015
|
else:
|
|
847
|
-
|
|
1016
|
+
raise RuntimeError("Should not be reached") # pylint: disable=W0707
|
|
848
1017
|
|
|
849
1018
|
worker_count = parallelism or multiprocessing.cpu_count()
|
|
850
1019
|
work_blocks = len(range(0, computation_window.ysize, self.ystep))
|
|
@@ -857,7 +1026,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
857
1026
|
elif and_sum:
|
|
858
1027
|
return self.sum()
|
|
859
1028
|
else:
|
|
860
|
-
|
|
1029
|
+
raise RuntimeError("Should not be reached")
|
|
861
1030
|
|
|
862
1031
|
if destination_layer is not None:
|
|
863
1032
|
try:
|
|
@@ -942,7 +1111,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
942
1111
|
computation_window.xsize,
|
|
943
1112
|
source_queue,
|
|
944
1113
|
result_queue,
|
|
945
|
-
computation_window
|
|
1114
|
+
computation_window,
|
|
946
1115
|
)) for i in range(worker_count)]
|
|
947
1116
|
for worker in workers:
|
|
948
1117
|
worker.start()
|
|
@@ -1074,11 +1243,15 @@ class LayerOperation(LayerMathMixin):
|
|
|
1074
1243
|
)
|
|
1075
1244
|
|
|
1076
1245
|
chunks = []
|
|
1246
|
+
|
|
1247
|
+
cse_cache = CSECacheTable(self, computation_window)
|
|
1248
|
+
|
|
1077
1249
|
for yoffset in range(0, height, self.ystep):
|
|
1250
|
+
cse_cache.reset_cache()
|
|
1078
1251
|
step = self.ystep
|
|
1079
1252
|
if yoffset + step > height:
|
|
1080
1253
|
step = height - yoffset
|
|
1081
|
-
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
1254
|
+
chunk = self._eval(cse_cache, computation_area, projection, yoffset, step, computation_window)
|
|
1082
1255
|
if isinstance(chunk, (float, int)):
|
|
1083
1256
|
chunk = backend.full((step, computation_window.xsize), chunk)
|
|
1084
1257
|
chunks.append(chunk)
|
|
@@ -1086,14 +1259,17 @@ class LayerOperation(LayerMathMixin):
|
|
|
1086
1259
|
|
|
1087
1260
|
return res
|
|
1088
1261
|
|
|
1262
|
+
def _read_array_for_area(self, _target_area, _target_projection, _x, _y, _w, _h):
|
|
1263
|
+
raise RuntimeError("Should not be called")
|
|
1264
|
+
|
|
1089
1265
|
class ShaderStyleOperation(LayerOperation):
|
|
1090
1266
|
|
|
1091
|
-
def _eval(self, area, projection, index, step, target_window=None):
|
|
1267
|
+
def _eval(self, cse_cache, area, projection, index, step, target_window=None):
|
|
1092
1268
|
if target_window is None:
|
|
1093
1269
|
target_window = self.window
|
|
1094
|
-
lhs_data = self.lhs._eval(area, projection, index, step, target_window)
|
|
1270
|
+
lhs_data = self.lhs._eval(cse_cache, area, projection, index, step, target_window)
|
|
1095
1271
|
if self.rhs is not None:
|
|
1096
|
-
rhs_data = self.rhs._eval(area, projection, index, step, target_window)
|
|
1272
|
+
rhs_data = self.rhs._eval(cse_cache, area, projection, index, step, target_window)
|
|
1097
1273
|
else:
|
|
1098
1274
|
rhs_data = None
|
|
1099
1275
|
|
|
@@ -1127,6 +1303,9 @@ class ShaderStyleOperation(LayerOperation):
|
|
|
1127
1303
|
|
|
1128
1304
|
return result
|
|
1129
1305
|
|
|
1306
|
+
def _read_array_for_area(self, _target_area, _target_projection, _x, _y, _w, _h):
|
|
1307
|
+
raise RuntimeError("Should not be called")
|
|
1308
|
+
|
|
1130
1309
|
def where(cond, a, b):
|
|
1131
1310
|
"""Return elements chosen from `a` or `b` depending on `cond`.
|
|
1132
1311
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
from .._backends import backend
|
|
3
|
+
from ..window import Window
|
|
4
|
+
|
|
5
|
+
class CSECacheTable:
|
|
6
|
+
|
|
7
|
+
def __init__(self, expression, window: Window) -> None:
|
|
8
|
+
self._table: dict[tuple[int, Window], tuple[int, backend.array_t | None]] = {}
|
|
9
|
+
self._populate(expression, window)
|
|
10
|
+
|
|
11
|
+
def __len__(self) -> int:
|
|
12
|
+
return len(self._table)
|
|
13
|
+
|
|
14
|
+
def _add(self, cse_hash: int | None, window: Window) -> int:
|
|
15
|
+
if cse_hash is None:
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
count, data = self._table[(cse_hash, window)]
|
|
20
|
+
cache_line = (count + 1, data)
|
|
21
|
+
except KeyError:
|
|
22
|
+
cache_line = (1, None)
|
|
23
|
+
self._table[(cse_hash, window)] = cache_line
|
|
24
|
+
count, _ = cache_line
|
|
25
|
+
return count
|
|
26
|
+
|
|
27
|
+
def _populate(self, expression, window: Window) -> None:
|
|
28
|
+
used_window = window.grow(expression.buffer_padding)
|
|
29
|
+
count = self._add(expression._cse_hash, used_window)
|
|
30
|
+
if count == 1:
|
|
31
|
+
# We only add children the first time we see an expression, otherwise
|
|
32
|
+
# we will cache data covered by this node's cache line potentially
|
|
33
|
+
for child in expression._children:
|
|
34
|
+
try:
|
|
35
|
+
self._populate(child, used_window)
|
|
36
|
+
except AttributeError:
|
|
37
|
+
try:
|
|
38
|
+
self._add(child._cse_hash, used_window)
|
|
39
|
+
except TypeError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def set_data(self, cse_hash: int | None, window: Window, data: backend.array_t) -> None:
|
|
43
|
+
if cse_hash is not None:
|
|
44
|
+
try:
|
|
45
|
+
count, old_data = self._table[(cse_hash, window)]
|
|
46
|
+
if count < 2:
|
|
47
|
+
return
|
|
48
|
+
if old_data is not None:
|
|
49
|
+
raise RuntimeWarning("Failure in CSE logic, setting data that is already set")
|
|
50
|
+
self._table[(cse_hash, window)] = (count, data)
|
|
51
|
+
except KeyError:
|
|
52
|
+
raise RuntimeWarning("Failure in CSE logic, setting data for unknown term") # pylint: disable=W0707
|
|
53
|
+
|
|
54
|
+
def get_data(self, cse_hash: int | None, window: Window) -> backend.array_t | None:
|
|
55
|
+
# TODO: In theory we could release data from the table if we decremented the count each read
|
|
56
|
+
# but we'd also need to fixed copy of the count for reset
|
|
57
|
+
if cse_hash is None:
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
_count, data = self._table[(cse_hash, window)]
|
|
61
|
+
return data
|
|
62
|
+
except KeyError:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def reset_cache(self):
|
|
66
|
+
self._table = {k: (count, None) for k, (count, _data) in self._table.items()}
|
yirgacheffe/layers/base.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
import uuid
|
|
2
3
|
from typing import Any, Sequence
|
|
3
4
|
|
|
4
5
|
import deprecation
|
|
@@ -28,7 +29,7 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
28
29
|
self._active_area: Area | None = None
|
|
29
30
|
self._projection = projection
|
|
30
31
|
self._window: Window | None = None
|
|
31
|
-
self.name = name
|
|
32
|
+
self.name = name if name is not None else str(uuid.uuid4())
|
|
32
33
|
|
|
33
34
|
self.reset_window()
|
|
34
35
|
|
|
@@ -41,6 +42,10 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
41
42
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
42
43
|
self.close()
|
|
43
44
|
|
|
45
|
+
@property
|
|
46
|
+
def _cse_hash(self) -> int | None:
|
|
47
|
+
raise NotImplementedError("Must be overridden by subclass")
|
|
48
|
+
|
|
44
49
|
def _park(self) -> None:
|
|
45
50
|
pass
|
|
46
51
|
|
yirgacheffe/layers/constant.py
CHANGED
|
@@ -15,6 +15,10 @@ class ConstantLayer(YirgacheffeLayer):
|
|
|
15
15
|
super().__init__(area, None)
|
|
16
16
|
self.value = float(value)
|
|
17
17
|
|
|
18
|
+
@property
|
|
19
|
+
def _cse_hash(self) -> int | None:
|
|
20
|
+
return hash(self.value)
|
|
21
|
+
|
|
18
22
|
@property
|
|
19
23
|
def datatype(self) -> DataType:
|
|
20
24
|
return DataType.Float64
|
yirgacheffe/layers/group.py
CHANGED
|
@@ -72,6 +72,10 @@ class GroupLayer(YirgacheffeLayer):
|
|
|
72
72
|
self._underlying_layers.reverse()
|
|
73
73
|
self.layers = self._underlying_layers
|
|
74
74
|
|
|
75
|
+
@property
|
|
76
|
+
def _cse_hash(self) -> int | None:
|
|
77
|
+
return hash(tuple(x._cse_hash for x in self._underlying_layers))
|
|
78
|
+
|
|
75
79
|
def _park(self) -> None:
|
|
76
80
|
for layer in self.layers:
|
|
77
81
|
try:
|
yirgacheffe/layers/h3layer.py
CHANGED
|
@@ -76,6 +76,15 @@ class H3CellLayer(YirgacheffeLayer):
|
|
|
76
76
|
(sorted_lats[1] / abs_ystep) * abs_ystep,
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
+
@property
|
|
80
|
+
def _cse_hash(self) -> int | None:
|
|
81
|
+
return hash((
|
|
82
|
+
self.cell_id,
|
|
83
|
+
self._underlying_area,
|
|
84
|
+
self.map_projection,
|
|
85
|
+
self._active_area,
|
|
86
|
+
))
|
|
87
|
+
|
|
79
88
|
@property
|
|
80
89
|
def _raster_dimensions(self) -> tuple[int, int]:
|
|
81
90
|
return (self._raster_xsize, self._raster_ysize)
|
yirgacheffe/layers/rasters.py
CHANGED
|
@@ -313,6 +313,18 @@ class RasterLayer(YirgacheffeLayer):
|
|
|
313
313
|
except RuntimeError as exc:
|
|
314
314
|
raise FileNotFoundError(f"Failed to open pickled raster {self._dataset_path}") from exc
|
|
315
315
|
|
|
316
|
+
@property
|
|
317
|
+
def _cse_hash(self) -> int | None:
|
|
318
|
+
return hash((
|
|
319
|
+
self.name,
|
|
320
|
+
self._underlying_area,
|
|
321
|
+
self.map_projection,
|
|
322
|
+
self.datatype,
|
|
323
|
+
self._active_area,
|
|
324
|
+
self._ignore_nodata,
|
|
325
|
+
self._band,
|
|
326
|
+
))
|
|
327
|
+
|
|
316
328
|
@property
|
|
317
329
|
def datatype(self) -> DataType:
|
|
318
330
|
if self._dataset is None:
|
yirgacheffe/layers/rescaled.py
CHANGED
|
@@ -52,6 +52,17 @@ class RescaledRasterLayer(YirgacheffeLayer):
|
|
|
52
52
|
self._x_scale = src_projection.xstep / target_projection.xstep
|
|
53
53
|
self._y_scale = src_projection.ystep / target_projection.ystep
|
|
54
54
|
|
|
55
|
+
@property
|
|
56
|
+
def _cse_hash(self) -> int | None:
|
|
57
|
+
return hash((
|
|
58
|
+
self._src._cse_hash,
|
|
59
|
+
self.name,
|
|
60
|
+
self._underlying_area,
|
|
61
|
+
self._nearest_neighbour,
|
|
62
|
+
self.map_projection,
|
|
63
|
+
self._active_area
|
|
64
|
+
))
|
|
65
|
+
|
|
55
66
|
def close(self):
|
|
56
67
|
self._src.close()
|
|
57
68
|
|
yirgacheffe/layers/vectors.py
CHANGED
|
@@ -303,7 +303,6 @@ class VectorLayer(YirgacheffeLayer):
|
|
|
303
303
|
if layer is None:
|
|
304
304
|
raise ValueError('No layer provided')
|
|
305
305
|
self.layer = layer
|
|
306
|
-
self.name = name
|
|
307
306
|
|
|
308
307
|
if isinstance(datatype, int):
|
|
309
308
|
self._datatype = DataType.of_gdal(datatype)
|
|
@@ -364,7 +363,7 @@ class VectorLayer(YirgacheffeLayer):
|
|
|
364
363
|
bottom=floor(min(x[2] for x in envelopes)),
|
|
365
364
|
)
|
|
366
365
|
|
|
367
|
-
super().__init__(area, projection)
|
|
366
|
+
super().__init__(area, projection, name=name)
|
|
368
367
|
|
|
369
368
|
|
|
370
369
|
def _get_operation_area(self, projection: MapProjection | None = None) -> Area:
|
|
@@ -441,6 +440,18 @@ class VectorLayer(YirgacheffeLayer):
|
|
|
441
440
|
if self._filter is not None:
|
|
442
441
|
self.layer.SetAttributeFilter(self._filter)
|
|
443
442
|
|
|
443
|
+
@property
|
|
444
|
+
def _cse_hash(self) -> int | None:
|
|
445
|
+
return hash((
|
|
446
|
+
self.name,
|
|
447
|
+
self._underlying_area,
|
|
448
|
+
self.map_projection,
|
|
449
|
+
self._active_area,
|
|
450
|
+
self._datatype,
|
|
451
|
+
self.burn_value,
|
|
452
|
+
self._filter,
|
|
453
|
+
))
|
|
454
|
+
|
|
444
455
|
@property
|
|
445
456
|
def datatype(self) -> DataType:
|
|
446
457
|
return self._datatype
|
yirgacheffe/window.py
CHANGED
|
@@ -41,6 +41,9 @@ class MapProjection:
|
|
|
41
41
|
self.xstep = xstep
|
|
42
42
|
self.ystep = ystep
|
|
43
43
|
|
|
44
|
+
def __hash__(self):
|
|
45
|
+
return hash((self.name, self.xstep, self.ystep))
|
|
46
|
+
|
|
44
47
|
def __eq__(self, other) -> bool:
|
|
45
48
|
if other is None:
|
|
46
49
|
return True
|
|
@@ -61,7 +64,7 @@ class MapProjection:
|
|
|
61
64
|
def scale(self) -> PixelScale:
|
|
62
65
|
return PixelScale(self.xstep, self.ystep)
|
|
63
66
|
|
|
64
|
-
@dataclass
|
|
67
|
+
@dataclass(frozen=True)
|
|
65
68
|
class Area:
|
|
66
69
|
"""Class to hold a geospatial area of data in the given projection.
|
|
67
70
|
|
|
@@ -154,7 +157,7 @@ class Area:
|
|
|
154
157
|
(other.top >= self.bottom >= other.bottom)
|
|
155
158
|
)
|
|
156
159
|
|
|
157
|
-
@dataclass
|
|
160
|
+
@dataclass(frozen=True)
|
|
158
161
|
class Window:
|
|
159
162
|
"""Class to hold the pixel dimensions of data in the given projection.
|
|
160
163
|
|
|
@@ -225,7 +228,7 @@ class Window:
|
|
|
225
228
|
"""
|
|
226
229
|
return Window(
|
|
227
230
|
xoff=self.xoff - pixels,
|
|
228
|
-
yoff=self.
|
|
231
|
+
yoff=self.yoff - pixels,
|
|
229
232
|
xsize=self.xsize + (2 * pixels),
|
|
230
233
|
ysize=self.ysize + (2 * pixels),
|
|
231
234
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yirgacheffe
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: Abstraction of gdal datasets for doing basic math operations
|
|
5
5
|
Author-email: Michael Dales <mwd24@cam.ac.uk>
|
|
6
6
|
License-Expression: ISC
|
|
@@ -37,6 +37,7 @@ Requires-Dist: mypy; extra == "dev"
|
|
|
37
37
|
Requires-Dist: pylint; extra == "dev"
|
|
38
38
|
Requires-Dist: pytest; extra == "dev"
|
|
39
39
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
40
41
|
Requires-Dist: build; extra == "dev"
|
|
41
42
|
Requires-Dist: twine; extra == "dev"
|
|
42
43
|
Requires-Dist: mkdocs-material; extra == "dev"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
yirgacheffe/__init__.py,sha256=Ps6W8A1TRriVNxZEF3jW1_KOLEtji4ffyoGRmQXne8g,927
|
|
2
|
+
yirgacheffe/_core.py,sha256=Tr6RAiRZOO3vbtiTjLoNRjjd1DkXYZOgpDqrjs7jCBw,7231
|
|
3
|
+
yirgacheffe/constants.py,sha256=bKUjOGNj19zwggV79lJgK7tiv51DH2-rgNOKswl2gvQ,293
|
|
4
|
+
yirgacheffe/operators.py,sha256=Y1KkNt79N1elR4ZplQaQngx29wdf2QFF_5la4PI3EhI,412
|
|
5
|
+
yirgacheffe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
yirgacheffe/rounding.py,sha256=Jzd9qlLnLpigT95GbQTByvYOo639Nfq4LBEVyvhYdoc,2289
|
|
7
|
+
yirgacheffe/window.py,sha256=11YqUw5GZs8qRjHSgQrvPbapTNFpx6OGwlj0qUtQc70,9863
|
|
8
|
+
yirgacheffe/_backends/__init__.py,sha256=jN-2iRrHStnPI6cNL7XhwhsROtI0EaGfIrbF5c-ECV0,334
|
|
9
|
+
yirgacheffe/_backends/enumeration.py,sha256=xeAWNfMORHd4Ue-CryajmteD-nxjIGcrU5m9PdXgEqA,2765
|
|
10
|
+
yirgacheffe/_backends/mlx.py,sha256=0Kq2weyC_fo-luATcMU1fBKB3aJjAg5Bl2_A3Mejp4E,6451
|
|
11
|
+
yirgacheffe/_backends/numpy.py,sha256=zgZo2u9ZVW8LYRKtLzSkFz3w7Y1j3_KwvVHTZ_xqGpE,4605
|
|
12
|
+
yirgacheffe/_operators/__init__.py,sha256=UZEy-0FR6un3UOMdws3XzqdaaDQLiWy0IqV9hOh2baE,49977
|
|
13
|
+
yirgacheffe/_operators/cse.py,sha256=4x5kwAGbTe8nBZYipz53mpW-Cr81A4vK-v6YWq5SmKg,2602
|
|
14
|
+
yirgacheffe/layers/__init__.py,sha256=mYKjw5YTcMNv_hMy7a6K4yRzIuNUbR8WuBTw4WIAmSk,435
|
|
15
|
+
yirgacheffe/layers/area.py,sha256=wJcMHbLJBaXS4BeFbu5rYeKfgu3gvaE9hwQ5j6aw-y4,3976
|
|
16
|
+
yirgacheffe/layers/base.py,sha256=jqtlAbRSHWDMZpEOl0dVuNNYKzR90s7uEuECvXCA3Dk,13378
|
|
17
|
+
yirgacheffe/layers/constant.py,sha256=eheddNT3fjMDpfISRipqKzOLJxR01D1IMerGJpperls,1567
|
|
18
|
+
yirgacheffe/layers/group.py,sha256=R0g-PhZprweVRYBJD5hp5trM8ztMCi0yyK-3UhDPGoo,16266
|
|
19
|
+
yirgacheffe/layers/h3layer.py,sha256=X5lfuM-TKVly6cWn9B-VbMTijmlsi7kUgp9OTYnJzJ0,10128
|
|
20
|
+
yirgacheffe/layers/rasters.py,sha256=Q9MPmS9WYJ17_inpL4KVkjJBX-jizjvERPdb8Mq-r5o,13820
|
|
21
|
+
yirgacheffe/layers/rescaled.py,sha256=7QzHL0l73-fS3XAhkdBRb3lhYJ-HnLrpjTByVfei9Fg,3630
|
|
22
|
+
yirgacheffe/layers/vectors.py,sha256=KxVUgBIgNyFuACyGkcdK8eU09stVCf5VQAvBL7RfoGA,20268
|
|
23
|
+
yirgacheffe-1.10.0.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
|
|
24
|
+
yirgacheffe-1.10.0.dist-info/METADATA,sha256=rRgnXn19ivS7CUPT78xw9RGoh0h1Nmm80JuPuAsVG9g,7451
|
|
25
|
+
yirgacheffe-1.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
yirgacheffe-1.10.0.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
|
|
27
|
+
yirgacheffe-1.10.0.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
|
|
28
|
+
yirgacheffe-1.10.0.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
yirgacheffe/__init__.py,sha256=Ps6W8A1TRriVNxZEF3jW1_KOLEtji4ffyoGRmQXne8g,927
|
|
2
|
-
yirgacheffe/_core.py,sha256=Tr6RAiRZOO3vbtiTjLoNRjjd1DkXYZOgpDqrjs7jCBw,7231
|
|
3
|
-
yirgacheffe/_operators.py,sha256=VGQ9AOOJP6cxsr_G2kxdaPaXqKi7K1csocxsudlRwVc,43440
|
|
4
|
-
yirgacheffe/constants.py,sha256=bKUjOGNj19zwggV79lJgK7tiv51DH2-rgNOKswl2gvQ,293
|
|
5
|
-
yirgacheffe/operators.py,sha256=Y1KkNt79N1elR4ZplQaQngx29wdf2QFF_5la4PI3EhI,412
|
|
6
|
-
yirgacheffe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
yirgacheffe/rounding.py,sha256=Jzd9qlLnLpigT95GbQTByvYOo639Nfq4LBEVyvhYdoc,2289
|
|
8
|
-
yirgacheffe/window.py,sha256=kAAsE3t49DbffrW78DLZuGwgT-MCpM3WBJQ-5MO5ciM,9755
|
|
9
|
-
yirgacheffe/_backends/__init__.py,sha256=jN-2iRrHStnPI6cNL7XhwhsROtI0EaGfIrbF5c-ECV0,334
|
|
10
|
-
yirgacheffe/_backends/enumeration.py,sha256=Clb-oRha65po_dED_lECXjnih-n77CtUg18-34xX6nA,2652
|
|
11
|
-
yirgacheffe/_backends/mlx.py,sha256=U1gl1lK1mZXLEET6ylF1TNs6WJ0PBEvfSk7ppn28n8w,6203
|
|
12
|
-
yirgacheffe/_backends/numpy.py,sha256=Gxx49JJH79GFEkKIpV6IyjCUcdtN5-qLlzRfylzKhS4,4142
|
|
13
|
-
yirgacheffe/layers/__init__.py,sha256=mYKjw5YTcMNv_hMy7a6K4yRzIuNUbR8WuBTw4WIAmSk,435
|
|
14
|
-
yirgacheffe/layers/area.py,sha256=wJcMHbLJBaXS4BeFbu5rYeKfgu3gvaE9hwQ5j6aw-y4,3976
|
|
15
|
-
yirgacheffe/layers/base.py,sha256=7b4WXuvnmCv8mR0iyCIuSEolnV8D3f2vtCaYlcJCIa8,13201
|
|
16
|
-
yirgacheffe/layers/constant.py,sha256=gtkQ98Z01CYYDgFElswtRZY4ZG3UnS5NIAoIVue5ufk,1481
|
|
17
|
-
yirgacheffe/layers/group.py,sha256=jFJ60YcbkLNeD-2w3QOLwbcYEWdgicrXMClIo2vz97Q,16139
|
|
18
|
-
yirgacheffe/layers/h3layer.py,sha256=Rq1bFo7CApIh5NdBcV7hSj3hm-DszY79nhYsTRAvJ_g,9916
|
|
19
|
-
yirgacheffe/layers/rasters.py,sha256=zBE9uXm6LvAQF2_XdQzcOgJQOQWGmuPflY5JNDrUf3k,13527
|
|
20
|
-
yirgacheffe/layers/rescaled.py,sha256=gEFbXeYxX1nVn7eQYmbGww90_yc5ENmgQrD_WxXxpQE,3352
|
|
21
|
-
yirgacheffe/layers/vectors.py,sha256=A27kuTr0C9BZhHG0-cplNEa7aSNcse37Pm9xTjEzv-c,19990
|
|
22
|
-
yirgacheffe-1.9.5.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
|
|
23
|
-
yirgacheffe-1.9.5.dist-info/METADATA,sha256=TvX6Nwvdp_u80s9PXHCvSoxndvp4mjOBE1wRcn-neL0,7407
|
|
24
|
-
yirgacheffe-1.9.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
yirgacheffe-1.9.5.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
|
|
26
|
-
yirgacheffe-1.9.5.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
|
|
27
|
-
yirgacheffe-1.9.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|