rio-tiler 8.0.4__py3-none-any.whl → 9.0.0a1__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.
- rio_tiler/__init__.py +1 -1
- rio_tiler/colormap.py +6 -6
- rio_tiler/experimental/vsifile.py +1 -3
- rio_tiler/experimental/zarr.py +29 -28
- rio_tiler/expression.py +4 -4
- rio_tiler/io/base.py +222 -347
- rio_tiler/io/rasterio.py +89 -81
- rio_tiler/io/stac.py +50 -41
- rio_tiler/io/xarray.py +35 -27
- rio_tiler/models.py +70 -53
- rio_tiler/mosaic/backend.py +2 -2
- rio_tiler/mosaic/methods/base.py +3 -4
- rio_tiler/mosaic/methods/defaults.py +18 -19
- rio_tiler/mosaic/reader.py +20 -19
- rio_tiler/reader.py +44 -44
- rio_tiler/tasks.py +9 -8
- rio_tiler/types.py +20 -21
- rio_tiler/utils.py +33 -42
- {rio_tiler-8.0.4.dist-info → rio_tiler-9.0.0a1.dist-info}/METADATA +1 -1
- {rio_tiler-8.0.4.dist-info → rio_tiler-9.0.0a1.dist-info}/RECORD +23 -23
- {rio_tiler-8.0.4.dist-info → rio_tiler-9.0.0a1.dist-info}/WHEEL +0 -0
- {rio_tiler-8.0.4.dist-info → rio_tiler-9.0.0a1.dist-info}/licenses/AUTHORS.txt +0 -0
- {rio_tiler-8.0.4.dist-info → rio_tiler-9.0.0a1.dist-info}/licenses/LICENSE +0 -0
rio_tiler/io/xarray.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import math
|
|
6
6
|
import os
|
|
7
7
|
import warnings
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
import attr
|
|
11
11
|
import numpy
|
|
@@ -81,7 +81,7 @@ class XarrayReader(BaseReader):
|
|
|
81
81
|
|
|
82
82
|
tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
|
|
83
83
|
|
|
84
|
-
_dims:
|
|
84
|
+
_dims: list = attr.ib(init=False, factory=list)
|
|
85
85
|
|
|
86
86
|
def __attrs_post_init__(self):
|
|
87
87
|
"""Set bounds and CRS."""
|
|
@@ -138,7 +138,7 @@ class XarrayReader(BaseReader):
|
|
|
138
138
|
return self._maxzoom
|
|
139
139
|
|
|
140
140
|
@property
|
|
141
|
-
def band_descriptions(self) ->
|
|
141
|
+
def band_descriptions(self) -> list[str]:
|
|
142
142
|
"""
|
|
143
143
|
Return list of `band descriptions` in DataArray.
|
|
144
144
|
|
|
@@ -160,7 +160,7 @@ class XarrayReader(BaseReader):
|
|
|
160
160
|
if coords_name:
|
|
161
161
|
return [str(self.input.coords[coords_name[0]].data)]
|
|
162
162
|
|
|
163
|
-
return [self.input.name or ""] # type: ignore
|
|
163
|
+
return [self.input.name or "b1"] # type: ignore
|
|
164
164
|
|
|
165
165
|
return [str(band) for d in self._dims for band in self.input[d].values]
|
|
166
166
|
|
|
@@ -173,7 +173,8 @@ class XarrayReader(BaseReader):
|
|
|
173
173
|
"crs": CRS_to_uri(self.crs) or self.crs.to_wkt(),
|
|
174
174
|
"band_metadata": [(f"b{ix}", v) for ix, v in enumerate(metadata, 1)],
|
|
175
175
|
"band_descriptions": [
|
|
176
|
-
(f"b{ix}", v
|
|
176
|
+
(f"b{ix}", v or f"b{ix}")
|
|
177
|
+
for ix, v in enumerate(self.band_descriptions, 1)
|
|
177
178
|
],
|
|
178
179
|
"dtype": str(self.input.dtype),
|
|
179
180
|
"nodata_type": "Nodata" if self.input.rio.nodata is not None else "None",
|
|
@@ -193,11 +194,10 @@ class XarrayReader(BaseReader):
|
|
|
193
194
|
def _sel_indexes(
|
|
194
195
|
self,
|
|
195
196
|
indexes: Indexes | None = None,
|
|
196
|
-
) ->
|
|
197
|
+
) -> tuple[xarray.DataArray, list[str]]:
|
|
197
198
|
"""Select `band` indexes in DataArray."""
|
|
198
199
|
da = self.input
|
|
199
200
|
band_descriptions = self.band_descriptions
|
|
200
|
-
band_names = [f"b{ix + 1}" for ix in range(self.input.rio.count)]
|
|
201
201
|
|
|
202
202
|
if indexes := cast_to_sequence(indexes):
|
|
203
203
|
assert all(v > 0 for v in indexes), "Indexes value must be >= 1"
|
|
@@ -207,30 +207,29 @@ class XarrayReader(BaseReader):
|
|
|
207
207
|
f"Invalid indexes {indexes} for array of shape {da.shape}"
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
return da,
|
|
210
|
+
return da, band_descriptions
|
|
211
211
|
|
|
212
212
|
indexes = [idx - 1 for idx in indexes]
|
|
213
213
|
|
|
214
214
|
da = da[indexes]
|
|
215
215
|
band_descriptions = [band_descriptions[idx] for idx in indexes]
|
|
216
|
-
band_names = [band_names[idx] for idx in indexes]
|
|
217
216
|
|
|
218
|
-
return da,
|
|
217
|
+
return da, band_descriptions
|
|
219
218
|
|
|
220
219
|
def statistics(
|
|
221
220
|
self,
|
|
222
221
|
categorical: bool = False,
|
|
223
|
-
categories:
|
|
224
|
-
percentiles:
|
|
225
|
-
hist_options:
|
|
222
|
+
categories: list[float] | None = None,
|
|
223
|
+
percentiles: list[int] | None = None,
|
|
224
|
+
hist_options: dict | None = None,
|
|
226
225
|
nodata: NoData | None = None,
|
|
227
226
|
indexes: Indexes | None = None,
|
|
228
227
|
**kwargs: Any,
|
|
229
|
-
) ->
|
|
228
|
+
) -> dict[str, BandStatistics]:
|
|
230
229
|
"""Return statistics from a dataset."""
|
|
231
230
|
hist_options = hist_options or {}
|
|
232
231
|
|
|
233
|
-
da,
|
|
232
|
+
da, band_descriptions = self._sel_indexes(indexes)
|
|
234
233
|
|
|
235
234
|
if nodata is not None:
|
|
236
235
|
da = da.rio.write_nodata(nodata)
|
|
@@ -246,14 +245,20 @@ class XarrayReader(BaseReader):
|
|
|
246
245
|
**hist_options,
|
|
247
246
|
)
|
|
248
247
|
|
|
249
|
-
return {
|
|
248
|
+
return {
|
|
249
|
+
f"b{ix +1}": BandStatistics(
|
|
250
|
+
**val,
|
|
251
|
+
description=band_descriptions[ix],
|
|
252
|
+
)
|
|
253
|
+
for ix, val in enumerate(stats)
|
|
254
|
+
}
|
|
250
255
|
|
|
251
256
|
def tile(
|
|
252
257
|
self,
|
|
253
258
|
tile_x: int,
|
|
254
259
|
tile_y: int,
|
|
255
260
|
tile_z: int,
|
|
256
|
-
tilesize: int =
|
|
261
|
+
tilesize: int | None = None,
|
|
257
262
|
reproject_method: WarpResampling = "nearest",
|
|
258
263
|
auto_expand: bool = True,
|
|
259
264
|
nodata: NoData | None = None,
|
|
@@ -281,16 +286,22 @@ class XarrayReader(BaseReader):
|
|
|
281
286
|
f"Tile(x={tile_x}, y={tile_y}, z={tile_z}) is outside bounds"
|
|
282
287
|
)
|
|
283
288
|
|
|
289
|
+
matrix = self.tms.matrix(tile_z)
|
|
290
|
+
bbox = cast(
|
|
291
|
+
BBox,
|
|
292
|
+
self.tms.xy_bounds(Tile(x=tile_x, y=tile_y, z=tile_z)),
|
|
293
|
+
)
|
|
294
|
+
|
|
284
295
|
return self.part(
|
|
285
|
-
|
|
296
|
+
bbox,
|
|
286
297
|
dst_crs=self.tms.rasterio_crs,
|
|
287
298
|
bounds_crs=self.tms.rasterio_crs,
|
|
288
299
|
reproject_method=reproject_method,
|
|
289
300
|
auto_expand=auto_expand,
|
|
290
301
|
nodata=nodata,
|
|
291
302
|
indexes=indexes,
|
|
292
|
-
height=tilesize,
|
|
293
|
-
width=tilesize,
|
|
303
|
+
height=tilesize or matrix.tileHeight,
|
|
304
|
+
width=tilesize or matrix.tileWidth,
|
|
294
305
|
out_dtype=out_dtype,
|
|
295
306
|
**kwargs,
|
|
296
307
|
)
|
|
@@ -338,7 +349,7 @@ class XarrayReader(BaseReader):
|
|
|
338
349
|
|
|
339
350
|
dst_crs = dst_crs or bounds_crs
|
|
340
351
|
|
|
341
|
-
da,
|
|
352
|
+
da, band_descriptions = self._sel_indexes(indexes)
|
|
342
353
|
|
|
343
354
|
if nodata is not None:
|
|
344
355
|
da = da.rio.write_nodata(nodata)
|
|
@@ -448,7 +459,6 @@ class XarrayReader(BaseReader):
|
|
|
448
459
|
bounds=bbox,
|
|
449
460
|
crs=da.rio.crs,
|
|
450
461
|
dataset_statistics=stats,
|
|
451
|
-
band_names=band_names,
|
|
452
462
|
band_descriptions=band_descriptions,
|
|
453
463
|
nodata=da.rio.nodata,
|
|
454
464
|
)
|
|
@@ -488,7 +498,7 @@ class XarrayReader(BaseReader):
|
|
|
488
498
|
)
|
|
489
499
|
max_size = None
|
|
490
500
|
|
|
491
|
-
da,
|
|
501
|
+
da, band_descriptions = self._sel_indexes(indexes)
|
|
492
502
|
|
|
493
503
|
if da.nbytes > MAX_ARRAY_SIZE:
|
|
494
504
|
raise MaxArraySizeError(
|
|
@@ -578,7 +588,6 @@ class XarrayReader(BaseReader):
|
|
|
578
588
|
bounds=da.rio.bounds(),
|
|
579
589
|
crs=da.rio.crs,
|
|
580
590
|
dataset_statistics=stats,
|
|
581
|
-
band_names=band_names,
|
|
582
591
|
band_descriptions=band_descriptions,
|
|
583
592
|
nodata=da.rio.nodata,
|
|
584
593
|
)
|
|
@@ -628,7 +637,7 @@ class XarrayReader(BaseReader):
|
|
|
628
637
|
):
|
|
629
638
|
raise PointOutsideBounds("Point is outside dataset bounds")
|
|
630
639
|
|
|
631
|
-
da,
|
|
640
|
+
da, band_descriptions = self._sel_indexes(indexes)
|
|
632
641
|
|
|
633
642
|
if nodata is not None:
|
|
634
643
|
da = da.rio.write_nodata(nodata)
|
|
@@ -648,7 +657,6 @@ class XarrayReader(BaseReader):
|
|
|
648
657
|
arr,
|
|
649
658
|
coordinates=(lon, lat),
|
|
650
659
|
crs=coord_crs,
|
|
651
|
-
band_names=band_names,
|
|
652
660
|
band_descriptions=band_descriptions,
|
|
653
661
|
pixel_location=(x, y),
|
|
654
662
|
nodata=da.rio.nodata,
|
|
@@ -656,7 +664,7 @@ class XarrayReader(BaseReader):
|
|
|
656
664
|
|
|
657
665
|
def feature(
|
|
658
666
|
self,
|
|
659
|
-
shape:
|
|
667
|
+
shape: dict,
|
|
660
668
|
dst_crs: CRS | None = None,
|
|
661
669
|
shape_crs: CRS = WGS84_CRS,
|
|
662
670
|
reproject_method: WarpResampling = "nearest",
|
rio_tiler/models.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""rio-tiler models."""
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
+
import re
|
|
4
5
|
import warnings
|
|
5
|
-
from
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any, Literal, cast
|
|
6
8
|
|
|
7
9
|
import attr
|
|
8
10
|
import numpy
|
|
@@ -34,7 +36,6 @@ from rio_tiler.types import (
|
|
|
34
36
|
GDALColorMapType,
|
|
35
37
|
IntervalTuple,
|
|
36
38
|
NoData,
|
|
37
|
-
NumType,
|
|
38
39
|
RIOResampling,
|
|
39
40
|
WarpResampling,
|
|
40
41
|
)
|
|
@@ -58,14 +59,14 @@ class Bounds(BaseModel):
|
|
|
58
59
|
class Info(Bounds):
|
|
59
60
|
"""Dataset Info."""
|
|
60
61
|
|
|
61
|
-
band_metadata:
|
|
62
|
-
band_descriptions:
|
|
62
|
+
band_metadata: list[tuple[str, dict]]
|
|
63
|
+
band_descriptions: list[tuple[str, str]]
|
|
63
64
|
dtype: str
|
|
64
65
|
nodata_type: Literal["Alpha", "Mask", "Internal", "Nodata", "None"]
|
|
65
|
-
colorinterp:
|
|
66
|
-
scales:
|
|
67
|
-
offsets:
|
|
68
|
-
colormap:
|
|
66
|
+
colorinterp: list[str] | None = None
|
|
67
|
+
scales: list[float] | None = None
|
|
68
|
+
offsets: list[float] | None = None
|
|
69
|
+
colormap: GDALColorMapType | None = None
|
|
69
70
|
|
|
70
71
|
model_config = {"extra": "allow"}
|
|
71
72
|
|
|
@@ -83,15 +84,16 @@ class BandStatistics(BaseModel):
|
|
|
83
84
|
majority: float
|
|
84
85
|
minority: float
|
|
85
86
|
unique: float
|
|
86
|
-
histogram:
|
|
87
|
+
histogram: list[list[float | int]]
|
|
87
88
|
valid_percent: float
|
|
88
89
|
masked_pixels: float
|
|
89
90
|
valid_pixels: float
|
|
91
|
+
description: str
|
|
90
92
|
|
|
91
93
|
model_config = {"extra": "allow"}
|
|
92
94
|
|
|
93
95
|
|
|
94
|
-
def to_coordsbbox(bbox) ->
|
|
96
|
+
def to_coordsbbox(bbox) -> BoundingBox | None:
|
|
95
97
|
"""Convert bbox to CoordsBbox nameTuple."""
|
|
96
98
|
return BoundingBox(*bbox) if bbox else None
|
|
97
99
|
|
|
@@ -100,7 +102,7 @@ def rescale_image(
|
|
|
100
102
|
array: numpy.ma.MaskedArray,
|
|
101
103
|
in_range: Sequence[IntervalTuple],
|
|
102
104
|
out_range: Sequence[IntervalTuple] = ((0, 255),),
|
|
103
|
-
out_dtype:
|
|
105
|
+
out_dtype: str | numpy.number = "uint8",
|
|
104
106
|
) -> numpy.ma.MaskedArray:
|
|
105
107
|
"""Rescale image data in-place."""
|
|
106
108
|
if len(array.shape) < 3:
|
|
@@ -156,16 +158,16 @@ class PointData:
|
|
|
156
158
|
"""
|
|
157
159
|
|
|
158
160
|
array: numpy.ma.MaskedArray = attr.ib(converter=to_masked)
|
|
159
|
-
band_names:
|
|
160
|
-
band_descriptions:
|
|
161
|
-
coordinates:
|
|
162
|
-
crs:
|
|
163
|
-
assets:
|
|
164
|
-
metadata:
|
|
165
|
-
nodata:
|
|
166
|
-
scales:
|
|
167
|
-
offsets:
|
|
168
|
-
pixel_location:
|
|
161
|
+
band_names: list[str] = attr.ib(kw_only=True)
|
|
162
|
+
band_descriptions: list[str] = attr.ib(kw_only=True)
|
|
163
|
+
coordinates: tuple[float, float] | None = attr.ib(default=None, kw_only=True)
|
|
164
|
+
crs: CRS | None = attr.ib(default=None, kw_only=True)
|
|
165
|
+
assets: list | None = attr.ib(default=None, kw_only=True)
|
|
166
|
+
metadata: dict | None = attr.ib(factory=dict, kw_only=True)
|
|
167
|
+
nodata: NoData | None = attr.ib(default=None, kw_only=True)
|
|
168
|
+
scales: list[float | int] = attr.ib(kw_only=True)
|
|
169
|
+
offsets: list[float | int] = attr.ib(kw_only=True)
|
|
170
|
+
pixel_location: tuple[float | int, float | int] | None = attr.ib(
|
|
169
171
|
default=None, kw_only=True
|
|
170
172
|
)
|
|
171
173
|
|
|
@@ -187,7 +189,7 @@ class PointData:
|
|
|
187
189
|
|
|
188
190
|
@band_descriptions.default
|
|
189
191
|
def _default_band_descriptions(self):
|
|
190
|
-
return ["" for ix in range(self.count)]
|
|
192
|
+
return ["" or f"b{ix + 1}" for ix in range(self.count)]
|
|
191
193
|
|
|
192
194
|
@scales.default
|
|
193
195
|
def _default_scales(self):
|
|
@@ -290,14 +292,20 @@ class PointData:
|
|
|
290
292
|
# Using numexpr do not preserve mask info
|
|
291
293
|
data.mask = False
|
|
292
294
|
|
|
293
|
-
|
|
295
|
+
mapexpr = {
|
|
296
|
+
self.band_names[idx]: desc for idx, desc in enumerate(self.band_descriptions)
|
|
297
|
+
}
|
|
298
|
+
_re = re.compile(r"\bb[0-9A-Z]+\b")
|
|
299
|
+
band_descriptions = [
|
|
300
|
+
_re.sub(lambda x: mapexpr[x.group()], block) for block in blocks
|
|
301
|
+
]
|
|
294
302
|
|
|
295
303
|
return PointData(
|
|
296
304
|
data,
|
|
297
305
|
assets=self.assets,
|
|
298
306
|
crs=self.crs,
|
|
299
307
|
coordinates=self.coordinates,
|
|
300
|
-
|
|
308
|
+
band_descriptions=band_descriptions,
|
|
301
309
|
metadata=self.metadata,
|
|
302
310
|
pixel_location=self.pixel_location,
|
|
303
311
|
)
|
|
@@ -340,22 +348,22 @@ class ImageData:
|
|
|
340
348
|
"""
|
|
341
349
|
|
|
342
350
|
array: numpy.ma.MaskedArray = attr.ib(converter=masked_and_3d)
|
|
343
|
-
assets:
|
|
344
|
-
bounds:
|
|
351
|
+
assets: list | None = attr.ib(default=None, kw_only=True)
|
|
352
|
+
bounds: BoundingBox | None = attr.ib(
|
|
345
353
|
default=None, converter=to_coordsbbox, kw_only=True
|
|
346
354
|
)
|
|
347
|
-
crs:
|
|
348
|
-
metadata:
|
|
349
|
-
nodata:
|
|
350
|
-
scales:
|
|
351
|
-
offsets:
|
|
352
|
-
band_names:
|
|
353
|
-
band_descriptions:
|
|
354
|
-
dataset_statistics:
|
|
355
|
+
crs: CRS | None = attr.ib(default=None, kw_only=True)
|
|
356
|
+
metadata: dict | None = attr.ib(factory=dict, kw_only=True)
|
|
357
|
+
nodata: NoData | None = attr.ib(default=None, kw_only=True)
|
|
358
|
+
scales: list[float | int] = attr.ib(kw_only=True)
|
|
359
|
+
offsets: list[float | int] = attr.ib(kw_only=True)
|
|
360
|
+
band_names: list[str] = attr.ib(kw_only=True)
|
|
361
|
+
band_descriptions: list[str] = attr.ib(kw_only=True)
|
|
362
|
+
dataset_statistics: Sequence[tuple[float, float]] | None = attr.ib(
|
|
355
363
|
default=None, kw_only=True
|
|
356
364
|
)
|
|
357
|
-
cutline_mask:
|
|
358
|
-
alpha_mask:
|
|
365
|
+
cutline_mask: numpy.ndarray | None = attr.ib(default=None)
|
|
366
|
+
alpha_mask: numpy.ndarray | None = attr.ib(default=None)
|
|
359
367
|
|
|
360
368
|
@band_names.default
|
|
361
369
|
def _default_band_names(self):
|
|
@@ -363,7 +371,7 @@ class ImageData:
|
|
|
363
371
|
|
|
364
372
|
@band_descriptions.default
|
|
365
373
|
def _default_band_descriptions(self):
|
|
366
|
-
return ["" for ix in range(self.count)]
|
|
374
|
+
return ["" or f"b{ix + 1}" for ix in range(self.count)]
|
|
367
375
|
|
|
368
376
|
@scales.default
|
|
369
377
|
def _default_scales(self):
|
|
@@ -596,7 +604,7 @@ class ImageData:
|
|
|
596
604
|
self,
|
|
597
605
|
in_range: Sequence[IntervalTuple],
|
|
598
606
|
out_range: Sequence[IntervalTuple] = ((0, 255),),
|
|
599
|
-
out_dtype:
|
|
607
|
+
out_dtype: str | numpy.number = "uint8",
|
|
600
608
|
) -> Self:
|
|
601
609
|
"""Rescale data in place."""
|
|
602
610
|
self.array = rescale_image(
|
|
@@ -618,7 +626,7 @@ class ImageData:
|
|
|
618
626
|
|
|
619
627
|
return self
|
|
620
628
|
|
|
621
|
-
def apply_color_formula(self, color_formula:
|
|
629
|
+
def apply_color_formula(self, color_formula: str | None) -> Self:
|
|
622
630
|
"""Apply color-operations formula in place."""
|
|
623
631
|
out = self.array.data
|
|
624
632
|
out[out < 0] = 0
|
|
@@ -691,14 +699,20 @@ class ImageData:
|
|
|
691
699
|
# NOTE: We use dataset mask when mixing bands
|
|
692
700
|
data.mask = numpy.logical_or.reduce(self.array.mask)
|
|
693
701
|
|
|
694
|
-
|
|
702
|
+
mapexpr = {
|
|
703
|
+
self.band_names[idx]: desc for idx, desc in enumerate(self.band_descriptions)
|
|
704
|
+
}
|
|
705
|
+
_re = re.compile(r"\bb[0-9A-Z]+\b")
|
|
706
|
+
band_descriptions = [
|
|
707
|
+
_re.sub(lambda x: mapexpr[x.group()], block) for block in blocks
|
|
708
|
+
]
|
|
695
709
|
|
|
696
710
|
return ImageData(
|
|
697
711
|
data,
|
|
698
712
|
assets=self.assets,
|
|
699
713
|
crs=self.crs,
|
|
700
714
|
bounds=self.bounds,
|
|
701
|
-
|
|
715
|
+
band_descriptions=band_descriptions,
|
|
702
716
|
metadata=self.metadata,
|
|
703
717
|
dataset_statistics=stats,
|
|
704
718
|
alpha_mask=self.alpha_mask,
|
|
@@ -759,9 +773,9 @@ class ImageData:
|
|
|
759
773
|
|
|
760
774
|
def post_process(
|
|
761
775
|
self,
|
|
762
|
-
in_range:
|
|
763
|
-
out_dtype:
|
|
764
|
-
color_formula:
|
|
776
|
+
in_range: Sequence[IntervalTuple] | None = None,
|
|
777
|
+
out_dtype: str | numpy.number = "uint8",
|
|
778
|
+
color_formula: str | None = None,
|
|
765
779
|
**kwargs: Any,
|
|
766
780
|
) -> "ImageData":
|
|
767
781
|
"""Post-process image data.
|
|
@@ -803,7 +817,7 @@ class ImageData:
|
|
|
803
817
|
self,
|
|
804
818
|
add_mask: bool = True,
|
|
805
819
|
img_format: str = "PNG",
|
|
806
|
-
colormap:
|
|
820
|
+
colormap: ColorMapType | None = None,
|
|
807
821
|
**kwargs,
|
|
808
822
|
) -> bytes:
|
|
809
823
|
"""Render data to image blob.
|
|
@@ -899,11 +913,11 @@ class ImageData:
|
|
|
899
913
|
def statistics(
|
|
900
914
|
self,
|
|
901
915
|
categorical: bool = False,
|
|
902
|
-
categories:
|
|
903
|
-
percentiles:
|
|
904
|
-
hist_options:
|
|
905
|
-
coverage:
|
|
906
|
-
) ->
|
|
916
|
+
categories: list[float] | None = None,
|
|
917
|
+
percentiles: list[int] | None = None,
|
|
918
|
+
hist_options: dict | None = None,
|
|
919
|
+
coverage: numpy.ndarray | None = None,
|
|
920
|
+
) -> dict[str, BandStatistics]:
|
|
907
921
|
"""Return statistics from ImageData."""
|
|
908
922
|
hist_options = hist_options or {}
|
|
909
923
|
|
|
@@ -917,13 +931,16 @@ class ImageData:
|
|
|
917
931
|
)
|
|
918
932
|
|
|
919
933
|
return {
|
|
920
|
-
f"{self.band_names[ix]}": BandStatistics(
|
|
934
|
+
f"{self.band_names[ix]}": BandStatistics(
|
|
935
|
+
**stats[ix],
|
|
936
|
+
description=self.band_descriptions[ix],
|
|
937
|
+
)
|
|
921
938
|
for ix in range(len(stats))
|
|
922
939
|
}
|
|
923
940
|
|
|
924
941
|
def get_coverage_array(
|
|
925
942
|
self,
|
|
926
|
-
shape:
|
|
943
|
+
shape: dict,
|
|
927
944
|
shape_crs: CRS = WGS84_CRS,
|
|
928
945
|
cover_scale: int = 10,
|
|
929
946
|
) -> NDArray[numpy.floating]:
|
|
@@ -972,7 +989,7 @@ class ImageData:
|
|
|
972
989
|
def reproject(
|
|
973
990
|
self,
|
|
974
991
|
dst_crs: CRS,
|
|
975
|
-
resolution:
|
|
992
|
+
resolution: tuple[float, float] | None = None,
|
|
976
993
|
reproject_method: WarpResampling = "nearest",
|
|
977
994
|
) -> "ImageData":
|
|
978
995
|
"""Reproject data and mask."""
|
rio_tiler/mosaic/backend.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import attr
|
|
8
8
|
from morecantile import TileMatrixSet
|
|
@@ -51,7 +51,7 @@ class BaseBackend(BaseReader):
|
|
|
51
51
|
input: str = attr.ib()
|
|
52
52
|
tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
|
|
53
53
|
|
|
54
|
-
reader:
|
|
54
|
+
reader: type[BaseReader] | type[MultiBaseReader] | type[MultiBandReader] = attr.ib(
|
|
55
55
|
default=Reader
|
|
56
56
|
)
|
|
57
57
|
reader_options: dict = attr.ib(factory=dict)
|
rio_tiler/mosaic/methods/base.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
import numpy
|
|
8
7
|
|
|
@@ -11,9 +10,9 @@ import numpy
|
|
|
11
10
|
class MosaicMethodBase(abc.ABC):
|
|
12
11
|
"""Abstract base class for rio-tiler-mosaic methods objects."""
|
|
13
12
|
|
|
14
|
-
mosaic:
|
|
13
|
+
mosaic: numpy.ma.MaskedArray | None = field(default=None, init=False)
|
|
15
14
|
exit_when_filled: bool = field(default=False, init=False)
|
|
16
|
-
cutline_mask:
|
|
15
|
+
cutline_mask: numpy.ndarray | None = field(default=None, init=False)
|
|
17
16
|
width: int = field(init=False)
|
|
18
17
|
height: int = field(init=False)
|
|
19
18
|
count: int = field(init=False)
|
|
@@ -42,7 +41,7 @@ class MosaicMethodBase(abc.ABC):
|
|
|
42
41
|
return False
|
|
43
42
|
|
|
44
43
|
@property
|
|
45
|
-
def data(self) ->
|
|
44
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
46
45
|
"""Return data."""
|
|
47
46
|
return self.mosaic
|
|
48
47
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""rio_tiler.mosaic.methods.defaults: default mosaic filling methods."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import List, Optional
|
|
5
4
|
|
|
6
5
|
import numpy
|
|
7
6
|
|
|
@@ -18,7 +17,7 @@ class FirstMethod(MosaicMethodBase):
|
|
|
18
17
|
"""Mosaic Method repr."""
|
|
19
18
|
return "<Mosaic: FirstMethod>"
|
|
20
19
|
|
|
21
|
-
def feed(self, array:
|
|
20
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
22
21
|
"""Add data to the mosaic array."""
|
|
23
22
|
if self.mosaic is None:
|
|
24
23
|
self.mosaic = array
|
|
@@ -39,7 +38,7 @@ class HighestMethod(MosaicMethodBase):
|
|
|
39
38
|
"""Mosaic Method repr."""
|
|
40
39
|
return "<Mosaic: HighestMethod>"
|
|
41
40
|
|
|
42
|
-
def feed(self, array:
|
|
41
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
43
42
|
"""Add data to the mosaic array."""
|
|
44
43
|
if self.mosaic is None:
|
|
45
44
|
self.mosaic = array
|
|
@@ -63,7 +62,7 @@ class LowestMethod(MosaicMethodBase):
|
|
|
63
62
|
"""Mosaic Method repr."""
|
|
64
63
|
return "<Mosaic: LowestMethod>"
|
|
65
64
|
|
|
66
|
-
def feed(self, array:
|
|
65
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
67
66
|
"""Add data to the mosaic array."""
|
|
68
67
|
if self.mosaic is None:
|
|
69
68
|
self.mosaic = array
|
|
@@ -84,14 +83,14 @@ class MeanMethod(MosaicMethodBase):
|
|
|
84
83
|
"""Stack the arrays and return the Mean pixel value."""
|
|
85
84
|
|
|
86
85
|
enforce_data_type: bool = True
|
|
87
|
-
stack:
|
|
86
|
+
stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
|
|
88
87
|
|
|
89
88
|
def __repr__(self):
|
|
90
89
|
"""Mosaic Method repr."""
|
|
91
90
|
return "<Mosaic: MeanMethod>"
|
|
92
91
|
|
|
93
92
|
@property
|
|
94
|
-
def data(self) ->
|
|
93
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
95
94
|
"""Return Mean of the data stack."""
|
|
96
95
|
if self.stack:
|
|
97
96
|
array = numpy.ma.mean(numpy.ma.stack(self.stack, axis=0), axis=0)
|
|
@@ -112,14 +111,14 @@ class MedianMethod(MosaicMethodBase):
|
|
|
112
111
|
"""Stack the arrays and return the Median pixel value."""
|
|
113
112
|
|
|
114
113
|
enforce_data_type: bool = True
|
|
115
|
-
stack:
|
|
114
|
+
stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
|
|
116
115
|
|
|
117
116
|
def __repr__(self):
|
|
118
117
|
"""Mosaic Method repr."""
|
|
119
118
|
return "<Mosaic: MedianMethod>"
|
|
120
119
|
|
|
121
120
|
@property
|
|
122
|
-
def data(self) ->
|
|
121
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
123
122
|
"""Return Median of the data stack."""
|
|
124
123
|
if self.stack:
|
|
125
124
|
array = numpy.ma.median(numpy.ma.stack(self.stack, axis=0), axis=0)
|
|
@@ -130,7 +129,7 @@ class MedianMethod(MosaicMethodBase):
|
|
|
130
129
|
|
|
131
130
|
return None
|
|
132
131
|
|
|
133
|
-
def feed(self, array:
|
|
132
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
134
133
|
"""Add array to the stack."""
|
|
135
134
|
self.stack.append(array)
|
|
136
135
|
|
|
@@ -139,21 +138,21 @@ class MedianMethod(MosaicMethodBase):
|
|
|
139
138
|
class StdevMethod(MosaicMethodBase):
|
|
140
139
|
"""Stack the arrays and return the Standard Deviation value."""
|
|
141
140
|
|
|
142
|
-
stack:
|
|
141
|
+
stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
|
|
143
142
|
|
|
144
143
|
def __repr__(self):
|
|
145
144
|
"""Mosaic Method repr."""
|
|
146
145
|
return "<Mosaic: StdevMethod>"
|
|
147
146
|
|
|
148
147
|
@property
|
|
149
|
-
def data(self) ->
|
|
148
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
150
149
|
"""Return STDDEV of the data stack."""
|
|
151
150
|
if self.stack:
|
|
152
151
|
return numpy.ma.std(numpy.ma.stack(self.stack, axis=0), axis=0)
|
|
153
152
|
|
|
154
153
|
return None
|
|
155
154
|
|
|
156
|
-
def feed(self, array:
|
|
155
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
157
156
|
"""Add array to the stack."""
|
|
158
157
|
self.stack.append(array)
|
|
159
158
|
|
|
@@ -167,14 +166,14 @@ class LastBandHighMethod(MosaicMethodBase):
|
|
|
167
166
|
return "<Mosaic: LastBandHighMethod>"
|
|
168
167
|
|
|
169
168
|
@property
|
|
170
|
-
def data(self) ->
|
|
169
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
171
170
|
"""Return data."""
|
|
172
171
|
if self.mosaic is not None:
|
|
173
172
|
return self.mosaic[:-1].copy()
|
|
174
173
|
|
|
175
174
|
return None
|
|
176
175
|
|
|
177
|
-
def feed(self, array:
|
|
176
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
178
177
|
"""Add data to the mosaic array."""
|
|
179
178
|
if self.mosaic is None:
|
|
180
179
|
self.mosaic = array
|
|
@@ -199,14 +198,14 @@ class LastBandLowMethod(MosaicMethodBase):
|
|
|
199
198
|
return "<Mosaic: LastBandLowMethod>"
|
|
200
199
|
|
|
201
200
|
@property
|
|
202
|
-
def data(self) ->
|
|
201
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
203
202
|
"""Return data."""
|
|
204
203
|
if self.mosaic is not None:
|
|
205
204
|
return self.mosaic[:-1].copy()
|
|
206
205
|
|
|
207
206
|
return None
|
|
208
207
|
|
|
209
|
-
def feed(self, array:
|
|
208
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
210
209
|
"""Add data to the mosaic array."""
|
|
211
210
|
if self.mosaic is None:
|
|
212
211
|
self.mosaic = array
|
|
@@ -226,14 +225,14 @@ class LastBandLowMethod(MosaicMethodBase):
|
|
|
226
225
|
class CountMethod(MosaicMethodBase):
|
|
227
226
|
"""Stack the arrays and return the valid pixel count."""
|
|
228
227
|
|
|
229
|
-
stack:
|
|
228
|
+
stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
|
|
230
229
|
|
|
231
230
|
def __repr__(self):
|
|
232
231
|
"""Mosaic Method repr."""
|
|
233
232
|
return "<Mosaic: CountMethod>"
|
|
234
233
|
|
|
235
234
|
@property
|
|
236
|
-
def data(self) ->
|
|
235
|
+
def data(self) -> numpy.ma.MaskedArray | None:
|
|
237
236
|
"""Return valid data count of the data stack."""
|
|
238
237
|
if self.stack:
|
|
239
238
|
data = numpy.ma.count(numpy.ma.stack(self.stack, axis=0), axis=0)
|
|
@@ -254,6 +253,6 @@ class CountMethod(MosaicMethodBase):
|
|
|
254
253
|
|
|
255
254
|
return None
|
|
256
255
|
|
|
257
|
-
def feed(self, array:
|
|
256
|
+
def feed(self, array: numpy.ma.MaskedArray | None):
|
|
258
257
|
"""Add array to the stack."""
|
|
259
258
|
self.stack.append(array)
|