mapchete-eo 2026.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.
Files changed (89) hide show
  1. mapchete_eo/__init__.py +1 -0
  2. mapchete_eo/array/__init__.py +0 -0
  3. mapchete_eo/array/buffer.py +16 -0
  4. mapchete_eo/array/color.py +29 -0
  5. mapchete_eo/array/convert.py +163 -0
  6. mapchete_eo/base.py +653 -0
  7. mapchete_eo/blacklist.txt +175 -0
  8. mapchete_eo/cli/__init__.py +30 -0
  9. mapchete_eo/cli/bounds.py +22 -0
  10. mapchete_eo/cli/options_arguments.py +227 -0
  11. mapchete_eo/cli/s2_brdf.py +77 -0
  12. mapchete_eo/cli/s2_cat_results.py +130 -0
  13. mapchete_eo/cli/s2_find_broken_products.py +77 -0
  14. mapchete_eo/cli/s2_jp2_static_catalog.py +166 -0
  15. mapchete_eo/cli/s2_mask.py +71 -0
  16. mapchete_eo/cli/s2_mgrs.py +45 -0
  17. mapchete_eo/cli/s2_rgb.py +114 -0
  18. mapchete_eo/cli/s2_verify.py +129 -0
  19. mapchete_eo/cli/static_catalog.py +82 -0
  20. mapchete_eo/eostac.py +30 -0
  21. mapchete_eo/exceptions.py +87 -0
  22. mapchete_eo/image_operations/__init__.py +12 -0
  23. mapchete_eo/image_operations/blend_functions.py +579 -0
  24. mapchete_eo/image_operations/color_correction.py +136 -0
  25. mapchete_eo/image_operations/compositing.py +266 -0
  26. mapchete_eo/image_operations/dtype_scale.py +43 -0
  27. mapchete_eo/image_operations/fillnodata.py +130 -0
  28. mapchete_eo/image_operations/filters.py +319 -0
  29. mapchete_eo/image_operations/linear_normalization.py +81 -0
  30. mapchete_eo/image_operations/sigmoidal.py +114 -0
  31. mapchete_eo/io/__init__.py +37 -0
  32. mapchete_eo/io/assets.py +496 -0
  33. mapchete_eo/io/items.py +162 -0
  34. mapchete_eo/io/levelled_cubes.py +259 -0
  35. mapchete_eo/io/path.py +155 -0
  36. mapchete_eo/io/products.py +423 -0
  37. mapchete_eo/io/profiles.py +45 -0
  38. mapchete_eo/platforms/sentinel2/__init__.py +17 -0
  39. mapchete_eo/platforms/sentinel2/_mapper_registry.py +89 -0
  40. mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +104 -0
  41. mapchete_eo/platforms/sentinel2/brdf/__init__.py +8 -0
  42. mapchete_eo/platforms/sentinel2/brdf/config.py +32 -0
  43. mapchete_eo/platforms/sentinel2/brdf/correction.py +260 -0
  44. mapchete_eo/platforms/sentinel2/brdf/hls.py +251 -0
  45. mapchete_eo/platforms/sentinel2/brdf/models.py +44 -0
  46. mapchete_eo/platforms/sentinel2/brdf/protocols.py +27 -0
  47. mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +136 -0
  48. mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +76 -0
  49. mapchete_eo/platforms/sentinel2/config.py +241 -0
  50. mapchete_eo/platforms/sentinel2/driver.py +43 -0
  51. mapchete_eo/platforms/sentinel2/masks.py +329 -0
  52. mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +6 -0
  53. mapchete_eo/platforms/sentinel2/metadata_parser/base.py +56 -0
  54. mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py +135 -0
  55. mapchete_eo/platforms/sentinel2/metadata_parser/models.py +78 -0
  56. mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py +639 -0
  57. mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +57 -0
  58. mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +108 -0
  59. mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +171 -0
  60. mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +217 -0
  61. mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +50 -0
  62. mapchete_eo/platforms/sentinel2/processing_baseline.py +163 -0
  63. mapchete_eo/platforms/sentinel2/product.py +747 -0
  64. mapchete_eo/platforms/sentinel2/source.py +114 -0
  65. mapchete_eo/platforms/sentinel2/types.py +114 -0
  66. mapchete_eo/processes/__init__.py +0 -0
  67. mapchete_eo/processes/config.py +51 -0
  68. mapchete_eo/processes/dtype_scale.py +112 -0
  69. mapchete_eo/processes/eo_to_xarray.py +19 -0
  70. mapchete_eo/processes/merge_rasters.py +239 -0
  71. mapchete_eo/product.py +323 -0
  72. mapchete_eo/protocols.py +61 -0
  73. mapchete_eo/search/__init__.py +14 -0
  74. mapchete_eo/search/base.py +285 -0
  75. mapchete_eo/search/config.py +113 -0
  76. mapchete_eo/search/s2_mgrs.py +313 -0
  77. mapchete_eo/search/stac_search.py +278 -0
  78. mapchete_eo/search/stac_static.py +197 -0
  79. mapchete_eo/search/utm_search.py +251 -0
  80. mapchete_eo/settings.py +25 -0
  81. mapchete_eo/sort.py +60 -0
  82. mapchete_eo/source.py +109 -0
  83. mapchete_eo/time.py +62 -0
  84. mapchete_eo/types.py +76 -0
  85. mapchete_eo-2026.2.0.dist-info/METADATA +91 -0
  86. mapchete_eo-2026.2.0.dist-info/RECORD +89 -0
  87. mapchete_eo-2026.2.0.dist-info/WHEEL +4 -0
  88. mapchete_eo-2026.2.0.dist-info/entry_points.txt +11 -0
  89. mapchete_eo-2026.2.0.dist-info/licenses/LICENSE +21 -0
mapchete_eo/base.py ADDED
@@ -0,0 +1,653 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+ import logging
5
+ from functools import cached_property
6
+ from typing import Any, Callable, List, Optional, Sequence, Type, Union, Dict, Generator
7
+
8
+ import croniter
9
+ from mapchete import Bounds
10
+ import numpy as np
11
+ import numpy.ma as ma
12
+ import xarray as xr
13
+ from dateutil.tz import tzutc
14
+ from mapchete.config.parse import guess_geometry
15
+ from mapchete.formats import base
16
+ from mapchete.geometry import reproject_geometry
17
+ from mapchete.io.vector import IndexedFeatures
18
+ from mapchete.path import MPath
19
+ from mapchete.tile import BufferedTile
20
+ from mapchete.types import MPathLike, NodataVal, NodataVals
21
+ from pydantic import BaseModel, model_validator
22
+ from pystac import Item
23
+ from rasterio.enums import Resampling
24
+ from rasterio.features import geometry_mask
25
+ from shapely.geometry import mapping
26
+ from shapely.geometry.base import BaseGeometry
27
+
28
+ from mapchete_eo.exceptions import CorruptedProductMetadata, PreprocessingNotFinished
29
+ from mapchete_eo.io import (
30
+ products_to_np_array,
31
+ products_to_xarray,
32
+ read_levelled_cube_to_np_array,
33
+ read_levelled_cube_to_xarray,
34
+ )
35
+ from mapchete_eo.source import Source
36
+ from mapchete_eo.product import EOProduct
37
+ from mapchete_eo.protocols import EOProductProtocol
38
+ from mapchete_eo.settings import mapchete_eo_settings
39
+ from mapchete_eo.sort import SortMethodConfig, TargetDateSort
40
+ from mapchete_eo.time import to_datetime
41
+ from mapchete_eo.types import DateTimeLike, MergeMethod, TimeRange
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ class BaseDriverConfig(BaseModel):
47
+ """
48
+ Configuration for mapchete-eo drivers.
49
+ """
50
+
51
+ format: str
52
+ source: Sequence[Source]
53
+ time: Optional[Union[TimeRange, List[TimeRange]]] = None
54
+ cat_baseurl: Optional[str] = None
55
+ cache: Optional[Any] = None
56
+ footprint_buffer: float = 0
57
+ area: Optional[Union[MPathLike, dict, type[BaseGeometry]]] = None
58
+ preprocessing_tasks: bool = False
59
+ search_kwargs: Optional[Dict[str, Any]] = None
60
+
61
+ @model_validator(mode="before")
62
+ def to_list(cls, values: Dict[str, Any]) -> Dict[str, Any]:
63
+ """Expands source to list."""
64
+ for field in ["source"]:
65
+ value = values.get(field)
66
+ if value is not None and not isinstance(value, list):
67
+ values[field] = [value]
68
+ return values
69
+
70
+ @model_validator(mode="before")
71
+ def deprecate_cat_baseurl(cls, values: Dict[str, Any]) -> Dict[str, Any]:
72
+ cat_baseurl = values.get("cat_baseurl")
73
+ if cat_baseurl: # pragma: no cover
74
+ warnings.warn(
75
+ "'cat_baseurl' will be deprecated soon. Please use 'catalog_type=static' in the source.",
76
+ category=DeprecationWarning,
77
+ stacklevel=2,
78
+ )
79
+ if values.get("source", []):
80
+ raise ValueError(
81
+ "deprecated cat_baseurl field found alongside sources."
82
+ )
83
+ values["source"] = [dict(collection=cat_baseurl, catalog_type="static")]
84
+ return values
85
+
86
+
87
+ class EODataCube(base.InputTile):
88
+ """Target Tile representation of input data."""
89
+
90
+ default_read_merge_method: MergeMethod = MergeMethod.first
91
+ default_read_merge_products_by: Optional[str] = None
92
+ default_read_nodataval: NodataVal = None
93
+ default_read_resampling: Resampling = Resampling.nearest
94
+
95
+ tile: BufferedTile
96
+ eo_bands: dict
97
+ time: Optional[List[TimeRange]]
98
+ area: BaseGeometry
99
+ area_pixelbuffer: int = 0
100
+
101
+ def __init__(
102
+ self,
103
+ tile: BufferedTile,
104
+ products: Optional[List[EOProductProtocol]],
105
+ eo_bands: dict,
106
+ time: Optional[List[TimeRange]] = None,
107
+ input_key: Optional[str] = None,
108
+ area: Optional[BaseGeometry] = None,
109
+ **kwargs,
110
+ ) -> None:
111
+ """Initialize."""
112
+ self.tile = tile
113
+ self._products = products
114
+ self.eo_bands = eo_bands
115
+ self.time = time
116
+ self.input_key = input_key
117
+ self.area = tile.bbox if area is None else area
118
+
119
+ @cached_property
120
+ def products(self) -> IndexedFeatures[EOProductProtocol]:
121
+ """
122
+ Indexed products.
123
+ """
124
+ # during task graph processing, the products have to be fetched as preprocessing task results
125
+ if self._products is None: # pragma: no cover
126
+ return IndexedFeatures(
127
+ [
128
+ item
129
+ for item in self.preprocessing_tasks_results.values()
130
+ if not isinstance(item, CorruptedProductMetadata)
131
+ ],
132
+ crs=self.tile.crs,
133
+ # by not using rtree, we avoid an edge case where products outside of process CRS bounds
134
+ # cause rtree to fail when indexing the products.
135
+ index=None,
136
+ )
137
+
138
+ # just return the prouducts as is
139
+ return IndexedFeatures(
140
+ [
141
+ item
142
+ for item in self._products
143
+ if not isinstance(item, CorruptedProductMetadata)
144
+ ],
145
+ crs=self.tile.crs,
146
+ # by not using rtree, we avoid an edge case where products outside of process CRS bounds
147
+ # cause rtree to fail when indexing the products.
148
+ index=None,
149
+ )
150
+
151
+ def read(
152
+ self,
153
+ assets: Optional[List[str]] = None,
154
+ eo_bands: Optional[List[str]] = None,
155
+ start_time: Optional[DateTimeLike] = None,
156
+ end_time: Optional[DateTimeLike] = None,
157
+ timestamps: Optional[List[DateTimeLike]] = None,
158
+ time_pattern: Optional[str] = None,
159
+ resampling: Optional[Union[Resampling, str]] = None,
160
+ merge_products_by: Optional[str] = None,
161
+ merge_method: Optional[MergeMethod] = None,
162
+ sort: Optional[SortMethodConfig] = None,
163
+ nodatavals: NodataVals = None,
164
+ raise_empty: bool = True,
165
+ **kwargs,
166
+ ) -> xr.Dataset:
167
+ """
168
+ Read input data into an xarray.Dataset.
169
+ """
170
+ return products_to_xarray(
171
+ products=self.filter_products(
172
+ start_time=start_time,
173
+ end_time=end_time,
174
+ timestamps=timestamps,
175
+ time_pattern=time_pattern,
176
+ ),
177
+ eo_bands=eo_bands,
178
+ assets=assets,
179
+ grid=self.tile,
180
+ raise_empty=raise_empty,
181
+ product_read_kwargs=kwargs,
182
+ sort=sort,
183
+ **self.default_read_values(
184
+ merge_products_by=merge_products_by,
185
+ merge_method=merge_method,
186
+ resampling=resampling,
187
+ nodatavals=nodatavals,
188
+ ),
189
+ )
190
+
191
+ def read_np_array(
192
+ self,
193
+ assets: Optional[List[str]] = None,
194
+ eo_bands: Optional[List[str]] = None,
195
+ start_time: Optional[DateTimeLike] = None,
196
+ end_time: Optional[DateTimeLike] = None,
197
+ timestamps: Optional[List[DateTimeLike]] = None,
198
+ time_pattern: Optional[str] = None,
199
+ resampling: Optional[Union[Resampling, str]] = None,
200
+ merge_products_by: Optional[str] = None,
201
+ merge_method: Optional[MergeMethod] = None,
202
+ sort: Optional[SortMethodConfig] = None,
203
+ nodatavals: NodataVals = None,
204
+ raise_empty: bool = True,
205
+ **kwargs,
206
+ ) -> ma.MaskedArray:
207
+ """
208
+ Read input data as a MaskedArray.
209
+ """
210
+ return products_to_np_array(
211
+ products=self.filter_products(
212
+ start_time=start_time,
213
+ end_time=end_time,
214
+ timestamps=timestamps,
215
+ time_pattern=time_pattern,
216
+ ),
217
+ eo_bands=eo_bands,
218
+ assets=assets,
219
+ grid=self.tile,
220
+ product_read_kwargs=kwargs,
221
+ raise_empty=raise_empty,
222
+ sort=sort,
223
+ **self.default_read_values(
224
+ merge_products_by=merge_products_by,
225
+ merge_method=merge_method,
226
+ resampling=resampling,
227
+ nodatavals=nodatavals,
228
+ ),
229
+ )
230
+
231
+ def read_levelled(
232
+ self,
233
+ target_height: int,
234
+ assets: Optional[List[str]] = None,
235
+ eo_bands: Optional[List[str]] = None,
236
+ start_time: Optional[DateTimeLike] = None,
237
+ end_time: Optional[DateTimeLike] = None,
238
+ timestamps: Optional[List[DateTimeLike]] = None,
239
+ time_pattern: Optional[str] = None,
240
+ resampling: Optional[Union[Resampling, str]] = None,
241
+ nodatavals: NodataVals = None,
242
+ merge_products_by: Optional[str] = None,
243
+ merge_method: Optional[MergeMethod] = None,
244
+ sort: SortMethodConfig = TargetDateSort(),
245
+ raise_empty: bool = True,
246
+ slice_axis_name: str = "layers",
247
+ band_axis_name: str = "bands",
248
+ x_axis_name: str = "x",
249
+ y_axis_name: str = "y",
250
+ **kwargs,
251
+ ) -> xr.Dataset:
252
+ return read_levelled_cube_to_xarray(
253
+ products=self.filter_products(
254
+ start_time=start_time,
255
+ end_time=end_time,
256
+ timestamps=timestamps,
257
+ time_pattern=time_pattern,
258
+ ),
259
+ target_height=target_height,
260
+ assets=assets,
261
+ eo_bands=eo_bands,
262
+ grid=self.tile,
263
+ raise_empty=raise_empty,
264
+ product_read_kwargs=kwargs,
265
+ slice_axis_name=slice_axis_name,
266
+ band_axis_name=band_axis_name,
267
+ x_axis_name=x_axis_name,
268
+ y_axis_name=y_axis_name,
269
+ sort=sort,
270
+ **self.default_read_values(
271
+ merge_products_by=merge_products_by,
272
+ merge_method=merge_method,
273
+ resampling=resampling,
274
+ nodatavals=nodatavals,
275
+ ),
276
+ )
277
+
278
+ def read_levelled_np_array(
279
+ self,
280
+ target_height: int,
281
+ assets: Optional[List[str]] = None,
282
+ eo_bands: Optional[List[str]] = None,
283
+ start_time: Optional[DateTimeLike] = None,
284
+ end_time: Optional[DateTimeLike] = None,
285
+ timestamps: Optional[List[DateTimeLike]] = None,
286
+ time_pattern: Optional[str] = None,
287
+ resampling: Optional[Union[Resampling, str]] = None,
288
+ nodatavals: NodataVals = None,
289
+ merge_products_by: Optional[str] = None,
290
+ merge_method: Optional[MergeMethod] = None,
291
+ sort: SortMethodConfig = TargetDateSort(),
292
+ raise_empty: bool = True,
293
+ **kwargs,
294
+ ) -> ma.MaskedArray:
295
+ """
296
+ Read levelled data (cubes with depth) as a MaskedArray.
297
+
298
+ Args:
299
+ target_height: Target stack height.
300
+ assets: List of asset names.
301
+ eo_bands: List of EO bands.
302
+ start_time: Start time.
303
+ end_time: End time.
304
+ timestamps: List of timestamps.
305
+ time_pattern: Time pattern.
306
+ resampling: Resampling method.
307
+ nodatavals: Nodata values.
308
+ merge_products_by: Property to merge by.
309
+ merge_method: Merge method.
310
+ sort: Sorting configuration.
311
+ raise_empty: Raise error if no data found.
312
+
313
+ Returns:
314
+ ma.MaskedArray: Output data array.
315
+ """
316
+ return read_levelled_cube_to_np_array(
317
+ products=self.filter_products(
318
+ start_time=start_time,
319
+ end_time=end_time,
320
+ timestamps=timestamps,
321
+ time_pattern=time_pattern,
322
+ ),
323
+ target_height=target_height,
324
+ assets=assets,
325
+ eo_bands=eo_bands,
326
+ grid=self.tile,
327
+ raise_empty=raise_empty,
328
+ product_read_kwargs=kwargs,
329
+ sort=sort,
330
+ **self.default_read_values(
331
+ merge_products_by=merge_products_by,
332
+ merge_method=merge_method,
333
+ resampling=resampling,
334
+ nodatavals=nodatavals,
335
+ ),
336
+ )
337
+
338
+ def read_masks(
339
+ self,
340
+ start_time: Optional[DateTimeLike] = None,
341
+ end_time: Optional[DateTimeLike] = None,
342
+ timestamps: Optional[List[DateTimeLike]] = None,
343
+ time_pattern: Optional[str] = None,
344
+ nodatavals: NodataVals = None,
345
+ **kwargs,
346
+ ):
347
+ """
348
+ Read product masks.
349
+
350
+ Args:
351
+ start_time: Start time.
352
+ end_time: End time.
353
+ timestamps: List of timestamps.
354
+ time_pattern: Time pattern.
355
+ nodatavals: Nodata values.
356
+
357
+ Returns:
358
+ ma.MaskedArray: Mask data.
359
+ """
360
+ from mapchete_eo.platforms.sentinel2.masks import read_masks
361
+
362
+ return read_masks(
363
+ products=self.filter_products(
364
+ start_time=start_time,
365
+ end_time=end_time,
366
+ timestamps=timestamps,
367
+ time_pattern=time_pattern,
368
+ ),
369
+ grid=self.tile,
370
+ nodatavals=nodatavals,
371
+ product_read_kwargs=kwargs,
372
+ )
373
+
374
+ def filter_products(
375
+ self,
376
+ start_time: Optional[DateTimeLike] = None,
377
+ end_time: Optional[DateTimeLike] = None,
378
+ timestamps: Optional[List[DateTimeLike]] = None,
379
+ time_pattern: Optional[str] = None,
380
+ ):
381
+ """
382
+ Return a filtered list of input products.
383
+ """
384
+ if any([start_time, end_time, timestamps]): # pragma: no cover
385
+ raise NotImplementedError("time subsets are not yet implemented")
386
+
387
+ if time_pattern:
388
+ # filter products by time pattern
389
+ return [
390
+ product
391
+ for product in self.products
392
+ if product.item.datetime
393
+ in [
394
+ t.replace(tzinfo=tzutc())
395
+ for t in croniter.croniter_range(
396
+ to_datetime(self.start_time),
397
+ to_datetime(self.end_time),
398
+ time_pattern,
399
+ )
400
+ ]
401
+ ]
402
+ return self.products
403
+
404
+ def is_empty(self) -> bool: # pragma: no cover
405
+ """
406
+ Check if there is data within this tile.
407
+
408
+ Returns
409
+ -------
410
+ is empty : bool
411
+ """
412
+ return len(self.products) == 0
413
+
414
+ def default_read_values(
415
+ self,
416
+ resampling: Optional[Union[Resampling, str]] = None,
417
+ nodatavals: NodataVals = None,
418
+ merge_products_by: Optional[str] = None,
419
+ merge_method: Optional[MergeMethod] = None,
420
+ ) -> dict:
421
+ """Provide proper read values depending on user input and defaults."""
422
+ if nodatavals is None:
423
+ nodatavals = self.default_read_nodataval
424
+ merge_products_by = merge_products_by or self.default_read_merge_products_by
425
+ merge_method = merge_method or self.default_read_merge_method
426
+ return dict(
427
+ resampling=(
428
+ self.default_read_resampling
429
+ if resampling is None
430
+ else (
431
+ resampling
432
+ if isinstance(resampling, Resampling)
433
+ else Resampling[resampling]
434
+ )
435
+ ),
436
+ nodatavals=nodatavals,
437
+ merge_products_by=merge_products_by,
438
+ merge_method=merge_method,
439
+ read_mask=self.get_read_mask(),
440
+ )
441
+
442
+ def get_read_mask(self) -> np.ndarray:
443
+ """
444
+ Determine read mask according to input area.
445
+
446
+ This will generate a numpy array where pixel overlapping the input area
447
+ are set True and thus will get filled by the read function. Pixel outside
448
+ of the area are not considered for reading.
449
+
450
+ On staged reading, i.e. first checking the product masks to assess valid
451
+ pixels, this will avoid reading product bands in cases the product only covers
452
+ pixels outside of the intended reading area.
453
+ """
454
+ area = self.area.buffer(self.area_pixelbuffer * self.tile.pixel_x_size)
455
+ if area.is_empty:
456
+ return np.zeros((self.tile.shape), dtype=bool)
457
+ return geometry_mask(
458
+ geometries=[mapping(area)],
459
+ out_shape=self.tile.shape,
460
+ transform=self.tile.transform,
461
+ invert=True,
462
+ )
463
+
464
+
465
+ class InputData(base.InputData):
466
+ """
467
+ Main driver class used by mapchete to handle input data discovery and indexing.
468
+ """
469
+
470
+ default_preprocessing_task: Callable = staticmethod(EOProduct.from_stac_item)
471
+ driver_config_model: Type[BaseDriverConfig] = BaseDriverConfig
472
+ params: BaseDriverConfig
473
+ time: Optional[Union[TimeRange, List[TimeRange]]]
474
+ area: BaseGeometry
475
+ _products: Optional[IndexedFeatures] = None
476
+
477
+ def __init__(
478
+ self,
479
+ input_params: dict,
480
+ readonly: bool = False,
481
+ input_key: Optional[str] = None,
482
+ standalone: bool = False,
483
+ **kwargs,
484
+ ) -> None:
485
+ """Initialize."""
486
+ super().__init__(input_params, **kwargs)
487
+ self.readonly = readonly
488
+ self.input_key = input_key
489
+ self.standalone = standalone
490
+
491
+ self.params = self.driver_config_model(**input_params["abstract"])
492
+ self.conf_dir = input_params.get("conf_dir")
493
+
494
+ # we have to make sure, the cache path is absolute
495
+ # not quite fond of this solution
496
+ if self.params.cache:
497
+ self.params.cache.path = MPath.from_inp(
498
+ self.params.cache.dict()
499
+ ).absolute_path(base_dir=input_params.get("conf_dir"))
500
+ self.area = self._init_area(input_params)
501
+ self.time = self.params.time
502
+
503
+ self.eo_bands = [
504
+ eo_band
505
+ for source in self.params.source
506
+ for eo_band in source.eo_bands(base_dir=self.conf_dir)
507
+ ]
508
+
509
+ if self.readonly: # pragma: no cover
510
+ return
511
+ # don't use preprocessing tasks for Sentinel-2 products:
512
+ if self.params.preprocessing_tasks or self.params.cache is not None:
513
+ for item in self.source_items():
514
+ self.add_preprocessing_task(
515
+ self.default_preprocessing_task,
516
+ fargs=(item,),
517
+ fkwargs=dict(cache_config=self.params.cache, cache_all=True),
518
+ key=item.id,
519
+ geometry=reproject_geometry(
520
+ item.geometry,
521
+ src_crs=mapchete_eo_settings.default_catalog_crs,
522
+ dst_crs=self.crs,
523
+ ),
524
+ )
525
+ else:
526
+ logger.debug("do preprocessing tasks now rather than later")
527
+ self._products = IndexedFeatures(
528
+ [
529
+ self.default_preprocessing_task(
530
+ item, cache_config=self.params.cache, cache_all=True
531
+ )
532
+ for item in self.source_items()
533
+ ]
534
+ )
535
+
536
+ def _init_area(self, input_params: dict) -> BaseGeometry:
537
+ """Returns valid driver area for this process."""
538
+ process_area = input_params["delimiters"]["effective_area"]
539
+ if self.params.area:
540
+ # read area parameter and intersect with effective area
541
+ configured_area, configured_area_crs = guess_geometry(
542
+ self.params.area,
543
+ bounds=Bounds.from_inp(
544
+ input_params.get("delimiters", {}).get("effective_bounds"),
545
+ crs=getattr(input_params.get("pyramid"), "crs"),
546
+ ),
547
+ raise_if_empty=False,
548
+ )
549
+ process_area = process_area.intersection(
550
+ reproject_geometry(
551
+ configured_area,
552
+ src_crs=configured_area_crs or self.crs,
553
+ dst_crs=self.crs,
554
+ )
555
+ )
556
+ return process_area
557
+
558
+ def source_items(self) -> Generator[Item, None, None]:
559
+ already_returned = set()
560
+ for source in self.params.source:
561
+ area = reproject_geometry(
562
+ self.area,
563
+ src_crs=self.crs,
564
+ dst_crs=source.catalog_crs,
565
+ )
566
+ if area.is_empty:
567
+ continue
568
+ for item in source.search(
569
+ time=self.time,
570
+ area=area,
571
+ base_dir=self.conf_dir,
572
+ ):
573
+ # if item was already found in previous source, skip
574
+ if item.id in already_returned:
575
+ continue
576
+
577
+ # if item is new, add to list and yield
578
+ already_returned.add(item.id)
579
+ item.properties["mapchete_eo:source"] = source
580
+ yield item
581
+ logger.debug("returned set of %s items", len(already_returned))
582
+
583
+ def bbox(self, out_crs: Optional[str] = None) -> BaseGeometry:
584
+ """Return data bounding box."""
585
+ return reproject_geometry(
586
+ self.area,
587
+ src_crs=self.pyramid.crs,
588
+ dst_crs=self.pyramid.crs if out_crs is None else out_crs,
589
+ segmentize_on_clip=True,
590
+ )
591
+
592
+ @cached_property
593
+ def products(self) -> IndexedFeatures:
594
+ """Hold preprocessed S2Products in an IndexedFeatures container."""
595
+
596
+ # nothing to index here
597
+ if self.readonly:
598
+ return IndexedFeatures([])
599
+
600
+ elif self._products is not None:
601
+ return self._products
602
+
603
+ # TODO: copied it from mapchete_satellite, not yet sure which use case this is
604
+ elif self.standalone: # pragma: no cover
605
+ raise NotImplementedError()
606
+
607
+ # if preprocessing tasks are ready, index them for further use
608
+ elif self.preprocessing_tasks_results:
609
+ return IndexedFeatures(
610
+ [
611
+ self.get_preprocessing_task_result(item.id)
612
+ for item in self.source_items()
613
+ if not isinstance(item, CorruptedProductMetadata)
614
+ ],
615
+ crs=self.crs,
616
+ )
617
+
618
+ elif not self.preprocessing_tasks:
619
+ return IndexedFeatures([])
620
+
621
+ # this happens on task graph execution when preprocessing task results are not ready
622
+ else: # pragma: no cover
623
+ raise PreprocessingNotFinished(
624
+ f"products are not ready yet because {len(self.preprocessing_tasks)} preprocessing task(s) were not executed."
625
+ )
626
+
627
+ def open(self, tile, **kwargs) -> EODataCube:
628
+ """
629
+ Return InputTile object.
630
+ """
631
+ try:
632
+ tile_products = self.products.filter(
633
+ reproject_geometry(
634
+ tile.bbox,
635
+ src_crs=tile.crs,
636
+ dst_crs=mapchete_eo_settings.default_catalog_crs,
637
+ ).bounds
638
+ )
639
+ except PreprocessingNotFinished: # pragma: no cover
640
+ tile_products = None
641
+ return self.input_tile_cls(
642
+ tile,
643
+ products=tile_products,
644
+ eo_bands=self.eo_bands,
645
+ time=self.time,
646
+ # passing on the input key is essential so dependent preprocessing tasks can be found!
647
+ input_key=self.input_key,
648
+ area=self.area.intersection(tile.bbox),
649
+ )
650
+
651
+ def cleanup(self):
652
+ for product in self.products:
653
+ product.clear_cached_data()