mapchete-eo 2025.11.0__tar.gz → 2026.2.0__tar.gz

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 (90) hide show
  1. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/.gitignore +12 -8
  2. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/LICENSE +1 -1
  3. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/PKG-INFO +10 -8
  4. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/README.rst +2 -2
  5. mapchete_eo-2026.2.0/mapchete_eo/__init__.py +1 -0
  6. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/base.py +49 -5
  7. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/compositing.py +12 -0
  8. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/assets.py +15 -11
  9. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/driver.py +5 -1
  10. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/masks.py +6 -2
  11. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +4 -1
  12. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/product.py +15 -2
  13. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/product.py +43 -3
  14. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/base.py +49 -12
  15. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/config.py +50 -0
  16. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/stac_search.py +20 -29
  17. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/stac_static.py +4 -12
  18. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/utm_search.py +24 -15
  19. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/sort.py +1 -3
  20. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/source.py +3 -1
  21. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/time.py +12 -3
  22. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/types.py +6 -3
  23. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/pyproject.toml +25 -7
  24. mapchete_eo-2025.11.0/mapchete_eo/__init__.py +0 -1
  25. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/__init__.py +0 -0
  26. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/buffer.py +0 -0
  27. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/color.py +0 -0
  28. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/convert.py +0 -0
  29. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/blacklist.txt +0 -0
  30. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/__init__.py +0 -0
  31. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/bounds.py +0 -0
  32. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/options_arguments.py +0 -0
  33. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_brdf.py +0 -0
  34. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_cat_results.py +0 -0
  35. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_find_broken_products.py +0 -0
  36. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_jp2_static_catalog.py +0 -0
  37. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_mask.py +0 -0
  38. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_mgrs.py +0 -0
  39. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_rgb.py +0 -0
  40. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_verify.py +0 -0
  41. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/static_catalog.py +0 -0
  42. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/eostac.py +0 -0
  43. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/exceptions.py +0 -0
  44. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/__init__.py +0 -0
  45. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/blend_functions.py +0 -0
  46. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/color_correction.py +0 -0
  47. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/dtype_scale.py +0 -0
  48. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/fillnodata.py +0 -0
  49. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/filters.py +0 -0
  50. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/linear_normalization.py +0 -0
  51. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/sigmoidal.py +0 -0
  52. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/__init__.py +0 -0
  53. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/items.py +0 -0
  54. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/levelled_cubes.py +0 -0
  55. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/path.py +0 -0
  56. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/products.py +0 -0
  57. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/profiles.py +0 -0
  58. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/__init__.py +0 -0
  59. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/_mapper_registry.py +0 -0
  60. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +0 -0
  61. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/__init__.py +0 -0
  62. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/config.py +0 -0
  63. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/correction.py +0 -0
  64. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/hls.py +0 -0
  65. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/models.py +0 -0
  66. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/protocols.py +0 -0
  67. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +0 -0
  68. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +0 -0
  69. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/config.py +0 -0
  70. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +0 -0
  71. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/base.py +0 -0
  72. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py +0 -0
  73. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/models.py +0 -0
  74. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py +0 -0
  75. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +0 -0
  76. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +0 -0
  77. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +0 -0
  78. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +0 -0
  79. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/processing_baseline.py +0 -0
  80. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/source.py +0 -0
  81. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/types.py +0 -0
  82. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/__init__.py +0 -0
  83. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/config.py +0 -0
  84. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/dtype_scale.py +0 -0
  85. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/eo_to_xarray.py +0 -0
  86. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/merge_rasters.py +0 -0
  87. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/protocols.py +0 -0
  88. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/__init__.py +0 -0
  89. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/s2_mgrs.py +0 -0
  90. {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/settings.py +0 -0
@@ -1,14 +1,18 @@
1
- .eggs
2
- *.egg-info
3
- *.pyc
1
+ build/
4
2
  .cache
5
- htmlcov
6
3
  .coverage
7
4
  .coverage.*
8
- build/
9
5
  dist/
10
- .pytest*
6
+ docs/build/html
7
+ *.egg-info
8
+ .eggs
9
+ examples/sentinel-2*/
11
10
  *.gfs
12
- .vscode/
11
+ htmlcov
12
+ .mypy_cache
13
+ *.pyc
13
14
  __pycache__
14
- examples/sentinel-2*/
15
+ .pytest*
16
+ .ruff_cache
17
+ .vscode/
18
+ .venv
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022 - 2025 EOX IT Services
3
+ Copyright (c) 2022 - 2026 EOX IT Services
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapchete-eo
3
- Version: 2025.11.0
3
+ Version: 2026.2.0
4
4
  Summary: mapchete EO data reader
5
5
  Project-URL: Homepage, https://gitlab.eox.at/maps/mapchete_eo
6
6
  Author-email: Joachim Ungar <joachim.ungar@eox.at>, Petr Sevcik <petr.sevcik@eox.at>
@@ -14,14 +14,15 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Scientific/Engineering :: GIS
17
+ Requires-Python: >=3.10
17
18
  Requires-Dist: click
18
- Requires-Dist: cql2
19
19
  Requires-Dist: croniter
20
20
  Requires-Dist: lxml
21
21
  Requires-Dist: mapchete[complete]>=2025.10.0
22
22
  Requires-Dist: opencv-python-headless
23
23
  Requires-Dist: pillow
24
24
  Requires-Dist: pydantic
25
+ Requires-Dist: pygeofilter
25
26
  Requires-Dist: pystac-client>=0.7.5
26
27
  Requires-Dist: pystac[urllib3]>=1.12.2
27
28
  Requires-Dist: retry
@@ -30,12 +31,13 @@ Requires-Dist: scipy
30
31
  Requires-Dist: tqdm
31
32
  Requires-Dist: xarray
32
33
  Provides-Extra: docs
33
- Requires-Dist: sphinx; extra == 'docs'
34
- Requires-Dist: sphinx-rtd-theme; extra == 'docs'
34
+ Requires-Dist: myst-parser; extra == 'docs'
35
+ Requires-Dist: sphinx-rtd-theme>=3.0.2; extra == 'docs'
36
+ Requires-Dist: sphinx>=8.0; extra == 'docs'
35
37
  Provides-Extra: test
38
+ Requires-Dist: pytest; extra == 'test'
36
39
  Requires-Dist: pytest-coverage; extra == 'test'
37
- Requires-Dist: pytest-lazy-fixture; extra == 'test'
38
- Requires-Dist: pytest<8; extra == 'test'
40
+ Requires-Dist: pytest-lazy-fixtures; extra == 'test'
39
41
  Description-Content-Type: text/x-rst
40
42
 
41
43
  .. image:: logo/mapchete_eo.svg
@@ -80,10 +82,10 @@ You must have ``mapchete`` with ``s3`` installed, so let's grab the ``complete``
80
82
 
81
83
  .. code-block:: bash
82
84
 
83
- pip install mapchete[complete]
85
+ uv pip install mapchete[complete]
84
86
 
85
87
  Then install mapchete-eo:
86
88
 
87
89
  .. code-block:: bash
88
90
 
89
- pip install mapchete-eo
91
+ uv pip install mapchete-eo
@@ -40,10 +40,10 @@ You must have ``mapchete`` with ``s3`` installed, so let's grab the ``complete``
40
40
 
41
41
  .. code-block:: bash
42
42
 
43
- pip install mapchete[complete]
43
+ uv pip install mapchete[complete]
44
44
 
45
45
  Then install mapchete-eo:
46
46
 
47
47
  .. code-block:: bash
48
48
 
49
- pip install mapchete-eo
49
+ uv pip install mapchete-eo
@@ -0,0 +1 @@
1
+ __version__ = "2026.2.0"
@@ -44,6 +44,10 @@ logger = logging.getLogger(__name__)
44
44
 
45
45
 
46
46
  class BaseDriverConfig(BaseModel):
47
+ """
48
+ Configuration for mapchete-eo drivers.
49
+ """
50
+
47
51
  format: str
48
52
  source: Sequence[Source]
49
53
  time: Optional[Union[TimeRange, List[TimeRange]]] = None
@@ -114,6 +118,9 @@ class EODataCube(base.InputTile):
114
118
 
115
119
  @cached_property
116
120
  def products(self) -> IndexedFeatures[EOProductProtocol]:
121
+ """
122
+ Indexed products.
123
+ """
117
124
  # during task graph processing, the products have to be fetched as preprocessing task results
118
125
  if self._products is None: # pragma: no cover
119
126
  return IndexedFeatures(
@@ -158,11 +165,7 @@ class EODataCube(base.InputTile):
158
165
  **kwargs,
159
166
  ) -> xr.Dataset:
160
167
  """
161
- Read reprojected & resampled input data.
162
-
163
- Returns
164
- -------
165
- data : xarray.Dataset
168
+ Read input data into an xarray.Dataset.
166
169
  """
167
170
  return products_to_xarray(
168
171
  products=self.filter_products(
@@ -201,6 +204,9 @@ class EODataCube(base.InputTile):
201
204
  raise_empty: bool = True,
202
205
  **kwargs,
203
206
  ) -> ma.MaskedArray:
207
+ """
208
+ Read input data as a MaskedArray.
209
+ """
204
210
  return products_to_np_array(
205
211
  products=self.filter_products(
206
212
  start_time=start_time,
@@ -286,6 +292,27 @@ class EODataCube(base.InputTile):
286
292
  raise_empty: bool = True,
287
293
  **kwargs,
288
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
+ """
289
316
  return read_levelled_cube_to_np_array(
290
317
  products=self.filter_products(
291
318
  start_time=start_time,
@@ -317,6 +344,19 @@ class EODataCube(base.InputTile):
317
344
  nodatavals: NodataVals = None,
318
345
  **kwargs,
319
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
+ """
320
360
  from mapchete_eo.platforms.sentinel2.masks import read_masks
321
361
 
322
362
  return read_masks(
@@ -423,6 +463,10 @@ class EODataCube(base.InputTile):
423
463
 
424
464
 
425
465
  class InputData(base.InputData):
466
+ """
467
+ Main driver class used by mapchete to handle input data discovery and indexing.
468
+ """
469
+
426
470
  default_preprocessing_task: Callable = staticmethod(EOProduct.from_stac_item)
427
471
  driver_config_model: Type[BaseDriverConfig] = BaseDriverConfig
428
472
  params: BaseDriverConfig
@@ -153,6 +153,18 @@ METHODS = {
153
153
  def composite(
154
154
  method: str, bg: np.ndarray, fg: np.ndarray, opacity: float = 1
155
155
  ) -> ma.MaskedArray:
156
+ """
157
+ Composite two image arrays using a named blending method.
158
+
159
+ Args:
160
+ method: Blending method name (e.g., 'multiply', 'screen').
161
+ bg: Background image array (channels-first).
162
+ fg: Foreground image array (channels-first).
163
+ opacity: Opacity of the foreground layer (0-1).
164
+
165
+ Returns:
166
+ ma.MaskedArray: Blended RGBA result.
167
+ """
156
168
  return METHODS[method](bg, fg, opacity)
157
169
 
158
170
 
@@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
32
32
 
33
33
 
34
34
  class STACRasterBandProperties(BaseModel):
35
- nodata: NodataVal = None
35
+ nodata: Optional[NodataVal] = None
36
36
  data_type: Optional[str] = None
37
37
  scale: float = 1.0
38
38
  offset: float = 0.0
@@ -40,9 +40,9 @@ class STACRasterBandProperties(BaseModel):
40
40
  @staticmethod
41
41
  def from_asset(
42
42
  asset: pystac.Asset,
43
- nodataval: NodataVal = None,
43
+ nodataval: Optional[NodataVal] = None,
44
44
  ) -> STACRasterBandProperties:
45
- if asset.extra_fields.get("raster:offset") is not None:
45
+ if asset.extra_fields.get("raster:offset", {}):
46
46
  properties = dict(
47
47
  offset=asset.extra_fields.get("raster:offset"),
48
48
  scale=asset.extra_fields.get("raster:scale"),
@@ -87,16 +87,21 @@ def asset_to_np_array(
87
87
  )
88
88
 
89
89
  logger.debug("reading asset %s and indexes %s ...", asset, indexes)
90
- data = read_raster(
90
+ array = read_raster(
91
91
  inp=path,
92
92
  indexes=indexes,
93
93
  grid=grid,
94
94
  resampling=resampling.name,
95
95
  dst_nodata=band_properties.nodata,
96
- ).data
97
-
96
+ ).array
98
97
  if apply_offset and band_properties.offset:
99
- data_type = band_properties.data_type or data.dtype
98
+ logger.debug(
99
+ "apply offset %s and scale %s to asset %s",
100
+ band_properties.offset,
101
+ band_properties.scale,
102
+ asset,
103
+ )
104
+ data_type = band_properties.data_type or array.dtype
100
105
 
101
106
  # determine value range for the target data_type
102
107
  clip_min, clip_max = dtype_ranges[str(data_type)]
@@ -105,9 +110,9 @@ def asset_to_np_array(
105
110
  if clip_min == band_properties.nodata:
106
111
  clip_min += 1
107
112
 
108
- data[:] = (
113
+ array[~array.mask] = (
109
114
  (
110
- ((data * band_properties.scale) + band_properties.offset)
115
+ ((array[~array.mask] * band_properties.scale) + band_properties.offset)
111
116
  / band_properties.scale
112
117
  )
113
118
  .round()
@@ -115,8 +120,7 @@ def asset_to_np_array(
115
120
  .astype(data_type, copy=False)
116
121
  .data
117
122
  )
118
-
119
- return data
123
+ return array
120
124
 
121
125
 
122
126
  def get_assets(
@@ -17,6 +17,10 @@ METADATA: dict = {
17
17
 
18
18
 
19
19
  class Sentinel2Cube(base.EODataCube):
20
+ """
21
+ Sentinel-2 data cube for Mapchete.
22
+ """
23
+
20
24
  # Sentinel-2 driver specific default values:
21
25
  default_read_merge_method: MergeMethod = MergeMethod.average
22
26
  default_read_merge_products_by: Optional[str] = "s2:datastrip_id"
@@ -29,7 +33,7 @@ Sentinel2CubeGroup = List[Tuple[str, Sentinel2Cube]]
29
33
 
30
34
  class InputData(base.InputData):
31
35
  """
32
- Main driver class used by mapchete.
36
+ Sentinel-2 driver for Mapchete.
33
37
  """
34
38
 
35
39
  # Sentinel-2 driver specific parameters:
@@ -61,7 +61,9 @@ def masks_to_xarray(
61
61
  raise_empty: bool = True,
62
62
  product_read_kwargs: dict = {},
63
63
  ) -> xr.Dataset:
64
- """Read grid window of EOProducts and merge into a 4D xarray."""
64
+ """
65
+ Read masks of products and merge into an xarray.Dataset.
66
+ """
65
67
  data_vars = [
66
68
  s
67
69
  for s in generate_slice_masks_dataarrays(
@@ -307,7 +309,9 @@ def product_masks_to_slices(
307
309
  group_by_property: Optional[str] = None,
308
310
  sort: Optional[SortMethodConfig] = None,
309
311
  ) -> List[Slice]:
310
- """Group products per given property into Slice objects and optionally sort slices."""
312
+ """
313
+ Group products by a property into Slices and optionally sort.
314
+ """
311
315
  if group_by_property:
312
316
  grouped = defaultdict(list)
313
317
  for product in products:
@@ -19,7 +19,10 @@ def parse_s2_product(
19
19
  cache_config: Optional[CacheConfig] = None,
20
20
  cache_all: bool = False,
21
21
  ) -> Union[S2Product, CorruptedProductMetadata]:
22
- # use mapper from source if applickable
22
+ """
23
+ Parse a Sentinel-2 STAC Item into an S2Product.
24
+ """
25
+ # use mapper from source if applicable
23
26
  source: Union[Sentinel2Source, None] = item.properties.pop(
24
27
  "mapchete_eo:source", None
25
28
  )
@@ -143,6 +143,10 @@ class Cache:
143
143
 
144
144
 
145
145
  class S2Product(EOProduct, EOProductProtocol):
146
+ """
147
+ Sentinel-2 specific EOProduct implementation.
148
+ """
149
+
146
150
  _item_dict: Optional[dict] = None
147
151
  cache: Optional[Cache] = None
148
152
  _scl_cache: Dict[GridProtocol, np.ndarray]
@@ -245,6 +249,9 @@ class S2Product(EOProduct, EOProductProtocol):
245
249
  read_mask: Optional[np.ndarray] = None,
246
250
  **kwargs,
247
251
  ) -> ma.MaskedArray:
252
+ """
253
+ Read Sentinel-2 assets into a MaskedArray with masks and BRDF.
254
+ """
248
255
  assets = assets or []
249
256
  eo_bands = eo_bands or []
250
257
  apply_offset = apply_offset and not self.metadata.boa_offset_applied
@@ -451,7 +458,9 @@ class S2Product(EOProduct, EOProductProtocol):
451
458
  grid: Union[GridProtocol, Resolution] = Resolution["20m"],
452
459
  cached_read: bool = False,
453
460
  ) -> ReferencedRaster:
454
- """Return SCL mask."""
461
+ """
462
+ Read Scene Classification Layer mask.
463
+ """
455
464
  grid = (
456
465
  self.metadata.grid(grid)
457
466
  if isinstance(grid, Resolution)
@@ -519,7 +528,9 @@ class S2Product(EOProduct, EOProductProtocol):
519
528
  mask_config: MaskConfig = MaskConfig(),
520
529
  target_mask: Optional[np.ndarray] = None,
521
530
  ) -> ReferencedRaster:
522
- """Merge masks into one 2D array."""
531
+ """
532
+ Merge all configured masks into one.
533
+ """
523
534
  grid = (
524
535
  self.metadata.grid(grid)
525
536
  if isinstance(grid, Resolution)
@@ -619,6 +630,8 @@ class S2Product(EOProduct, EOProductProtocol):
619
630
  logger.debug(
620
631
  "mask for product %s already full, skip reading other masks", self.id
621
632
  )
633
+ except FileNotFoundError as exc: # pragma: no cover
634
+ raise CorruptedProduct from exc
622
635
 
623
636
  # ATTENTION: target_mask and out have to be combined *after* mask was buffered!
624
637
  # use 'logical or' not '+' !!!
@@ -26,7 +26,9 @@ logger = logging.getLogger(__name__)
26
26
 
27
27
 
28
28
  class EOProduct(EOProductProtocol):
29
- """Wrapper class around a Item which provides read functions."""
29
+ """
30
+ Wrapper class around a STAC Item which provides data reading capabilities.
31
+ """
30
32
 
31
33
  id: str
32
34
  default_dtype: DTypeLike = np.uint16
@@ -70,7 +72,22 @@ class EOProduct(EOProductProtocol):
70
72
  raise_empty: bool = True,
71
73
  **kwargs,
72
74
  ) -> xr.Dataset:
73
- """Read bands and assets into xarray."""
75
+ """
76
+ Read bands and assets into an xarray.Dataset.
77
+
78
+ Args:
79
+ assets: List of asset names.
80
+ eo_bands: List of EO band names.
81
+ grid: Target grid protocol.
82
+ resampling: Resampling algorithm.
83
+ nodatavals: Custom nodata values.
84
+ x_axis_name: Name of X axis in output.
85
+ y_axis_name: Name of Y axis in output.
86
+ raise_empty: Raise exception if no data is found.
87
+
88
+ Returns:
89
+ xr.Dataset: Dataset with assets as data variables.
90
+ """
74
91
  # developer info: all fancy stuff for special platforms like Sentinel-2
75
92
  # should be implemented in the respective read_np_array() methods which get
76
93
  # called by this method. No need to apply masks etc. here too.
@@ -121,6 +138,21 @@ class EOProduct(EOProductProtocol):
121
138
  apply_offset: bool = True,
122
139
  **kwargs,
123
140
  ) -> ma.MaskedArray:
141
+ """
142
+ Read assets or EO bands into a MaskedArray.
143
+
144
+ Args:
145
+ assets: List of asset names.
146
+ eo_bands: List of EO band names.
147
+ grid: Target grid.
148
+ resampling: Resampling method.
149
+ nodatavals: Nodata values.
150
+ raise_empty: Raise if empty.
151
+ apply_offset: Apply offset/scale metadata if present.
152
+
153
+ Returns:
154
+ ma.MaskedArray: Output array.
155
+ """
124
156
  assets = assets or []
125
157
  eo_bands = eo_bands or []
126
158
  bands = assets or eo_bands
@@ -182,7 +214,15 @@ def eo_bands_to_band_locations(
182
214
  role: Literal["data", "reflectance", "visual"] = "data",
183
215
  ) -> List[BandLocation]:
184
216
  """
185
- Find out location (asset and band index) of EO band.
217
+ Map EO band names to asset locations.
218
+
219
+ Args:
220
+ item: STAC Item.
221
+ eo_bands: List of common band names.
222
+ role: Functional role of the assets.
223
+
224
+ Returns:
225
+ List[BandLocation]: List of location objects.
186
226
  """
187
227
  return [find_eo_band(item, eo_band, role=role) for eo_band in eo_bands]
188
228
 
@@ -2,9 +2,10 @@ from functools import cached_property
2
2
  import json
3
3
  import logging
4
4
  from abc import ABC, abstractmethod
5
- from typing import Any, Callable, Dict, Generator, List, Optional, Type, Union
5
+ from typing import Any, Callable, Dict, Generator, List, Optional, Set, Type, Union
6
6
 
7
- from cql2 import Expr
7
+ from pygeofilter.parsers.ecql import parse as parse_ecql
8
+ from pygeofilter.backends.native.evaluate import NativeEvaluator
8
9
  from pydantic import BaseModel
9
10
  from mapchete.path import MPath, MPathLike
10
11
  from mapchete.types import Bounds
@@ -17,6 +18,8 @@ from rasterio.profiles import Profile
17
18
  from shapely.geometry.base import BaseGeometry
18
19
 
19
20
  from mapchete_eo.io.assets import get_assets, get_metadata_assets
21
+ from mapchete_eo.product import blacklist_products
22
+ from mapchete_eo.settings import mapchete_eo_settings
20
23
  from mapchete_eo.types import TimeRange
21
24
 
22
25
  logger = logging.getLogger(__name__)
@@ -47,12 +50,17 @@ class FSSpecStacIO(StacApiIO):
47
50
 
48
51
  class CollectionSearcher(ABC):
49
52
  """
50
- This class serves as a bridge between an Archive and a catalog implementation.
53
+ Bridge between a Source and a catalog implementation.
51
54
  """
52
55
 
53
56
  config_cls: Type[BaseModel]
54
57
  collection: str
55
58
  stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None
59
+ blacklist: Set[str] = (
60
+ blacklist_products(mapchete_eo_settings.blacklist)
61
+ if mapchete_eo_settings.blacklist
62
+ else set()
63
+ )
56
64
 
57
65
  def __init__(
58
66
  self,
@@ -70,17 +78,21 @@ class CollectionSearcher(ABC):
70
78
  @cached_property
71
79
  def eo_bands(self) -> List[str]: ...
72
80
 
73
- @abstractmethod
81
+ @property
82
+ def config(self) -> BaseModel:
83
+ return self.config_cls()
84
+
74
85
  @cached_property
75
- def id(self) -> str: ...
86
+ def id(self) -> str:
87
+ return self.client.id
76
88
 
77
- @abstractmethod
78
89
  @cached_property
79
- def description(self) -> str: ...
90
+ def description(self) -> str:
91
+ return self.client.description
80
92
 
81
- @abstractmethod
82
93
  @cached_property
83
- def stac_extensions(self) -> List[str]: ...
94
+ def stac_extensions(self) -> List[str]:
95
+ return self.client.stac_extensions
84
96
 
85
97
  @abstractmethod
86
98
  def search(
@@ -117,7 +129,29 @@ class StaticCollectionWriterMixin(CollectionSearcher):
117
129
  stac_io: DefaultStacIO = FSSpecStacIO(),
118
130
  progress_callback: Optional[Callable] = None,
119
131
  ) -> MPath:
120
- """Dump static version of current items."""
132
+ """
133
+ Export a static STAC catalog from the search results.
134
+
135
+ Args:
136
+ output_path: Destination directory for the static catalog.
137
+ bounds: Spatial filter bounds.
138
+ area: Spatial filter geometry.
139
+ time: Temporal filter range.
140
+ search_kwargs: Additional search arguments.
141
+ name: Catalog name.
142
+ description: Catalog description.
143
+ assets: List of assets to download.
144
+ assets_dst_resolution: Sub-sampling resolution for assets.
145
+ assets_convert_profile: Output profile for assets (e.g. for COG conversion).
146
+ copy_metadata: Whether to copy sidecar metadata files.
147
+ metadata_parser_classes: Custom parser classes for metadata.
148
+ overwrite: Overwrite existing files.
149
+ stac_io: Custom STAC IO implementation.
150
+ progress_callback: Optional function for progress reporting.
151
+
152
+ Returns:
153
+ MPath: Path to the generated catalog.json.
154
+ """
121
155
  output_path = MPath.from_inp(output_path)
122
156
  assets = assets or []
123
157
  # initialize catalog
@@ -240,9 +274,12 @@ def filter_items(
240
274
  and passed down to the individual search approaches via said config and this Function.
241
275
  """
242
276
  if query:
243
- expr = Expr(query)
277
+ ast = parse_ecql(query)
278
+ evaluator = NativeEvaluator(use_getattr=False)
279
+ filter_func = evaluator.evaluate(ast)
244
280
  for item in items:
245
- if expr.matches(item.properties):
281
+ # pystac items store metadata in 'properties'
282
+ if filter_func(item.properties):
246
283
  yield item
247
284
  else:
248
285
  yield from items
@@ -1,3 +1,7 @@
1
+ import logging
2
+
3
+
4
+ from contextlib import contextmanager
1
5
  from typing import Optional, Dict, Any
2
6
 
3
7
  from mapchete.path import MPath, MPathLike
@@ -46,18 +50,64 @@ class UTMSearchConfig(BaseModel):
46
50
  path=MPath(
47
51
  "https://sentinel-s2-l2a-stac.s3.amazonaws.com/sentinel-s2-l2a.json"
48
52
  ),
53
+ endpoint="s3://sentinel-s2-l2a-stac",
49
54
  ),
50
55
  S2_L1C=dict(
51
56
  id="sentinel-s2-l1c",
52
57
  path=MPath(
53
58
  "https://sentinel-s2-l1c-stac.s3.amazonaws.com/sentinel-s2-l1c.json"
54
59
  ),
60
+ endpoint="s3://sentinel-s2-l1c-stac",
55
61
  ),
56
62
  S1_GRD=dict(
57
63
  id="sentinel-s1-l1c",
58
64
  path=MPath(
59
65
  "https://sentinel-s1-l1c-stac.s3.amazonaws.com/sentinel-s1-l1c.json"
60
66
  ),
67
+ endpoint="s3://sentinel-s1-l1c-stac",
61
68
  ),
62
69
  )
63
70
  search_index: Optional[MPathLike] = None
71
+
72
+
73
+ @contextmanager
74
+ def patch_invalid_assets():
75
+ """
76
+ Context manager/decorator to fix pystac crash on malformed assets (strings instead of dicts).
77
+
78
+ """
79
+ try:
80
+ from pystac.extensions.file import FileExtensionHooks
81
+ except ImportError: # pragma: no cover
82
+ yield
83
+ return
84
+
85
+ logger = logging.getLogger(__name__)
86
+
87
+ _original_migrate = FileExtensionHooks.migrate
88
+
89
+ def _safe_migrate(self, obj, version, info):
90
+ if "assets" in obj and isinstance(obj["assets"], dict):
91
+ bad_keys = []
92
+ for key, asset in obj["assets"].items():
93
+ if not isinstance(asset, dict):
94
+ logger.debug(
95
+ "Removing malformed asset '%s' (type %s) from item %s",
96
+ key,
97
+ type(asset),
98
+ obj.get("id", "unknown"),
99
+ )
100
+ bad_keys.append(key)
101
+
102
+ for key in bad_keys:
103
+ del obj["assets"][key]
104
+
105
+ return _original_migrate(self, obj, version, info)
106
+
107
+ # Apply patch
108
+ FileExtensionHooks.migrate = _safe_migrate
109
+ try:
110
+ yield
111
+ finally:
112
+ # Restore original
113
+ FileExtensionHooks.migrate = _original_migrate