mapchete-eo 2025.10.0__py2.py3-none-any.whl → 2025.11.0__py2.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.
- mapchete_eo/__init__.py +1 -1
- mapchete_eo/array/convert.py +7 -1
- mapchete_eo/base.py +123 -55
- mapchete_eo/cli/options_arguments.py +11 -27
- mapchete_eo/cli/s2_brdf.py +1 -1
- mapchete_eo/cli/s2_cat_results.py +4 -20
- mapchete_eo/cli/s2_find_broken_products.py +4 -20
- mapchete_eo/cli/s2_jp2_static_catalog.py +2 -2
- mapchete_eo/cli/static_catalog.py +4 -45
- mapchete_eo/eostac.py +1 -1
- mapchete_eo/io/assets.py +7 -7
- mapchete_eo/io/items.py +37 -22
- mapchete_eo/io/levelled_cubes.py +66 -35
- mapchete_eo/io/path.py +19 -8
- mapchete_eo/io/products.py +37 -27
- mapchete_eo/platforms/sentinel2/__init__.py +1 -1
- mapchete_eo/platforms/sentinel2/_mapper_registry.py +89 -0
- mapchete_eo/platforms/sentinel2/brdf/correction.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/hls.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/models.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/protocols.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +1 -1
- mapchete_eo/platforms/sentinel2/config.py +73 -13
- mapchete_eo/platforms/sentinel2/driver.py +0 -39
- mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +6 -0
- mapchete_eo/platforms/sentinel2/{path_mappers → metadata_parser}/base.py +1 -1
- mapchete_eo/platforms/sentinel2/{path_mappers/metadata_xml.py → metadata_parser/default_path_mapper.py} +2 -2
- mapchete_eo/platforms/sentinel2/metadata_parser/models.py +78 -0
- mapchete_eo/platforms/sentinel2/{metadata_parser.py → metadata_parser/s2metadata.py} +51 -146
- mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +57 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +108 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +171 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +217 -0
- mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +22 -1
- mapchete_eo/platforms/sentinel2/processing_baseline.py +3 -0
- mapchete_eo/platforms/sentinel2/product.py +88 -23
- mapchete_eo/platforms/sentinel2/source.py +114 -0
- mapchete_eo/platforms/sentinel2/types.py +5 -0
- mapchete_eo/processes/merge_rasters.py +7 -3
- mapchete_eo/product.py +14 -9
- mapchete_eo/protocols.py +5 -0
- mapchete_eo/search/__init__.py +3 -3
- mapchete_eo/search/base.py +126 -100
- mapchete_eo/search/config.py +25 -4
- mapchete_eo/search/s2_mgrs.py +8 -9
- mapchete_eo/search/stac_search.py +111 -75
- mapchete_eo/search/stac_static.py +63 -94
- mapchete_eo/search/utm_search.py +39 -48
- mapchete_eo/settings.py +1 -0
- mapchete_eo/sort.py +16 -2
- mapchete_eo/source.py +107 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/METADATA +2 -1
- mapchete_eo-2025.11.0.dist-info/RECORD +89 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/entry_points.txt +1 -1
- mapchete_eo/archives/__init__.py +0 -0
- mapchete_eo/archives/base.py +0 -65
- mapchete_eo/geometry.py +0 -271
- mapchete_eo/known_catalogs.py +0 -42
- mapchete_eo/platforms/sentinel2/archives.py +0 -190
- mapchete_eo/platforms/sentinel2/path_mappers/__init__.py +0 -29
- mapchete_eo/platforms/sentinel2/path_mappers/earthsearch.py +0 -34
- mapchete_eo/platforms/sentinel2/path_mappers/sinergise.py +0 -105
- mapchete_eo-2025.10.0.dist-info/RECORD +0 -88
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/WHEEL +0 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import List, Callable, Dict, Any, Optional
|
|
2
|
+
|
|
3
|
+
from pystac import Item
|
|
4
|
+
|
|
5
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
6
|
+
from mapchete_eo.platforms.sentinel2.types import DataArchive, MetadataArchive
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# decorators for mapper functions using the registry pattern #
|
|
10
|
+
##############################################################
|
|
11
|
+
ID_MAPPER_REGISTRY: Dict[Any, Callable[[Item], Item]] = {}
|
|
12
|
+
STAC_METADATA_MAPPER_REGISTRY: Dict[Any, Callable[[Item], Item]] = {}
|
|
13
|
+
S2METADATA_MAPPER_REGISTRY: Dict[Any, Callable[[Item], S2Metadata]] = {}
|
|
14
|
+
|
|
15
|
+
MAPPER_REGISTRIES: Dict[str, Any] = {
|
|
16
|
+
"ID": ID_MAPPER_REGISTRY,
|
|
17
|
+
"STAC metadata": STAC_METADATA_MAPPER_REGISTRY,
|
|
18
|
+
"S2Metadata": S2METADATA_MAPPER_REGISTRY,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _register_func(registry: Dict[str, Callable], key: Any, func: Callable):
|
|
23
|
+
if key in registry:
|
|
24
|
+
raise ValueError(f"{key} already registered in {registry}")
|
|
25
|
+
registry[key] = func
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def maps_item_id(from_collections: List[str]):
|
|
29
|
+
"""
|
|
30
|
+
Decorator registering mapper to common ID.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def decorator(func):
|
|
34
|
+
# Use a tuple of the metadata as the key
|
|
35
|
+
# key = (path_type, version)
|
|
36
|
+
for collection in from_collections:
|
|
37
|
+
_register_func(registry=ID_MAPPER_REGISTRY, key=collection, func=func)
|
|
38
|
+
return func
|
|
39
|
+
|
|
40
|
+
return decorator
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def maps_stac_metadata(
|
|
44
|
+
from_collections: List[str], to_data_archives: Optional[List[DataArchive]] = None
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Decorator registering STAC metadata mapper.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def decorator(func):
|
|
51
|
+
# Use a tuple of the metadata as the key
|
|
52
|
+
for collection in from_collections:
|
|
53
|
+
if to_data_archives:
|
|
54
|
+
for data_archive in to_data_archives:
|
|
55
|
+
_register_func(
|
|
56
|
+
registry=STAC_METADATA_MAPPER_REGISTRY,
|
|
57
|
+
key=(collection, data_archive),
|
|
58
|
+
func=func,
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
_register_func(
|
|
62
|
+
registry=STAC_METADATA_MAPPER_REGISTRY,
|
|
63
|
+
key=collection,
|
|
64
|
+
func=func,
|
|
65
|
+
)
|
|
66
|
+
return func
|
|
67
|
+
|
|
68
|
+
return decorator
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def creates_s2metadata(
|
|
72
|
+
from_collections: List[str], to_metadata_archives: List[MetadataArchive]
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Decorator registering S2Metadata creator.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def decorator(func):
|
|
79
|
+
# Use a tuple of the metadata as the key
|
|
80
|
+
for collection in from_collections:
|
|
81
|
+
for metadata_archive in to_metadata_archives:
|
|
82
|
+
_register_func(
|
|
83
|
+
registry=S2METADATA_MAPPER_REGISTRY,
|
|
84
|
+
key=(collection, metadata_archive),
|
|
85
|
+
func=func,
|
|
86
|
+
)
|
|
87
|
+
return func
|
|
88
|
+
|
|
89
|
+
return decorator
|
|
@@ -13,7 +13,7 @@ from rasterio.fill import fillnodata
|
|
|
13
13
|
|
|
14
14
|
from mapchete_eo.exceptions import BRDFError
|
|
15
15
|
from mapchete_eo.platforms.sentinel2.brdf.models import BRDFModels, get_model
|
|
16
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
16
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
17
17
|
from mapchete_eo.platforms.sentinel2.types import (
|
|
18
18
|
L2ABand,
|
|
19
19
|
Resolution,
|
|
@@ -16,7 +16,7 @@ from mapchete_eo.platforms.sentinel2.brdf.protocols import (
|
|
|
16
16
|
)
|
|
17
17
|
from mapchete_eo.platforms.sentinel2.brdf.config import L2ABandFParams, ModelParameters
|
|
18
18
|
from mapchete_eo.platforms.sentinel2.brdf.sun_angle_arrays import get_sun_zenith_angles
|
|
19
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
19
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
20
20
|
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
21
21
|
|
|
22
22
|
|
|
@@ -12,7 +12,7 @@ from mapchete_eo.platforms.sentinel2.brdf.hls import HLS
|
|
|
12
12
|
from mapchete_eo.platforms.sentinel2.brdf.ross_thick import RossThick
|
|
13
13
|
|
|
14
14
|
# from mapchete_eo.platforms.sentinel2.brdf.hls2 import HLS2
|
|
15
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
15
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
16
16
|
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
@@ -6,7 +6,7 @@ from mapchete.io.raster import ReferencedRaster
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from numpy.typing import DTypeLike
|
|
8
8
|
|
|
9
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
9
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
10
10
|
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
11
11
|
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ from mapchete_eo.platforms.sentinel2.brdf.protocols import (
|
|
|
14
14
|
)
|
|
15
15
|
from mapchete_eo.platforms.sentinel2.brdf.config import L2ABandFParams, ModelParameters
|
|
16
16
|
from mapchete_eo.platforms.sentinel2.brdf.hls import _get_viewing_angles
|
|
17
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
17
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
18
18
|
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
19
19
|
|
|
20
20
|
|
|
@@ -3,7 +3,7 @@ from typing import Tuple
|
|
|
3
3
|
from fiona.transform import transform
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
|
-
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
6
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.s2metadata import S2Metadata
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_sun_zenith_angles(s2_metadata: S2Metadata) -> np.ndarray:
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional, Union
|
|
3
|
+
from typing import List, Optional, Union, Dict, Any
|
|
4
|
+
import warnings
|
|
4
5
|
|
|
5
6
|
from mapchete.path import MPathLike
|
|
6
|
-
from pydantic import
|
|
7
|
-
BaseModel,
|
|
8
|
-
ValidationError,
|
|
9
|
-
field_validator,
|
|
10
|
-
)
|
|
7
|
+
from pydantic import BaseModel, ValidationError, field_validator, model_validator
|
|
11
8
|
|
|
12
9
|
from mapchete_eo.base import BaseDriverConfig
|
|
13
10
|
from mapchete_eo.io.path import ProductPathGenerationMethod
|
|
14
|
-
from mapchete_eo.platforms.sentinel2.archives import ArchiveClsFromString, AWSL2ACOGv1
|
|
15
11
|
from mapchete_eo.platforms.sentinel2.brdf.config import BRDFModels
|
|
12
|
+
from mapchete_eo.platforms.sentinel2.preconfigured_sources import (
|
|
13
|
+
KNOWN_SOURCES,
|
|
14
|
+
DEPRECATED_ARCHIVES,
|
|
15
|
+
)
|
|
16
|
+
from mapchete_eo.platforms.sentinel2.source import Sentinel2Source
|
|
16
17
|
from mapchete_eo.platforms.sentinel2.types import (
|
|
17
18
|
CloudType,
|
|
18
19
|
ProductQIMaskResolution,
|
|
@@ -23,6 +24,9 @@ from mapchete_eo.search.config import StacSearchConfig
|
|
|
23
24
|
from mapchete_eo.types import TimeRange
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
default_source = Sentinel2Source.model_validate(KNOWN_SOURCES["EarthSearch"])
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
class BRDFModelConfig(BaseModel):
|
|
27
31
|
model: BRDFModels = BRDFModels.HLS
|
|
28
32
|
bands: List[str] = ["blue", "green", "red", "nir"]
|
|
@@ -47,7 +51,7 @@ class BRDFSCLClassConfig(BRDFModelConfig):
|
|
|
47
51
|
out.append(value)
|
|
48
52
|
elif isinstance(value, str):
|
|
49
53
|
out.append(SceneClassification[value])
|
|
50
|
-
else:
|
|
54
|
+
else: # pragma: no cover
|
|
51
55
|
raise ValidationError("value must be mappable to SceneClassification")
|
|
52
56
|
return out
|
|
53
57
|
|
|
@@ -107,10 +111,18 @@ class CacheConfig(BaseModel):
|
|
|
107
111
|
class Sentinel2DriverConfig(BaseDriverConfig):
|
|
108
112
|
format: str = "Sentinel-2"
|
|
109
113
|
time: Union[TimeRange, List[TimeRange]]
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
|
|
115
|
+
# new
|
|
116
|
+
source: List[Sentinel2Source] = [default_source]
|
|
117
|
+
|
|
118
|
+
# deprecated
|
|
119
|
+
# for backwards compatibility, archive should be converted to
|
|
120
|
+
# catalog & data_archive
|
|
121
|
+
# archive: ArchiveClsFromString = AWSL2ACOGv1
|
|
122
|
+
# cat_baseurl: Optional[MPathLike] = None
|
|
112
123
|
search_index: Optional[MPathLike] = None
|
|
113
|
-
|
|
124
|
+
|
|
125
|
+
# custom params
|
|
114
126
|
stac_config: StacSearchConfig = StacSearchConfig()
|
|
115
127
|
first_granule_only: bool = False
|
|
116
128
|
utm_zone: Optional[int] = None
|
|
@@ -118,6 +130,54 @@ class Sentinel2DriverConfig(BaseDriverConfig):
|
|
|
118
130
|
brdf: Optional[BRDFConfig] = None
|
|
119
131
|
cache: Optional[CacheConfig] = None
|
|
120
132
|
|
|
133
|
+
@model_validator(mode="before")
|
|
134
|
+
def deprecated_values(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
135
|
+
archive = values.pop("archive", None)
|
|
136
|
+
if archive:
|
|
137
|
+
warnings.warn(
|
|
138
|
+
"'archive' will be deprecated soon. Please use 'source'.",
|
|
139
|
+
category=DeprecationWarning,
|
|
140
|
+
stacklevel=2,
|
|
141
|
+
)
|
|
142
|
+
if values.get("source") is None:
|
|
143
|
+
values["source"] = DEPRECATED_ARCHIVES[archive]
|
|
144
|
+
|
|
145
|
+
cat_baseurl = values.pop("cat_baseurl", None)
|
|
146
|
+
if cat_baseurl: # pragma: no cover
|
|
147
|
+
warnings.warn(
|
|
148
|
+
"'cat_baseurl' will be deprecated soon. Please use 'catalog_type=static' in the source.",
|
|
149
|
+
category=DeprecationWarning,
|
|
150
|
+
stacklevel=2,
|
|
151
|
+
)
|
|
152
|
+
if values.get("source", []):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"deprecated cat_baseurl field found alongside sources."
|
|
155
|
+
)
|
|
156
|
+
values["source"] = [dict(collection=cat_baseurl, catalog_type="static")]
|
|
157
|
+
|
|
158
|
+
# add default source if necessary
|
|
159
|
+
sources = values.get("source", [])
|
|
160
|
+
if not sources:
|
|
161
|
+
values["source"] = [default_source.model_dump(exclude_none=True)]
|
|
162
|
+
|
|
163
|
+
max_cloud_cover = values.pop("max_cloud_cover", None)
|
|
164
|
+
if max_cloud_cover: # pragma: no cover
|
|
165
|
+
warnings.warn(
|
|
166
|
+
"'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
|
|
167
|
+
category=DeprecationWarning,
|
|
168
|
+
stacklevel=2,
|
|
169
|
+
)
|
|
170
|
+
updated_sources = []
|
|
171
|
+
for source in values.get("source", []):
|
|
172
|
+
if source.get("query") is not None:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"deprecated max_cloud_cover is set but also a query field is given in {source}"
|
|
175
|
+
)
|
|
176
|
+
source["query"] = f"eo:cloud_cover<={max_cloud_cover}"
|
|
177
|
+
updated_sources.append(source)
|
|
178
|
+
values["source"] = updated_sources
|
|
179
|
+
return values
|
|
180
|
+
|
|
121
181
|
|
|
122
182
|
class MaskConfig(BaseModel):
|
|
123
183
|
# mask by footprint geometry
|
|
@@ -160,7 +220,7 @@ class MaskConfig(BaseModel):
|
|
|
160
220
|
out.append(value)
|
|
161
221
|
elif isinstance(value, str):
|
|
162
222
|
out.append(SceneClassification[value])
|
|
163
|
-
else:
|
|
223
|
+
else: # pragma: no cover
|
|
164
224
|
raise ValidationError("value must be mappable to SceneClassification")
|
|
165
225
|
return out
|
|
166
226
|
|
|
@@ -175,7 +235,7 @@ class MaskConfig(BaseModel):
|
|
|
175
235
|
elif isinstance(config, dict):
|
|
176
236
|
return MaskConfig(**config)
|
|
177
237
|
|
|
178
|
-
else:
|
|
238
|
+
else: # pragma: no cover
|
|
179
239
|
raise TypeError(
|
|
180
240
|
f"mask configuration should either be a dictionary or a MaskConfig object, not {config}"
|
|
181
241
|
)
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
from typing import Optional, List, Tuple
|
|
2
2
|
|
|
3
|
-
from mapchete.geometry import reproject_geometry
|
|
4
|
-
from mapchete.path import MPath
|
|
5
3
|
from mapchete.types import NodataVal
|
|
6
4
|
from rasterio.enums import Resampling
|
|
7
5
|
|
|
8
6
|
from mapchete_eo import base
|
|
9
|
-
from mapchete_eo.archives.base import Archive
|
|
10
7
|
from mapchete_eo.platforms.sentinel2.config import Sentinel2DriverConfig
|
|
11
8
|
from mapchete_eo.platforms.sentinel2.preprocessing_tasks import parse_s2_product
|
|
12
|
-
from mapchete_eo.search.stac_static import STACStaticCatalog
|
|
13
|
-
from mapchete_eo.settings import mapchete_eo_settings
|
|
14
9
|
from mapchete_eo.types import MergeMethod
|
|
15
10
|
|
|
16
11
|
METADATA: dict = {
|
|
@@ -42,37 +37,3 @@ class InputData(base.InputData):
|
|
|
42
37
|
driver_config_model = Sentinel2DriverConfig
|
|
43
38
|
params: Sentinel2DriverConfig
|
|
44
39
|
input_tile_cls = Sentinel2Cube
|
|
45
|
-
|
|
46
|
-
def set_archive(self, base_dir: MPath):
|
|
47
|
-
if self.params.cat_baseurl:
|
|
48
|
-
self.archive = Archive(
|
|
49
|
-
catalog=STACStaticCatalog(
|
|
50
|
-
baseurl=MPath(self.params.cat_baseurl).absolute_path(
|
|
51
|
-
base_dir=base_dir
|
|
52
|
-
),
|
|
53
|
-
),
|
|
54
|
-
area=self.bbox(mapchete_eo_settings.default_catalog_crs),
|
|
55
|
-
time=self.time,
|
|
56
|
-
search_kwargs=dict(max_cloud_cover=self.params.max_cloud_cover),
|
|
57
|
-
)
|
|
58
|
-
elif self.params.archive:
|
|
59
|
-
catalog_area = reproject_geometry(
|
|
60
|
-
self.area,
|
|
61
|
-
src_crs=self.crs,
|
|
62
|
-
dst_crs=mapchete_eo_settings.default_catalog_crs,
|
|
63
|
-
)
|
|
64
|
-
self.archive = self.params.archive(
|
|
65
|
-
time=self.time,
|
|
66
|
-
bounds=catalog_area.bounds,
|
|
67
|
-
area=catalog_area,
|
|
68
|
-
search_kwargs=dict(
|
|
69
|
-
search_index=(
|
|
70
|
-
MPath(self.params.search_index).absolute_path(base_dir=base_dir)
|
|
71
|
-
if self.params.search_index
|
|
72
|
-
else None
|
|
73
|
-
),
|
|
74
|
-
max_cloud_cover=self.params.max_cloud_cover,
|
|
75
|
-
),
|
|
76
|
-
)
|
|
77
|
-
else:
|
|
78
|
-
raise ValueError("either 'archive' or 'cat_baseurl' or both is required.")
|
|
@@ -11,7 +11,7 @@ from typing import Optional
|
|
|
11
11
|
from mapchete.path import MPath
|
|
12
12
|
|
|
13
13
|
from mapchete_eo.io import open_xml
|
|
14
|
-
from mapchete_eo.platforms.sentinel2.
|
|
14
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser.base import S2MetadataPathMapper
|
|
15
15
|
from mapchete_eo.platforms.sentinel2.processing_baseline import ProcessingBaseline
|
|
16
16
|
from mapchete_eo.platforms.sentinel2.types import (
|
|
17
17
|
BandQI,
|
|
@@ -23,7 +23,7 @@ from mapchete_eo.platforms.sentinel2.types import (
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class XMLMapper(
|
|
26
|
+
class XMLMapper(S2MetadataPathMapper):
|
|
27
27
|
def __init__(
|
|
28
28
|
self, metadata_xml: MPath, xml_root: Optional[Element] = None, **kwargs
|
|
29
29
|
):
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.ma as ma
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from mapchete.io.raster import ReferencedRaster
|
|
11
|
+
from rasterio.fill import fillnodata
|
|
12
|
+
|
|
13
|
+
from mapchete_eo.exceptions import CorruptedProductMetadata
|
|
14
|
+
from mapchete_eo.platforms.sentinel2.types import (
|
|
15
|
+
SunAngle,
|
|
16
|
+
ViewAngle,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SunAngleData(BaseModel):
|
|
23
|
+
model_config = dict(arbitrary_types_allowed=True)
|
|
24
|
+
raster: ReferencedRaster
|
|
25
|
+
mean: float
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SunAnglesData(BaseModel):
|
|
29
|
+
azimuth: SunAngleData
|
|
30
|
+
zenith: SunAngleData
|
|
31
|
+
|
|
32
|
+
def get_angle(self, angle: SunAngle) -> SunAngleData:
|
|
33
|
+
if angle == SunAngle.azimuth:
|
|
34
|
+
return self.azimuth
|
|
35
|
+
elif angle == SunAngle.zenith:
|
|
36
|
+
return self.zenith
|
|
37
|
+
else:
|
|
38
|
+
raise KeyError(f"unknown angle: {angle}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ViewingIncidenceAngle(BaseModel):
|
|
42
|
+
model_config = dict(arbitrary_types_allowed=True)
|
|
43
|
+
detectors: Dict[int, ReferencedRaster]
|
|
44
|
+
mean: float
|
|
45
|
+
|
|
46
|
+
def merge_detectors(
|
|
47
|
+
self, fill_edges: bool = True, smoothing_iterations: int = 3
|
|
48
|
+
) -> ReferencedRaster:
|
|
49
|
+
if not self.detectors:
|
|
50
|
+
raise CorruptedProductMetadata("no viewing incidence angles available")
|
|
51
|
+
sample = next(iter(self.detectors.values()))
|
|
52
|
+
with warnings.catch_warnings():
|
|
53
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
54
|
+
merged = np.nanmean(
|
|
55
|
+
np.stack([raster.data for raster in self.detectors.values()]), axis=0
|
|
56
|
+
)
|
|
57
|
+
if fill_edges:
|
|
58
|
+
merged = fillnodata(
|
|
59
|
+
ma.masked_invalid(merged), smoothing_iterations=smoothing_iterations
|
|
60
|
+
)
|
|
61
|
+
return ReferencedRaster.from_array_like(
|
|
62
|
+
array_like=ma.masked_invalid(merged),
|
|
63
|
+
transform=sample.transform,
|
|
64
|
+
crs=sample.crs,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ViewingIncidenceAngles(BaseModel):
|
|
69
|
+
azimuth: ViewingIncidenceAngle
|
|
70
|
+
zenith: ViewingIncidenceAngle
|
|
71
|
+
|
|
72
|
+
def get_angle(self, angle: ViewAngle) -> ViewingIncidenceAngle:
|
|
73
|
+
if angle == ViewAngle.azimuth:
|
|
74
|
+
return self.azimuth
|
|
75
|
+
elif angle == ViewAngle.zenith:
|
|
76
|
+
return self.zenith
|
|
77
|
+
else:
|
|
78
|
+
raise KeyError(f"unknown angle: {angle}")
|