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.
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/.gitignore +12 -8
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/LICENSE +1 -1
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/PKG-INFO +10 -8
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/README.rst +2 -2
- mapchete_eo-2026.2.0/mapchete_eo/__init__.py +1 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/base.py +49 -5
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/compositing.py +12 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/assets.py +15 -11
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/driver.py +5 -1
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/masks.py +6 -2
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +4 -1
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/product.py +15 -2
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/product.py +43 -3
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/base.py +49 -12
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/config.py +50 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/stac_search.py +20 -29
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/stac_static.py +4 -12
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/utm_search.py +24 -15
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/sort.py +1 -3
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/source.py +3 -1
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/time.py +12 -3
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/types.py +6 -3
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/pyproject.toml +25 -7
- mapchete_eo-2025.11.0/mapchete_eo/__init__.py +0 -1
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/buffer.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/color.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/array/convert.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/blacklist.txt +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/bounds.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/options_arguments.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_brdf.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_cat_results.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_find_broken_products.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_jp2_static_catalog.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_mask.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_mgrs.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_rgb.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/s2_verify.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/cli/static_catalog.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/eostac.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/exceptions.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/blend_functions.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/color_correction.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/dtype_scale.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/fillnodata.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/filters.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/linear_normalization.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/image_operations/sigmoidal.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/items.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/levelled_cubes.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/path.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/products.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/io/profiles.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/_mapper_registry.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/config.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/correction.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/hls.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/models.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/protocols.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/config.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/base.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/models.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/processing_baseline.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/source.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/platforms/sentinel2/types.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/config.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/dtype_scale.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/eo_to_xarray.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/processes/merge_rasters.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/protocols.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/__init__.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/search/s2_mgrs.py +0 -0
- {mapchete_eo-2025.11.0 → mapchete_eo-2026.2.0}/mapchete_eo/settings.py +0 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
*.egg-info
|
|
3
|
-
*.pyc
|
|
1
|
+
build/
|
|
4
2
|
.cache
|
|
5
|
-
htmlcov
|
|
6
3
|
.coverage
|
|
7
4
|
.coverage.*
|
|
8
|
-
build/
|
|
9
5
|
dist/
|
|
10
|
-
|
|
6
|
+
docs/build/html
|
|
7
|
+
*.egg-info
|
|
8
|
+
.eggs
|
|
9
|
+
examples/sentinel-2*/
|
|
11
10
|
*.gfs
|
|
12
|
-
|
|
11
|
+
htmlcov
|
|
12
|
+
.mypy_cache
|
|
13
|
+
*.pyc
|
|
13
14
|
__pycache__
|
|
14
|
-
|
|
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 -
|
|
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:
|
|
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:
|
|
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-
|
|
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
|
|
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")
|
|
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
|
-
|
|
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
|
-
).
|
|
97
|
-
|
|
96
|
+
).array
|
|
98
97
|
if apply_offset and band_properties.offset:
|
|
99
|
-
|
|
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
|
-
|
|
113
|
+
array[~array.mask] = (
|
|
109
114
|
(
|
|
110
|
-
((
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
"""
|
|
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
|
-
|
|
277
|
+
ast = parse_ecql(query)
|
|
278
|
+
evaluator = NativeEvaluator(use_getattr=False)
|
|
279
|
+
filter_func = evaluator.evaluate(ast)
|
|
244
280
|
for item in items:
|
|
245
|
-
|
|
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
|