rio-tiler 7.9.1__py3-none-any.whl → 8.0.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.
rio_tiler/io/xarray.py CHANGED
@@ -3,8 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import math
6
+ import os
6
7
  import warnings
7
- from typing import Any, Dict, List, Optional, Tuple
8
+ from typing import Any, Dict, List, Tuple
8
9
 
9
10
  import attr
10
11
  import numpy
@@ -23,6 +24,7 @@ from rasterio.warp import transform_bounds, transform_geom
23
24
  from rio_tiler.constants import WEB_MERCATOR_CRS, WEB_MERCATOR_TMS, WGS84_CRS
24
25
  from rio_tiler.errors import (
25
26
  InvalidGeographicBounds,
27
+ MaxArraySizeError,
26
28
  MissingCRS,
27
29
  PointOutsideBounds,
28
30
  TileOutsideBounds,
@@ -50,6 +52,9 @@ except ImportError: # pragma: nocover
50
52
  rioxarray = None # type: ignore
51
53
 
52
54
 
55
+ MAX_ARRAY_SIZE = os.environ.get("RIO_TILER_MAX_ARRAY_SIZE", 1_000_000_000) # 1Gb
56
+
57
+
53
58
  @attr.s
54
59
  class XarrayReader(BaseReader):
55
60
  """Xarray Reader.
@@ -133,7 +138,7 @@ class XarrayReader(BaseReader):
133
138
  return self._maxzoom
134
139
 
135
140
  @property
136
- def band_names(self) -> List[str]:
141
+ def band_descriptions(self) -> List[str]:
137
142
  """
138
143
  Return list of `band descriptions` in DataArray.
139
144
 
@@ -155,7 +160,7 @@ class XarrayReader(BaseReader):
155
160
  if coords_name:
156
161
  return [str(self.input.coords[coords_name[0]].data)]
157
162
 
158
- return [self.input.name or "array"]
163
+ return [self.input.name or ""]
159
164
 
160
165
  return [str(band) for d in self._dims for band in self.input[d].values]
161
166
 
@@ -168,7 +173,7 @@ class XarrayReader(BaseReader):
168
173
  "crs": CRS_to_uri(self.crs) or self.crs.to_wkt(),
169
174
  "band_metadata": [(f"b{ix}", v) for ix, v in enumerate(metadata, 1)],
170
175
  "band_descriptions": [
171
- (f"b{ix}", v) for ix, v in enumerate(self.band_names, 1)
176
+ (f"b{ix}", v) for ix, v in enumerate(self.band_descriptions, 1)
172
177
  ],
173
178
  "dtype": str(self.input.dtype),
174
179
  "nodata_type": "Nodata" if self.input.rio.nodata is not None else "None",
@@ -186,11 +191,14 @@ class XarrayReader(BaseReader):
186
191
  return Info(**meta)
187
192
 
188
193
  def _sel_indexes(
189
- self, indexes: Optional[Indexes] = None
190
- ) -> Tuple[xarray.DataArray, List[str]]:
194
+ self,
195
+ indexes: Indexes | None = None,
196
+ ) -> Tuple[xarray.DataArray, List[str], List[str]]:
191
197
  """Select `band` indexes in DataArray."""
192
198
  da = self.input
193
- band_names = self.band_names
199
+ band_descriptions = self.band_descriptions
200
+ band_names = [f"b{ix + 1}" for ix in range(self.input.rio.count)]
201
+
194
202
  if indexes := cast_to_sequence(indexes):
195
203
  assert all(v > 0 for v in indexes), "Indexes value must be >= 1"
196
204
  if da.ndim == 2:
@@ -199,28 +207,30 @@ class XarrayReader(BaseReader):
199
207
  f"Invalid indexes {indexes} for array of shape {da.shape}"
200
208
  )
201
209
 
202
- return da, band_names
210
+ return da, band_names, band_descriptions
203
211
 
204
212
  indexes = [idx - 1 for idx in indexes]
213
+
205
214
  da = da[indexes]
206
- band_names = [self.band_names[idx] for idx in indexes]
215
+ band_descriptions = [band_descriptions[idx] for idx in indexes]
216
+ band_names = [band_names[idx] for idx in indexes]
207
217
 
208
- return da, band_names
218
+ return da, band_names, band_descriptions
209
219
 
210
220
  def statistics(
211
221
  self,
212
222
  categorical: bool = False,
213
- categories: Optional[List[float]] = None,
214
- percentiles: Optional[List[int]] = None,
215
- hist_options: Optional[Dict] = None,
216
- nodata: Optional[NoData] = None,
217
- indexes: Optional[Indexes] = None,
223
+ categories: List[float] | None = None,
224
+ percentiles: List[int] | None = None,
225
+ hist_options: Dict | None = None,
226
+ nodata: NoData | None = None,
227
+ indexes: Indexes | None = None,
218
228
  **kwargs: Any,
219
229
  ) -> Dict[str, BandStatistics]:
220
230
  """Return statistics from a dataset."""
221
231
  hist_options = hist_options or {}
222
232
 
223
- da, band_names = self._sel_indexes(indexes)
233
+ da, band_names, _ = self._sel_indexes(indexes)
224
234
 
225
235
  if nodata is not None:
226
236
  da = da.rio.write_nodata(nodata)
@@ -246,8 +256,8 @@ class XarrayReader(BaseReader):
246
256
  tilesize: int = 256,
247
257
  reproject_method: WarpResampling = "nearest",
248
258
  auto_expand: bool = True,
249
- nodata: Optional[NoData] = None,
250
- indexes: Optional[Indexes] = None,
259
+ nodata: NoData | None = None,
260
+ indexes: Indexes | None = None,
251
261
  out_dtype: str | numpy.dtype | None = None,
252
262
  **kwargs: Any,
253
263
  ) -> ImageData:
@@ -291,15 +301,15 @@ class XarrayReader(BaseReader):
291
301
  def part( # noqa: C901
292
302
  self,
293
303
  bbox: BBox,
294
- dst_crs: Optional[CRS] = None,
304
+ dst_crs: CRS | None = None,
295
305
  bounds_crs: CRS = WGS84_CRS,
296
306
  reproject_method: WarpResampling = "nearest",
297
307
  auto_expand: bool = True,
298
- nodata: Optional[NoData] = None,
299
- indexes: Optional[Indexes] = None,
300
- max_size: Optional[int] = None,
301
- height: Optional[int] = None,
302
- width: Optional[int] = None,
308
+ nodata: NoData | None = None,
309
+ indexes: Indexes | None = None,
310
+ max_size: int | None = None,
311
+ height: int | None = None,
312
+ width: int | None = None,
303
313
  resampling_method: RIOResampling = "nearest",
304
314
  out_dtype: str | numpy.dtype | None = None,
305
315
  **kwargs: Any,
@@ -331,7 +341,7 @@ class XarrayReader(BaseReader):
331
341
 
332
342
  dst_crs = dst_crs or bounds_crs
333
343
 
334
- da, band_names = self._sel_indexes(indexes)
344
+ da, band_names, band_descriptions = self._sel_indexes(indexes)
335
345
 
336
346
  if nodata is not None:
337
347
  da = da.rio.write_nodata(nodata)
@@ -348,6 +358,11 @@ class XarrayReader(BaseReader):
348
358
  auto_expand=auto_expand,
349
359
  )
350
360
 
361
+ if da.nbytes > MAX_ARRAY_SIZE:
362
+ raise MaxArraySizeError(
363
+ f"Maximum array limit {MAX_ARRAY_SIZE} reached, trying to put DataArray of {da.shape} in memory."
364
+ )
365
+
351
366
  src_width = da.rio.width
352
367
  src_height = da.rio.height
353
368
  src_bounds = list(da.rio.bounds())
@@ -373,17 +388,18 @@ class XarrayReader(BaseReader):
373
388
  )
374
389
  src_bounds[1] = max(src_bounds[1], -85.06)
375
390
  src_bounds[3] = min(src_bounds[3], 85.06)
376
- w = windows.from_bounds(*src_bounds, transform=src_transform)
377
- src_height = round(w.height)
378
- src_width = round(w.width)
379
391
 
380
- # South->North
392
+ # North->South
381
393
  if src_transform.e > 0:
382
394
  src_bounds = [src_bounds[0], src_bounds[3], src_bounds[2], src_bounds[1]]
383
395
  # West->East
384
396
  if src_transform.a < 0:
385
397
  src_bounds = [src_bounds[2], src_bounds[1], src_bounds[1], src_bounds[3]]
386
398
 
399
+ w = windows.from_bounds(*src_bounds, transform=src_transform)
400
+ src_height = round(w.height)
401
+ src_width = round(w.width)
402
+
387
403
  if dst_crs != self.crs:
388
404
  # transform of the reprojected dataset
389
405
  dst_transform, _, _ = calculate_default_transform(
@@ -421,45 +437,33 @@ class XarrayReader(BaseReader):
421
437
  )
422
438
 
423
439
  arr = da.to_masked_array()
440
+ arr.mask |= arr.data == da.rio.nodata
424
441
  if out_dtype:
425
442
  arr = arr.astype(out_dtype)
426
- arr.mask |= arr.data == da.rio.nodata
427
443
 
428
444
  output_bounds = da.rio._unordered_bounds()
429
445
  if output_bounds[1] > output_bounds[3] and da.rio.transform().e > 0:
430
446
  yaxis = self.input.dims.index(self.input.rio.y_dim)
431
447
  arr = numpy.flip(arr, axis=yaxis)
432
448
 
433
- img = ImageData(
449
+ return ImageData(
434
450
  arr,
435
451
  bounds=bbox,
436
452
  crs=da.rio.crs,
437
453
  dataset_statistics=stats,
438
454
  band_names=band_names,
455
+ band_descriptions=band_descriptions,
456
+ nodata=da.rio.nodata,
439
457
  )
440
458
 
441
- output_height = height or img.height
442
- output_width = width or img.width
443
- if max_size and not (width and height):
444
- output_height, output_width = _get_width_height(
445
- max_size, img.height, img.width
446
- )
447
-
448
- if output_height != img.height or output_width != img.width:
449
- img = img.resize(
450
- output_height, output_width, resampling_method=resampling_method
451
- )
452
-
453
- return img
454
-
455
- def preview(
459
+ def preview( # noqa: C901
456
460
  self,
457
461
  max_size: int = 1024,
458
- height: Optional[int] = None,
459
- width: Optional[int] = None,
460
- nodata: Optional[NoData] = None,
461
- indexes: Optional[Indexes] = None,
462
- dst_crs: Optional[CRS] = None,
462
+ height: int | None = None,
463
+ width: int | None = None,
464
+ nodata: NoData | None = None,
465
+ indexes: Indexes | None = None,
466
+ dst_crs: CRS | None = None,
463
467
  reproject_method: WarpResampling = "nearest",
464
468
  resampling_method: RIOResampling = "nearest",
465
469
  out_dtype: str | numpy.dtype | None = None,
@@ -487,7 +491,12 @@ class XarrayReader(BaseReader):
487
491
  )
488
492
  max_size = None
489
493
 
490
- da, band_names = self._sel_indexes(indexes)
494
+ da, band_names, band_descriptions = self._sel_indexes(indexes)
495
+
496
+ if da.nbytes > MAX_ARRAY_SIZE:
497
+ raise MaxArraySizeError(
498
+ f"Maximum array limit {MAX_ARRAY_SIZE} reached, trying to put DataArray of {da.shape} in memory."
499
+ )
491
500
 
492
501
  if nodata is not None:
493
502
  da = da.rio.write_nodata(nodata)
@@ -573,6 +582,8 @@ class XarrayReader(BaseReader):
573
582
  crs=da.rio.crs,
574
583
  dataset_statistics=stats,
575
584
  band_names=band_names,
585
+ band_descriptions=band_descriptions,
586
+ nodata=da.rio.nodata,
576
587
  )
577
588
 
578
589
  if max_size:
@@ -595,8 +606,8 @@ class XarrayReader(BaseReader):
595
606
  lon: float,
596
607
  lat: float,
597
608
  coord_crs: CRS = WGS84_CRS,
598
- nodata: Optional[NoData] = None,
599
- indexes: Optional[Indexes] = None,
609
+ nodata: NoData | None = None,
610
+ indexes: Indexes | None = None,
600
611
  out_dtype: str | numpy.dtype | None = None,
601
612
  **kwargs: Any,
602
613
  ) -> PointData:
@@ -620,7 +631,7 @@ class XarrayReader(BaseReader):
620
631
  ):
621
632
  raise PointOutsideBounds("Point is outside dataset bounds")
622
633
 
623
- da, band_names = self._sel_indexes(indexes)
634
+ da, band_names, band_descriptions = self._sel_indexes(indexes)
624
635
 
625
636
  if nodata is not None:
626
637
  da = da.rio.write_nodata(nodata)
@@ -641,21 +652,23 @@ class XarrayReader(BaseReader):
641
652
  coordinates=(lon, lat),
642
653
  crs=coord_crs,
643
654
  band_names=band_names,
655
+ band_descriptions=band_descriptions,
644
656
  pixel_location=(x, y),
657
+ nodata=da.rio.nodata,
645
658
  )
646
659
 
647
660
  def feature(
648
661
  self,
649
662
  shape: Dict,
650
- dst_crs: Optional[CRS] = None,
663
+ dst_crs: CRS | None = None,
651
664
  shape_crs: CRS = WGS84_CRS,
652
665
  reproject_method: WarpResampling = "nearest",
653
666
  auto_expand: bool = True,
654
- nodata: Optional[NoData] = None,
655
- indexes: Optional[Indexes] = None,
656
- max_size: Optional[int] = None,
657
- height: Optional[int] = None,
658
- width: Optional[int] = None,
667
+ nodata: NoData | None = None,
668
+ indexes: Indexes | None = None,
669
+ max_size: int | None = None,
670
+ height: int | None = None,
671
+ width: int | None = None,
659
672
  resampling_method: RIOResampling = "nearest",
660
673
  out_dtype: str | numpy.dtype | None = None,
661
674
  **kwargs: Any,