yirgacheffe 1.2.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.
- yirgacheffe/__init__.py +17 -0
- yirgacheffe/backends/__init__.py +13 -0
- yirgacheffe/backends/enumeration.py +33 -0
- yirgacheffe/backends/mlx.py +156 -0
- yirgacheffe/backends/numpy.py +110 -0
- yirgacheffe/constants.py +1 -0
- yirgacheffe/h3layer.py +2 -0
- yirgacheffe/layers/__init__.py +44 -0
- yirgacheffe/layers/area.py +91 -0
- yirgacheffe/layers/base.py +265 -0
- yirgacheffe/layers/constant.py +41 -0
- yirgacheffe/layers/group.py +357 -0
- yirgacheffe/layers/h3layer.py +203 -0
- yirgacheffe/layers/rasters.py +333 -0
- yirgacheffe/layers/rescaled.py +94 -0
- yirgacheffe/layers/vectors.py +380 -0
- yirgacheffe/operators.py +738 -0
- yirgacheffe/rounding.py +57 -0
- yirgacheffe/window.py +141 -0
- yirgacheffe-1.2.0.dist-info/METADATA +473 -0
- yirgacheffe-1.2.0.dist-info/RECORD +25 -0
- yirgacheffe-1.2.0.dist-info/WHEEL +5 -0
- yirgacheffe-1.2.0.dist-info/entry_points.txt +2 -0
- yirgacheffe-1.2.0.dist-info/licenses/LICENSE +7 -0
- yirgacheffe-1.2.0.dist-info/top_level.txt +1 -0
yirgacheffe/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from osgeo import gdal
|
|
2
|
+
try:
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
__version__ = metadata.version(__name__)
|
|
5
|
+
except ModuleNotFoundError:
|
|
6
|
+
__version__ = "unknown"
|
|
7
|
+
|
|
8
|
+
gdal.UseExceptions()
|
|
9
|
+
|
|
10
|
+
# I don't really want this here, but it's just too useful having it exposed
|
|
11
|
+
WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,'\
|
|
12
|
+
'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
|
|
13
|
+
'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
|
|
14
|
+
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
|
|
15
|
+
|
|
16
|
+
# For legacy reasons [facepalm]
|
|
17
|
+
WSG_84_PROJECTION = WGS_84_PROJECTION
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
BACKEND = os.environ.get("YIRGACHEFFE_BACKEND", "NUMPY").upper()
|
|
4
|
+
|
|
5
|
+
match BACKEND:
|
|
6
|
+
case "MLX":
|
|
7
|
+
from . import mlx
|
|
8
|
+
backend = mlx
|
|
9
|
+
case "NUMPY":
|
|
10
|
+
from . import numpy
|
|
11
|
+
backend = numpy
|
|
12
|
+
case _:
|
|
13
|
+
raise NotImplementedError("Only NUMPY and MLX backends supported")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
class operators(Enum):
|
|
4
|
+
ADD = 1
|
|
5
|
+
SUB = 2
|
|
6
|
+
MUL = 3
|
|
7
|
+
TRUEDIV = 4
|
|
8
|
+
POW = 5
|
|
9
|
+
EQ = 6
|
|
10
|
+
NE = 7
|
|
11
|
+
LT = 8
|
|
12
|
+
LE = 9
|
|
13
|
+
GT = 10
|
|
14
|
+
GE = 11
|
|
15
|
+
AND = 12
|
|
16
|
+
OR = 13
|
|
17
|
+
LOG = 14
|
|
18
|
+
LOG2 = 15
|
|
19
|
+
LOG10 = 16
|
|
20
|
+
EXP = 17
|
|
21
|
+
EXP2 = 18
|
|
22
|
+
CLIP = 19
|
|
23
|
+
WHERE = 20
|
|
24
|
+
MIN = 21
|
|
25
|
+
MAX = 22
|
|
26
|
+
SUM = 23
|
|
27
|
+
MINIMUM = 24
|
|
28
|
+
MAXIMUM = 25
|
|
29
|
+
NAN_TO_NUM = 26
|
|
30
|
+
ISIN = 27
|
|
31
|
+
REMAINDER = 28
|
|
32
|
+
FLOORDIV = 29
|
|
33
|
+
CONV2D = 30
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import mlx.core as mx # pylint: disable=E0001,E0611,E0401
|
|
4
|
+
import mlx.nn
|
|
5
|
+
|
|
6
|
+
from .enumeration import operators as op
|
|
7
|
+
|
|
8
|
+
array_t = mx.array
|
|
9
|
+
float_t = mx.float32
|
|
10
|
+
|
|
11
|
+
promote = mx.array
|
|
12
|
+
demote_array = np.asarray
|
|
13
|
+
demote_scalar = np.float64
|
|
14
|
+
|
|
15
|
+
eval_op = mx.eval
|
|
16
|
+
|
|
17
|
+
add_op = mx.add
|
|
18
|
+
sub_op = mx.array.__sub__
|
|
19
|
+
truediv_op = mx.array.__truediv__
|
|
20
|
+
pow_op = mx.array.__pow__
|
|
21
|
+
eq_op = mx.array.__eq__
|
|
22
|
+
ne_op =mx.array.__ne__
|
|
23
|
+
lt_op = mx.less
|
|
24
|
+
le_op = mx.less_equal
|
|
25
|
+
gt_op = mx.greater
|
|
26
|
+
ge_op = mx.greater_equal
|
|
27
|
+
and_op = mx.array.__and__
|
|
28
|
+
or_op = mx.array.__or__
|
|
29
|
+
log = mx.log
|
|
30
|
+
log2 = mx.log2
|
|
31
|
+
log10 = mx.log10
|
|
32
|
+
exp = mx.exp
|
|
33
|
+
clip = mx.clip
|
|
34
|
+
where = mx.where
|
|
35
|
+
min_op = mx.min
|
|
36
|
+
max_op = mx.max
|
|
37
|
+
maximum = mx.maximum
|
|
38
|
+
minimum = mx.minimum
|
|
39
|
+
zeros = mx.zeros
|
|
40
|
+
pad = mx.pad
|
|
41
|
+
isscalar = np.isscalar
|
|
42
|
+
full = mx.full
|
|
43
|
+
allclose = mx.allclose
|
|
44
|
+
remainder_op = mx.remainder
|
|
45
|
+
floordiv_op = mx.array.__floordiv__
|
|
46
|
+
|
|
47
|
+
def sum_op(a):
|
|
48
|
+
# There are weird issues around how MLX overflows int8, so just promote the data ahead of summing
|
|
49
|
+
match a.dtype:
|
|
50
|
+
case mx.int8:
|
|
51
|
+
res = mx.sum(a.astype(mx.int32))
|
|
52
|
+
case mx.uint8:
|
|
53
|
+
res = mx.sum(a.astype(mx.uint32))
|
|
54
|
+
case _:
|
|
55
|
+
res = mx.sum(a)
|
|
56
|
+
return demote_scalar(res)
|
|
57
|
+
|
|
58
|
+
def _is_float(x):
|
|
59
|
+
if isinstance(x, float):
|
|
60
|
+
return True
|
|
61
|
+
try:
|
|
62
|
+
np_floats = [np.dtype('float16'), np.dtype('float32'), np.dtype('float64')]
|
|
63
|
+
if x.dtype in np_floats:
|
|
64
|
+
return True
|
|
65
|
+
match x.dtype:
|
|
66
|
+
case mx.float32 | mx.float64:
|
|
67
|
+
return True
|
|
68
|
+
case _:
|
|
69
|
+
return False
|
|
70
|
+
except AttributeError:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def mul_op(a, b):
|
|
74
|
+
# numpy will promote an operation between float and int to float, whereas it looks like mlx does the inverse
|
|
75
|
+
# and so for consistency with the numpy path, we do some fiddling here if necessary
|
|
76
|
+
if _is_float(b):
|
|
77
|
+
match a.dtype:
|
|
78
|
+
case mx.int8 | mx.int32 | mx.uint8 | mx.uint32:
|
|
79
|
+
a = a.astype(mx.float32)
|
|
80
|
+
case mx.int64 | mx.uint64:
|
|
81
|
+
a = a.astype(mx.float64)
|
|
82
|
+
case _:
|
|
83
|
+
pass
|
|
84
|
+
return mx.multiply(a, b)
|
|
85
|
+
|
|
86
|
+
def exp2(a):
|
|
87
|
+
mx.eval(a)
|
|
88
|
+
return promote(np.exp2(a))
|
|
89
|
+
|
|
90
|
+
def nan_to_num(a, nan, posinf, neginf, copy): # pylint: disable=W0613
|
|
91
|
+
return mx.nan_to_num(a, float(nan), posinf, neginf)
|
|
92
|
+
|
|
93
|
+
def isin(a, test_elements):
|
|
94
|
+
# There is no `isin` on MLX currently, so we need to fallback to CPU behaviour here
|
|
95
|
+
# https://ml-explore.github.io/mlx/build/html/dev/custom_metal_kernels.html#using-shape-strides
|
|
96
|
+
mx.eval(a)
|
|
97
|
+
return promote(np.isin(a, test_elements))
|
|
98
|
+
|
|
99
|
+
def conv2d_op(data, weights):
|
|
100
|
+
# From numpy.py: torch wants to process dimensions of channels of width of height
|
|
101
|
+
# but mlx wants to process dimensions of width of height of channels, so we end up
|
|
102
|
+
# having to reshape the data, as we only ever use one channel.
|
|
103
|
+
# Which is why both the data and weights get nested into two arrays here,
|
|
104
|
+
# and then we have to unpack it from that nesting.
|
|
105
|
+
|
|
106
|
+
weights = mx.array(weights)
|
|
107
|
+
|
|
108
|
+
original_data_shape = data.shape
|
|
109
|
+
original_weights_shape = weights.shape
|
|
110
|
+
|
|
111
|
+
unshifted_preped_weights = np.array([[weights]])
|
|
112
|
+
conv_weights_shape = [1] + list(original_weights_shape) + [1]
|
|
113
|
+
preped_weights = mx.array(np.reshape(unshifted_preped_weights, conv_weights_shape))
|
|
114
|
+
|
|
115
|
+
conv = mlx.nn.Conv2d(1, 1, weights.shape, bias=False)
|
|
116
|
+
conv.weight = preped_weights
|
|
117
|
+
|
|
118
|
+
conv_data_shape = [1] + list(original_data_shape) + [1]
|
|
119
|
+
unshifted_data_shape = np.array([[data]])
|
|
120
|
+
preped_data = mx.array(np.reshape(unshifted_data_shape, conv_data_shape))
|
|
121
|
+
|
|
122
|
+
shifted_res = conv(preped_data)[0]
|
|
123
|
+
res = np.reshape(shifted_res, [1] + list(shifted_res.shape)[:-1])
|
|
124
|
+
return res[0]
|
|
125
|
+
|
|
126
|
+
operator_map = {
|
|
127
|
+
op.ADD: mx.array.__add__,
|
|
128
|
+
op.SUB: mx.array.__sub__,
|
|
129
|
+
op.MUL: mul_op,
|
|
130
|
+
op.TRUEDIV: mx.array.__truediv__,
|
|
131
|
+
op.POW: mx.array.__pow__,
|
|
132
|
+
op.EQ: mx.array.__eq__,
|
|
133
|
+
op.NE: mx.array.__ne__,
|
|
134
|
+
op.LT: mx.array.__lt__,
|
|
135
|
+
op.LE: mx.array.__le__,
|
|
136
|
+
op.GT: mx.array.__gt__,
|
|
137
|
+
op.GE: mx.array.__ge__,
|
|
138
|
+
op.AND: mx.array.__and__,
|
|
139
|
+
op.OR: mx.array.__or__,
|
|
140
|
+
op.LOG: mx.log,
|
|
141
|
+
op.LOG2: mx.log2,
|
|
142
|
+
op.LOG10: mx.log10,
|
|
143
|
+
op.EXP: mx.exp,
|
|
144
|
+
op.EXP2: exp2,
|
|
145
|
+
op.CLIP: mx.clip,
|
|
146
|
+
op.WHERE: mx.where,
|
|
147
|
+
op.MIN: mx.min,
|
|
148
|
+
op.MAX:mx.max,
|
|
149
|
+
op.MINIMUM: mx.minimum,
|
|
150
|
+
op.MAXIMUM: mx.maximum,
|
|
151
|
+
op.NAN_TO_NUM: nan_to_num,
|
|
152
|
+
op.ISIN: isin,
|
|
153
|
+
op.REMAINDER: mx.remainder,
|
|
154
|
+
op.FLOORDIV: mx.array.__floordiv__,
|
|
155
|
+
op.CONV2D: conv2d_op,
|
|
156
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
|
|
5
|
+
from .enumeration import operators as op
|
|
6
|
+
|
|
7
|
+
array_t = np.ndarray
|
|
8
|
+
float_t = np.float64
|
|
9
|
+
|
|
10
|
+
promote = lambda a: a
|
|
11
|
+
demote_array = lambda a: a
|
|
12
|
+
demote_scalar = lambda a: a
|
|
13
|
+
eval_op = lambda a: a
|
|
14
|
+
|
|
15
|
+
add_op = np.ndarray.__add__
|
|
16
|
+
sub_op = np.ndarray.__sub__
|
|
17
|
+
mul_op = np.ndarray.__mul__
|
|
18
|
+
truediv_op = np.ndarray.__truediv__
|
|
19
|
+
pow_op = np.ndarray.__pow__
|
|
20
|
+
eq_op = np.ndarray.__eq__
|
|
21
|
+
ne_op = np.ndarray.__ne__
|
|
22
|
+
lt_op = np.ndarray.__lt__
|
|
23
|
+
le_op = np.ndarray.__le__
|
|
24
|
+
gt_op = np.ndarray.__gt__
|
|
25
|
+
ge_op = np.ndarray.__ge__
|
|
26
|
+
and_op = np.ndarray.__and__
|
|
27
|
+
or_op = np.ndarray.__or__
|
|
28
|
+
nan_to_num = np.nan_to_num
|
|
29
|
+
isin = np.isin
|
|
30
|
+
log = np.log
|
|
31
|
+
log2 = np.log2
|
|
32
|
+
log10 = np.log10
|
|
33
|
+
exp = np.exp
|
|
34
|
+
exp2 = np.exp2
|
|
35
|
+
clip = np.clip
|
|
36
|
+
where = np.where
|
|
37
|
+
min_op = np.min
|
|
38
|
+
max_op = np.max
|
|
39
|
+
maximum = np.maximum
|
|
40
|
+
minimum = np.minimum
|
|
41
|
+
zeros = np.zeros
|
|
42
|
+
pad = np.pad
|
|
43
|
+
sum_op = lambda a: np.sum(a.astype(np.float64))
|
|
44
|
+
isscalar = np.isscalar
|
|
45
|
+
full = np.full
|
|
46
|
+
allclose = np.allclose
|
|
47
|
+
remainder_op = np.ndarray.__mod__
|
|
48
|
+
floordiv_op = np.ndarray.__floordiv__
|
|
49
|
+
|
|
50
|
+
def conv2d_op(data, weights):
|
|
51
|
+
# torch wants to process dimensions of channels of width of height
|
|
52
|
+
# Which is why both the data and weights get nested into two arrays here,
|
|
53
|
+
# and then we have to unpack it from that nesting.
|
|
54
|
+
|
|
55
|
+
preped_weights = np.array([[weights]])
|
|
56
|
+
conv = torch.nn.Conv2d(1, 1, weights.shape, bias=False)
|
|
57
|
+
conv.weight = torch.nn.Parameter(torch.from_numpy(preped_weights))
|
|
58
|
+
preped_data = torch.from_numpy(np.array([[data]]))
|
|
59
|
+
|
|
60
|
+
res = conv(preped_data)
|
|
61
|
+
return res.detach().numpy()[0][0]
|
|
62
|
+
|
|
63
|
+
operator_map = {
|
|
64
|
+
op.ADD: np.ndarray.__add__,
|
|
65
|
+
op.SUB: np.ndarray.__sub__,
|
|
66
|
+
op.MUL: np.ndarray.__mul__,
|
|
67
|
+
op.TRUEDIV: np.ndarray.__truediv__,
|
|
68
|
+
op.POW: np.ndarray.__pow__,
|
|
69
|
+
op.EQ: np.ndarray.__eq__,
|
|
70
|
+
op.NE: np.ndarray.__ne__,
|
|
71
|
+
op.LT: np.ndarray.__lt__,
|
|
72
|
+
op.LE: np.ndarray.__le__,
|
|
73
|
+
op.GT: np.ndarray.__gt__,
|
|
74
|
+
op.GE: np.ndarray.__ge__,
|
|
75
|
+
op.AND: np.ndarray.__and__,
|
|
76
|
+
op.OR: np.ndarray.__or__,
|
|
77
|
+
op.LOG: np.log,
|
|
78
|
+
op.LOG2: np.log2,
|
|
79
|
+
op.LOG10: np.log10,
|
|
80
|
+
op.EXP: np.exp,
|
|
81
|
+
op.EXP2: np.exp2,
|
|
82
|
+
op.CLIP: np.clip,
|
|
83
|
+
op.WHERE: np.where,
|
|
84
|
+
op.MIN: np.min,
|
|
85
|
+
op.MAX: np.max,
|
|
86
|
+
op.MINIMUM: np.minimum,
|
|
87
|
+
op.MAXIMUM: np.maximum,
|
|
88
|
+
op.NAN_TO_NUM: np.nan_to_num,
|
|
89
|
+
op.ISIN: np.isin,
|
|
90
|
+
op.REMAINDER: np.ndarray.__mod__,
|
|
91
|
+
op.FLOORDIV: np.ndarray.__floordiv__,
|
|
92
|
+
op.CONV2D: conv2d_op,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
operator_str_map = {
|
|
96
|
+
op.POW: "np.ndarray.__pow__(%s, %s)",
|
|
97
|
+
op.LOG: "np.log(%s)",
|
|
98
|
+
op.LOG2: "np.log2(%s)",
|
|
99
|
+
op.LOG10: "np.log10(%s)",
|
|
100
|
+
op.EXP: "np.exp(%s)",
|
|
101
|
+
op.EXP2: "np.exp2(%s)",
|
|
102
|
+
op.CLIP: "np.clip",
|
|
103
|
+
op.WHERE: "np.where(%s, %s, %s)",
|
|
104
|
+
op.MIN: "np.min(%s)",
|
|
105
|
+
op.MAX: "np.max(%s)",
|
|
106
|
+
op.MINIMUM: "np.minimum(%s)",
|
|
107
|
+
op.MAXIMUM: "np.maximum(%s)",
|
|
108
|
+
op.NAN_TO_NUM: "np.nan_to_num(%s)",
|
|
109
|
+
op.ISIN: "np.isin(%s, %s)",
|
|
110
|
+
}
|
yirgacheffe/constants.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
YSTEP = 512
|
yirgacheffe/h3layer.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from osgeo import ogr
|
|
2
|
+
|
|
3
|
+
from ..window import PixelScale
|
|
4
|
+
from .base import YirgacheffeLayer
|
|
5
|
+
from .rasters import RasterLayer, InvalidRasterBand
|
|
6
|
+
from .rescaled import RescaledRasterLayer
|
|
7
|
+
from .vectors import RasteredVectorLayer, VectorLayer
|
|
8
|
+
from .area import UniformAreaLayer
|
|
9
|
+
from .constant import ConstantLayer
|
|
10
|
+
from .group import GroupLayer, TiledGroupLayer
|
|
11
|
+
try:
|
|
12
|
+
from .h3layer import H3CellLayer
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Layer(RasterLayer):
|
|
18
|
+
"""A place holder for now, at some point I want to replace Layer with RasterLayer."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VectorRangeLayer(RasteredVectorLayer):
|
|
22
|
+
"""Deprecated older name for VectorLayer"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, range_vectors: str, where_filter: str, scale: PixelScale, projection: str):
|
|
25
|
+
vectors = ogr.Open(range_vectors)
|
|
26
|
+
if vectors is None:
|
|
27
|
+
raise FileNotFoundError(range_vectors)
|
|
28
|
+
layer = vectors.GetLayer()
|
|
29
|
+
if where_filter is not None:
|
|
30
|
+
layer.SetAttributeFilter(where_filter)
|
|
31
|
+
super().__init__(layer, scale, projection)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DynamicVectorRangeLayer(VectorLayer):
|
|
35
|
+
"""Deprecated older name DynamicVectorLayer"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, range_vectors: str, where_filter: str, scale: PixelScale, projection: str):
|
|
38
|
+
vectors = ogr.Open(range_vectors)
|
|
39
|
+
if vectors is None:
|
|
40
|
+
raise FileNotFoundError(range_vectors)
|
|
41
|
+
layer = vectors.GetLayer()
|
|
42
|
+
if where_filter is not None:
|
|
43
|
+
layer.SetAttributeFilter(where_filter)
|
|
44
|
+
super().__init__(layer, scale, projection)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from math import ceil, floor
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
from osgeo import gdal
|
|
6
|
+
|
|
7
|
+
from ..window import Area, Window
|
|
8
|
+
from .rasters import RasterLayer
|
|
9
|
+
|
|
10
|
+
class UniformAreaLayer(RasterLayer):
|
|
11
|
+
"""If you have a pixel area map where all the row entries are identical, then you
|
|
12
|
+
can speed up the AoH calculations by simplifying that to a 1 pixel wide map and then
|
|
13
|
+
synthesizing the rest of the data at calc time, as decompressing the large compressed
|
|
14
|
+
TIFF files is quite slow. This class is used to load such a dataset.
|
|
15
|
+
|
|
16
|
+
If you have a file that is large that you'd like to shrink you can call the static method
|
|
17
|
+
generate_narrow_area_projection which will shrink the file and correct the geo info.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def generate_narrow_area_projection(source_filename: str, target_filename: str) -> None:
|
|
22
|
+
source = gdal.Open(source_filename, gdal.GA_ReadOnly)
|
|
23
|
+
if source is None:
|
|
24
|
+
raise FileNotFoundError(source_filename)
|
|
25
|
+
if not UniformAreaLayer.is_uniform_area_projection(source):
|
|
26
|
+
raise ValueError("Data in area pixel map is not uniform across rows")
|
|
27
|
+
source_band = source.GetRasterBand(1)
|
|
28
|
+
target = gdal.GetDriverByName('GTiff').Create(
|
|
29
|
+
target_filename,
|
|
30
|
+
1,
|
|
31
|
+
source.RasterYSize,
|
|
32
|
+
1,
|
|
33
|
+
source_band.DataType,
|
|
34
|
+
['COMPRESS=LZW']
|
|
35
|
+
)
|
|
36
|
+
target.SetProjection(source.GetProjection())
|
|
37
|
+
target.SetGeoTransform(source.GetGeoTransform())
|
|
38
|
+
# Although the output is 1 pixel wide, the input can be very wide, so we do this in stages
|
|
39
|
+
# otherwise gdal eats all the memory
|
|
40
|
+
step = 1000
|
|
41
|
+
target_band = target.GetRasterBand(1)
|
|
42
|
+
for yoffset in range(0, source.RasterYSize, step):
|
|
43
|
+
this_step = step
|
|
44
|
+
if (yoffset + this_step) > source.RasterYSize:
|
|
45
|
+
this_step = source.RasterYSize - yoffset
|
|
46
|
+
data = source_band.ReadAsArray(0, yoffset, 1, this_step)
|
|
47
|
+
target_band.WriteArray(data, 0, yoffset)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def is_uniform_area_projection(dataset) -> bool:
|
|
51
|
+
"Check that the dataset conforms to the assumption that all rows contain the same value. Likely to be slow."
|
|
52
|
+
band = dataset.GetRasterBand(1)
|
|
53
|
+
for yoffset in range(dataset.RasterYSize):
|
|
54
|
+
row = band.ReadAsArray(0, yoffset, dataset.RasterXSize, 1)
|
|
55
|
+
if not numpy.all(numpy.isclose(row, row[0])):
|
|
56
|
+
return False
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def __init__(self, dataset, name: Optional[str] = None, band: int = 1):
|
|
60
|
+
if dataset.RasterXSize > 1:
|
|
61
|
+
raise ValueError("Expected a shrunk dataset")
|
|
62
|
+
self.databand = dataset.GetRasterBand(1).ReadAsArray(0, 0, 1, dataset.RasterYSize)
|
|
63
|
+
|
|
64
|
+
super().__init__(dataset, name, band)
|
|
65
|
+
|
|
66
|
+
transform = dataset.GetGeoTransform()
|
|
67
|
+
|
|
68
|
+
pixel_scale = self.pixel_scale
|
|
69
|
+
assert pixel_scale # from raster we should always have one
|
|
70
|
+
|
|
71
|
+
self._underlying_area = Area(
|
|
72
|
+
floor(-180 / pixel_scale.xstep) * pixel_scale.xstep,
|
|
73
|
+
self.area.top,
|
|
74
|
+
ceil(180 / pixel_scale.xstep) * pixel_scale.xstep,
|
|
75
|
+
self.area.bottom
|
|
76
|
+
)
|
|
77
|
+
self._active_area = self._underlying_area
|
|
78
|
+
|
|
79
|
+
self._window = Window(
|
|
80
|
+
xoff=0,
|
|
81
|
+
yoff=0,
|
|
82
|
+
xsize=int((self.area.right - self.area.left) / transform[1]),
|
|
83
|
+
ysize=dataset.RasterYSize,
|
|
84
|
+
)
|
|
85
|
+
self._raster_xsize = self.window.xsize
|
|
86
|
+
|
|
87
|
+
def read_array_with_window(self, xoffset: int, yoffset: int, xsize: int, ysize: int, window: Window) -> Any:
|
|
88
|
+
if ysize <= 0:
|
|
89
|
+
raise ValueError("Request dimensions must be positive and non-zero")
|
|
90
|
+
offset = window.yoff + yoffset
|
|
91
|
+
return self.databand[offset:offset + ysize]
|