mapchete-eo 2025.10.1__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/base.py +94 -54
- 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 +36 -23
- mapchete_eo/io/path.py +19 -8
- mapchete_eo/io/products.py +22 -24
- 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 -144
- 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 +83 -18
- mapchete_eo/platforms/sentinel2/source.py +114 -0
- mapchete_eo/platforms/sentinel2/types.py +5 -0
- mapchete_eo/product.py +14 -8
- mapchete_eo/protocols.py +5 -0
- mapchete_eo/search/__init__.py +3 -3
- mapchete_eo/search/base.py +105 -92
- mapchete_eo/search/config.py +25 -4
- mapchete_eo/search/s2_mgrs.py +8 -9
- mapchete_eo/search/stac_search.py +96 -77
- mapchete_eo/search/stac_static.py +47 -91
- mapchete_eo/search/utm_search.py +36 -49
- mapchete_eo/settings.py +1 -0
- mapchete_eo/sort.py +4 -6
- mapchete_eo/source.py +107 -0
- {mapchete_eo-2025.10.1.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.1.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.1.dist-info/RECORD +0 -88
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/WHEEL +0 -0
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
from functools import cached_property
|
|
2
2
|
import logging
|
|
3
3
|
import warnings
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Dict, Generator, List, Optional, Union
|
|
5
5
|
|
|
6
6
|
from mapchete import Bounds
|
|
7
7
|
from mapchete.types import BoundsLike
|
|
8
8
|
from pystac import Item, Catalog, Collection
|
|
9
9
|
from mapchete.io.vector import bounds_intersect
|
|
10
|
-
from mapchete.path import MPathLike
|
|
11
10
|
from pystac.stac_io import StacIO
|
|
12
|
-
from pystac_client import
|
|
11
|
+
from pystac_client import CollectionClient
|
|
13
12
|
from shapely.geometry import shape
|
|
14
13
|
from shapely.geometry.base import BaseGeometry
|
|
15
14
|
|
|
16
15
|
from mapchete_eo.search.base import (
|
|
17
|
-
|
|
16
|
+
CollectionSearcher,
|
|
18
17
|
FSSpecStacIO,
|
|
19
|
-
|
|
18
|
+
StaticCollectionWriterMixin,
|
|
20
19
|
filter_items,
|
|
21
20
|
)
|
|
22
21
|
from mapchete_eo.search.config import StacStaticConfig
|
|
@@ -29,21 +28,42 @@ logger = logging.getLogger(__name__)
|
|
|
29
28
|
StacIO.set_default(FSSpecStacIO)
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
class
|
|
31
|
+
class STACStaticCollection(StaticCollectionWriterMixin, CollectionSearcher):
|
|
33
32
|
config_cls = StacStaticConfig
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None,
|
|
39
|
-
):
|
|
40
|
-
self.client = Client.from_file(str(baseurl), stac_io=FSSpecStacIO())
|
|
41
|
-
self.collections = [c.id for c in self.client.get_children()]
|
|
42
|
-
self.stac_item_modifiers = stac_item_modifiers
|
|
34
|
+
@cached_property
|
|
35
|
+
def client(self) -> CollectionClient:
|
|
36
|
+
return CollectionClient.from_file(str(self.collection), stac_io=FSSpecStacIO())
|
|
43
37
|
|
|
44
38
|
@cached_property
|
|
45
39
|
def eo_bands(self) -> List[str]:
|
|
46
|
-
|
|
40
|
+
eo_bands = self.client.extra_fields.get("properties", {}).get("eo:bands")
|
|
41
|
+
if eo_bands:
|
|
42
|
+
return eo_bands
|
|
43
|
+
else:
|
|
44
|
+
warnings.warn(
|
|
45
|
+
"Unable to read eo:bands definition from collection. "
|
|
46
|
+
"Trying now to get information from assets ..."
|
|
47
|
+
)
|
|
48
|
+
# see if eo:bands can be found in properties
|
|
49
|
+
try:
|
|
50
|
+
item = next(self.client.get_items(recursive=True))
|
|
51
|
+
eo_bands = item.properties.get("eo:bands")
|
|
52
|
+
if eo_bands:
|
|
53
|
+
return eo_bands
|
|
54
|
+
|
|
55
|
+
# look through the assets and collect eo:bands
|
|
56
|
+
out = {}
|
|
57
|
+
for asset in item.assets.values():
|
|
58
|
+
for eo_band in asset.extra_fields.get("eo:bands", []):
|
|
59
|
+
out[eo_band["name"]] = eo_band
|
|
60
|
+
if out:
|
|
61
|
+
return [v for v in out.values()]
|
|
62
|
+
except StopIteration:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
logger.debug("cannot find eo:bands definition")
|
|
66
|
+
return []
|
|
47
67
|
|
|
48
68
|
@cached_property
|
|
49
69
|
def id(self) -> str:
|
|
@@ -62,16 +82,13 @@ class STACStaticCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
62
82
|
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
|
|
63
83
|
bounds: Optional[BoundsLike] = None,
|
|
64
84
|
area: Optional[BaseGeometry] = None,
|
|
85
|
+
query: Optional[str] = None,
|
|
65
86
|
search_kwargs: Optional[Dict[str, Any]] = None,
|
|
66
87
|
) -> Generator[Item, None, None]:
|
|
67
|
-
config = self.config_cls(**search_kwargs or {})
|
|
68
88
|
if area is None and bounds:
|
|
69
89
|
bounds = Bounds.from_inp(bounds)
|
|
70
90
|
area = shape(bounds)
|
|
71
|
-
for item in filter_items(
|
|
72
|
-
self._raw_search(time=time, area=area),
|
|
73
|
-
max_cloud_cover=config.max_cloud_cover,
|
|
74
|
-
):
|
|
91
|
+
for item in filter_items(self._raw_search(time=time, area=area), query=query):
|
|
75
92
|
yield item
|
|
76
93
|
|
|
77
94
|
def _raw_search(
|
|
@@ -82,83 +99,22 @@ class STACStaticCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
82
99
|
if area is not None and area.is_empty:
|
|
83
100
|
return
|
|
84
101
|
logger.debug("iterate through children")
|
|
85
|
-
|
|
86
|
-
if time:
|
|
87
|
-
for time_range in time if isinstance(time, list) else [time]:
|
|
88
|
-
for item in _all_intersecting_items(
|
|
89
|
-
collection,
|
|
90
|
-
area=area,
|
|
91
|
-
time_range=time_range,
|
|
92
|
-
):
|
|
93
|
-
item.make_asset_hrefs_absolute()
|
|
94
|
-
yield item
|
|
95
|
-
else:
|
|
102
|
+
if time:
|
|
103
|
+
for time_range in time if isinstance(time, list) else [time]:
|
|
96
104
|
for item in _all_intersecting_items(
|
|
97
|
-
|
|
105
|
+
self.client,
|
|
98
106
|
area=area,
|
|
107
|
+
time_range=time_range,
|
|
99
108
|
):
|
|
100
109
|
item.make_asset_hrefs_absolute()
|
|
101
110
|
yield item
|
|
102
|
-
|
|
103
|
-
def _eo_bands(self) -> List[str]:
|
|
104
|
-
for collection in self.client.get_children():
|
|
105
|
-
eo_bands = collection.extra_fields.get("properties", {}).get("eo:bands")
|
|
106
|
-
if eo_bands:
|
|
107
|
-
return eo_bands
|
|
108
|
-
else:
|
|
109
|
-
warnings.warn(
|
|
110
|
-
"Unable to read eo:bands definition from collections. "
|
|
111
|
-
"Trying now to get information from assets ..."
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# see if eo:bands can be found in properties
|
|
115
|
-
item = _get_first_item(self.client.get_children())
|
|
116
|
-
eo_bands = item.properties.get("eo:bands")
|
|
117
|
-
if eo_bands:
|
|
118
|
-
return eo_bands
|
|
119
|
-
|
|
120
|
-
# look through the assets and collect eo:bands
|
|
121
|
-
out = {}
|
|
122
|
-
for asset in item.assets.values():
|
|
123
|
-
for eo_band in asset.extra_fields.get("eo:bands", []):
|
|
124
|
-
out[eo_band["name"]] = eo_band
|
|
125
|
-
if out:
|
|
126
|
-
return [v for v in out.values()]
|
|
127
|
-
|
|
128
|
-
logger.debug("cannot find eo:bands definition")
|
|
129
|
-
return []
|
|
130
|
-
|
|
131
|
-
def get_collections(
|
|
132
|
-
self,
|
|
133
|
-
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
|
|
134
|
-
bounds: Optional[BoundsLike] = None,
|
|
135
|
-
area: Optional[BaseGeometry] = None,
|
|
136
|
-
):
|
|
137
|
-
if area is None and bounds is not None:
|
|
138
|
-
area = Bounds.from_inp(bounds).geometry
|
|
139
|
-
for collection in self.client.get_children():
|
|
140
|
-
if time:
|
|
141
|
-
for time_range in time if isinstance(time, list) else [time]:
|
|
142
|
-
if _collection_extent_intersects(
|
|
143
|
-
collection,
|
|
144
|
-
area=area,
|
|
145
|
-
time_range=time_range,
|
|
146
|
-
):
|
|
147
|
-
yield collection
|
|
148
|
-
else:
|
|
149
|
-
if _collection_extent_intersects(collection, area=area):
|
|
150
|
-
yield collection
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def _get_first_item(collections):
|
|
154
|
-
for collection in collections:
|
|
155
|
-
for item in collection.get_all_items():
|
|
156
|
-
return item
|
|
157
111
|
else:
|
|
158
|
-
for
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
112
|
+
for item in _all_intersecting_items(
|
|
113
|
+
self.client,
|
|
114
|
+
area=area,
|
|
115
|
+
):
|
|
116
|
+
item.make_asset_hrefs_absolute()
|
|
117
|
+
yield item
|
|
162
118
|
|
|
163
119
|
|
|
164
120
|
def _all_intersecting_items(
|
mapchete_eo/search/utm_search.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from functools import cached_property
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Dict, Generator, List, Optional, Set, Union
|
|
5
5
|
|
|
6
6
|
from mapchete.io.vector import fiona_open
|
|
7
7
|
from mapchete.path import MPath, MPathLike
|
|
@@ -15,8 +15,8 @@ from shapely.geometry.base import BaseGeometry
|
|
|
15
15
|
from mapchete_eo.exceptions import ItemGeometryError
|
|
16
16
|
from mapchete_eo.product import blacklist_products
|
|
17
17
|
from mapchete_eo.search.base import (
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
CollectionSearcher,
|
|
19
|
+
StaticCollectionWriterMixin,
|
|
20
20
|
filter_items,
|
|
21
21
|
)
|
|
22
22
|
from mapchete_eo.search.config import UTMSearchConfig
|
|
@@ -28,7 +28,7 @@ from mapchete_eo.types import TimeRange
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class UTMSearchCatalog(
|
|
31
|
+
class UTMSearchCatalog(StaticCollectionWriterMixin, CollectionSearcher):
|
|
32
32
|
endpoint: str
|
|
33
33
|
id: str
|
|
34
34
|
day_subdir_schema: str
|
|
@@ -42,36 +42,41 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
42
42
|
)
|
|
43
43
|
config_cls = UTMSearchConfig
|
|
44
44
|
|
|
45
|
-
def __init__(
|
|
46
|
-
self,
|
|
47
|
-
endpoint: Optional[MPathLike] = None,
|
|
48
|
-
collections: List[str] = [],
|
|
49
|
-
stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None,
|
|
50
|
-
):
|
|
51
|
-
self.endpoint = endpoint or self.endpoint
|
|
52
|
-
if len(collections) == 0: # pragma: no cover
|
|
53
|
-
raise ValueError("no collections provided")
|
|
54
|
-
self.collections = collections
|
|
55
|
-
self.stac_item_modifiers = stac_item_modifiers
|
|
56
|
-
|
|
57
45
|
@cached_property
|
|
58
46
|
def eo_bands(self) -> List[str]: # pragma: no cover
|
|
59
|
-
|
|
47
|
+
for (
|
|
48
|
+
collection_properties
|
|
49
|
+
) in UTMSearchConfig().sinergise_aws_collections.values():
|
|
50
|
+
if collection_properties["id"] == self.collection.split("/")[-1]:
|
|
51
|
+
collection = Collection.from_dict(
|
|
52
|
+
collection_properties["path"].read_json()
|
|
53
|
+
)
|
|
54
|
+
if collection:
|
|
55
|
+
summary = collection.summaries.to_dict()
|
|
56
|
+
if "eo:bands" in summary:
|
|
57
|
+
return summary["eo:bands"]
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"cannot find collection {collection}")
|
|
60
|
+
else:
|
|
61
|
+
logger.debug(
|
|
62
|
+
"cannot find eo:bands definition from collection %s",
|
|
63
|
+
self.collection,
|
|
64
|
+
)
|
|
65
|
+
return []
|
|
60
66
|
|
|
61
67
|
def search(
|
|
62
68
|
self,
|
|
63
69
|
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
|
|
64
70
|
bounds: Optional[BoundsLike] = None,
|
|
65
71
|
area: Optional[BaseGeometry] = None,
|
|
72
|
+
query: Optional[str] = None,
|
|
66
73
|
search_kwargs: Optional[Dict[str, Any]] = None,
|
|
67
74
|
) -> Generator[Item, None, None]:
|
|
68
|
-
config = self.config_cls(**search_kwargs or {})
|
|
69
|
-
if bounds:
|
|
70
|
-
bounds = Bounds.from_inp(bounds)
|
|
71
|
-
|
|
72
75
|
for item in filter_items(
|
|
73
|
-
self._raw_search(
|
|
74
|
-
|
|
76
|
+
self._raw_search(
|
|
77
|
+
time=time, bounds=Bounds.from_inp(bounds) if bounds else None, area=area
|
|
78
|
+
),
|
|
79
|
+
query=query,
|
|
75
80
|
):
|
|
76
81
|
yield item
|
|
77
82
|
|
|
@@ -92,7 +97,12 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
92
97
|
elif bounds is not None:
|
|
93
98
|
bounds = Bounds.from_inp(bounds)
|
|
94
99
|
area = shape(bounds)
|
|
95
|
-
|
|
100
|
+
|
|
101
|
+
# Cleaner time list in case None present as time (undefined)
|
|
102
|
+
time_list: list[TimeRange] = (
|
|
103
|
+
[t for t in time if t is not None] if isinstance(time, list) else [time]
|
|
104
|
+
)
|
|
105
|
+
for time_range in time_list:
|
|
96
106
|
start_time = (
|
|
97
107
|
time_range.start
|
|
98
108
|
if isinstance(time_range.start, datetime.date)
|
|
@@ -151,28 +161,6 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
151
161
|
elif area.intersects(shape(item.geometry)):
|
|
152
162
|
yield item
|
|
153
163
|
|
|
154
|
-
def _eo_bands(self) -> list:
|
|
155
|
-
for collection_name in self.collections:
|
|
156
|
-
for (
|
|
157
|
-
collection_properties
|
|
158
|
-
) in UTMSearchConfig().sinergise_aws_collections.values():
|
|
159
|
-
if collection_properties["id"] == collection_name:
|
|
160
|
-
collection = Collection.from_dict(
|
|
161
|
-
collection_properties["path"].read_json()
|
|
162
|
-
)
|
|
163
|
-
if collection:
|
|
164
|
-
summary = collection.summaries.to_dict()
|
|
165
|
-
if "eo:bands" in summary:
|
|
166
|
-
return summary["eo:bands"]
|
|
167
|
-
else:
|
|
168
|
-
raise ValueError(f"cannot find collection {collection}")
|
|
169
|
-
else:
|
|
170
|
-
logger.debug(
|
|
171
|
-
"cannot find eo:bands definition from collections %s",
|
|
172
|
-
self.collections,
|
|
173
|
-
)
|
|
174
|
-
return []
|
|
175
|
-
|
|
176
164
|
def get_collections(self):
|
|
177
165
|
"""
|
|
178
166
|
yeild transformed collection from:
|
|
@@ -182,9 +170,8 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
|
|
|
182
170
|
"""
|
|
183
171
|
for collection_properties in self.config.sinergise_aws_collections.values():
|
|
184
172
|
collection = Collection.from_dict(collection_properties["path"].read_json())
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
yield collection
|
|
173
|
+
if self.collection.split("/")[-1] == collection.id:
|
|
174
|
+
yield collection
|
|
188
175
|
|
|
189
176
|
|
|
190
177
|
def items_from_static_index(
|
mapchete_eo/settings.py
CHANGED
|
@@ -16,6 +16,7 @@ class Settings(BaseSettings):
|
|
|
16
16
|
default_cache_location: MPathLike = MPath("s3://eox-mhub-cache/")
|
|
17
17
|
default_catalog_crs: CRS = CRS.from_epsg(4326)
|
|
18
18
|
blacklist: Optional[MPathLike] = None
|
|
19
|
+
lazy_load_stac_items: bool = True
|
|
19
20
|
|
|
20
21
|
# read from environment
|
|
21
22
|
model_config = SettingsConfigDict(env_prefix="MAPCHETE_EO_")
|
mapchete_eo/sort.py
CHANGED
|
@@ -5,10 +5,8 @@ This module holds all code required to sort products or slices.
|
|
|
5
5
|
from typing import Callable, List, Optional
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
|
-
from pystac import Item
|
|
9
8
|
|
|
10
|
-
from mapchete_eo.
|
|
11
|
-
from mapchete_eo.protocols import DateTimeProtocol
|
|
9
|
+
from mapchete_eo.protocols import DateTimeProtocol, GetPropertyProtocol
|
|
12
10
|
from mapchete_eo.time import timedelta, to_datetime
|
|
13
11
|
from mapchete_eo.types import DateTimeLike
|
|
14
12
|
|
|
@@ -51,11 +49,11 @@ class TargetDateSort(SortMethodConfig):
|
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
def sort_objects_by_cloud_cover(
|
|
54
|
-
objects: List[
|
|
55
|
-
) -> List[
|
|
52
|
+
objects: List[GetPropertyProtocol], reverse: bool = False
|
|
53
|
+
) -> List[GetPropertyProtocol]:
|
|
56
54
|
if len(objects) == 0: # pragma: no cover
|
|
57
55
|
return objects
|
|
58
|
-
objects.sort(key=lambda x:
|
|
56
|
+
objects.sort(key=lambda x: x.get_property("eo:cloud_cover"), reverse=reverse)
|
|
59
57
|
return objects
|
|
60
58
|
|
|
61
59
|
|
mapchete_eo/source.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from functools import cached_property
|
|
2
|
+
from typing import Any, Dict, List, Literal, Optional, Generator, Union, Callable
|
|
3
|
+
|
|
4
|
+
from mapchete.bounds import Bounds
|
|
5
|
+
from mapchete.path import MPath
|
|
6
|
+
from mapchete.types import BoundsLike, CRSLike, MPathLike
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
8
|
+
from pystac import Item
|
|
9
|
+
from shapely.geometry.base import BaseGeometry
|
|
10
|
+
from shapely.errors import GEOSException
|
|
11
|
+
|
|
12
|
+
from mapchete_eo.exceptions import ItemGeometryError
|
|
13
|
+
from mapchete_eo.search.base import CollectionSearcher
|
|
14
|
+
from mapchete_eo.search import STACSearchCollection, STACStaticCollection
|
|
15
|
+
from mapchete_eo.settings import mapchete_eo_settings
|
|
16
|
+
from mapchete_eo.types import TimeRange
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Source(BaseModel):
|
|
20
|
+
"""All information required to consume EO products."""
|
|
21
|
+
|
|
22
|
+
collection: str
|
|
23
|
+
catalog_crs: Optional[CRSLike] = mapchete_eo_settings.default_catalog_crs
|
|
24
|
+
query: Optional[str] = None
|
|
25
|
+
area: Optional[Union[MPathLike, dict, type[BaseGeometry]]] = None
|
|
26
|
+
bounds: Optional[BoundsLike] = None
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def item_modifier_funcs(self) -> List[Callable]:
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
@cached_property
|
|
35
|
+
def catalog_type(self) -> Literal["search", "static"]:
|
|
36
|
+
# TODO: stupid test but probably sufficient
|
|
37
|
+
return "static" if self.collection.endswith(".json") else "search"
|
|
38
|
+
|
|
39
|
+
def _spatial_subset(
|
|
40
|
+
self,
|
|
41
|
+
bounds: Optional[BoundsLike] = None,
|
|
42
|
+
area: Optional[BaseGeometry] = None,
|
|
43
|
+
) -> Dict[str, Any]:
|
|
44
|
+
"""Combine bounds and area with bounds defined in Source if any."""
|
|
45
|
+
if self.bounds is None:
|
|
46
|
+
return {"bounds": bounds, "area": area}
|
|
47
|
+
self_bounds = Bounds.from_inp(self.bounds)
|
|
48
|
+
out = dict()
|
|
49
|
+
if bounds is not None:
|
|
50
|
+
bounds = Bounds.from_inp(bounds)
|
|
51
|
+
if bounds.intersects(self_bounds):
|
|
52
|
+
out["bounds"] = Bounds.from_inp(
|
|
53
|
+
bounds.geometry.intersection(self_bounds.geometry)
|
|
54
|
+
)
|
|
55
|
+
if area is not None:
|
|
56
|
+
out["area"] = area.intersection(self_bounds.geometry)
|
|
57
|
+
return out
|
|
58
|
+
|
|
59
|
+
def search(
|
|
60
|
+
self,
|
|
61
|
+
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
|
|
62
|
+
bounds: Optional[BoundsLike] = None,
|
|
63
|
+
area: Optional[BaseGeometry] = None,
|
|
64
|
+
base_dir: Optional[MPathLike] = None,
|
|
65
|
+
) -> Generator[Item, None, None]:
|
|
66
|
+
for item in self.get_catalog(base_dir=base_dir).search(
|
|
67
|
+
time=time,
|
|
68
|
+
query=self.query,
|
|
69
|
+
search_kwargs=dict(query=self.query) if self.query else None,
|
|
70
|
+
**self._spatial_subset(
|
|
71
|
+
bounds=bounds,
|
|
72
|
+
area=area,
|
|
73
|
+
),
|
|
74
|
+
):
|
|
75
|
+
yield self.apply_item_modifier_funcs(item)
|
|
76
|
+
|
|
77
|
+
def apply_item_modifier_funcs(self, item: Item) -> Item:
|
|
78
|
+
try:
|
|
79
|
+
for modifier in self.item_modifier_funcs:
|
|
80
|
+
item = modifier(item)
|
|
81
|
+
except GEOSException as exc:
|
|
82
|
+
raise ItemGeometryError(
|
|
83
|
+
f"item {item.get_self_href()} geometry could not be resolved: {str(exc)}"
|
|
84
|
+
)
|
|
85
|
+
return item
|
|
86
|
+
|
|
87
|
+
def get_catalog(self, base_dir: Optional[MPathLike] = None) -> CollectionSearcher:
|
|
88
|
+
match self.catalog_type:
|
|
89
|
+
case "search":
|
|
90
|
+
return STACSearchCollection(self.collection)
|
|
91
|
+
case "static":
|
|
92
|
+
return STACStaticCollection(
|
|
93
|
+
collection=MPath(self.collection).absolute_path(base_dir=base_dir)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def eo_bands(self, base_dir: Optional[MPathLike] = None) -> List[str]:
|
|
97
|
+
return self.get_catalog(base_dir=base_dir).eo_bands
|
|
98
|
+
|
|
99
|
+
@model_validator(mode="before")
|
|
100
|
+
def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
101
|
+
if "max_cloud_cover" in values:
|
|
102
|
+
raise DeprecationWarning(
|
|
103
|
+
"'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
|
|
104
|
+
)
|
|
105
|
+
elif "area" in values: # pragma: no cover
|
|
106
|
+
raise NotImplementedError("please use 'bounds' as spatial subset for now")
|
|
107
|
+
return values
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapchete-eo
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.11.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>
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
17
17
|
Requires-Dist: click
|
|
18
|
+
Requires-Dist: cql2
|
|
18
19
|
Requires-Dist: croniter
|
|
19
20
|
Requires-Dist: lxml
|
|
20
21
|
Requires-Dist: mapchete[complete]>=2025.10.0
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
mapchete_eo/__init__.py,sha256=2E0oLwEHFZUcghiMB3GjI_PKEVs-4pasu50WwMW-H2A,26
|
|
2
|
+
mapchete_eo/base.py,sha256=_6wbR9IZBBBdRBbZY4FEQ0uu603C-tMPYK4MGQ5xSYY,21953
|
|
3
|
+
mapchete_eo/blacklist.txt,sha256=6KhBY0jNjXgRpKRvKJoOTswvNUoP56IrIcNeCYnd2qo,17471
|
|
4
|
+
mapchete_eo/eostac.py,sha256=M-EL8y8pPw8H9hb85FPxSl-1FL_kIYOzxsTIhkrbBZY,570
|
|
5
|
+
mapchete_eo/exceptions.py,sha256=ul7_9o6_SfJXQPsDOQqRBhh09xv2t4AwHl6YJ7yWN5s,2150
|
|
6
|
+
mapchete_eo/product.py,sha256=0GJDk4pPvdCLxuJkZCMQnM7LIjvOBRUhIzchcQCqMjI,9644
|
|
7
|
+
mapchete_eo/protocols.py,sha256=KUbteFTOCSYEiSUJZj2MeckmrJHfJFrXCvZE70tByP8,1617
|
|
8
|
+
mapchete_eo/settings.py,sha256=lbwYoqiQXBRexxL7Y524Ry0dk4vBKpjN1u09UOP1_CM,734
|
|
9
|
+
mapchete_eo/sort.py,sha256=DfYAGEM6vzHJOFhaKxcKr-GwH5xM5ar4_mlp0uiVUK0,1787
|
|
10
|
+
mapchete_eo/source.py,sha256=Tfo5IIYdRWpS06K-M7mzesl4WIUEt4onGmc1uKJ0F14,4158
|
|
11
|
+
mapchete_eo/time.py,sha256=mCIrn6C-B7CDoFIIM4u8pG15nVV8KWQixF2fuhdDTdE,1799
|
|
12
|
+
mapchete_eo/types.py,sha256=yIHKZHlGCSerJzDBS2AH7yYLun4rY3rZZ73SCKiN26U,1814
|
|
13
|
+
mapchete_eo/array/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
mapchete_eo/array/buffer.py,sha256=GeeM7RVQ-Z_SKlkaeztWUrxEsa_yck-nXwXRIM3w0_I,483
|
|
15
|
+
mapchete_eo/array/color.py,sha256=ArJ3-0qjJJGAlRQodLios6JwD0uHLSRRb9BwfB31v-U,810
|
|
16
|
+
mapchete_eo/array/convert.py,sha256=oimklMfyS2XxQLd73zQ2cr9aE-39z1UIt6ry_2ZQI10,5390
|
|
17
|
+
mapchete_eo/cli/__init__.py,sha256=SRRilPKUtwsUCi1GdMNPxjIrCM-XyYM2hK1T9vvKUI8,946
|
|
18
|
+
mapchete_eo/cli/bounds.py,sha256=QcOXOXMcnd8ecwiCFVWukTLUFIkpj9SbB3ot-GA4heA,551
|
|
19
|
+
mapchete_eo/cli/options_arguments.py,sha256=6VqiwswEKpr5bS0w1ugRSf2z7rMKA-wiEFZdcmri94c,6916
|
|
20
|
+
mapchete_eo/cli/s2_brdf.py,sha256=siUGau8HzXNyQZkDUx1zAeaLcCfonJG5kWHSCvwJOhA,2729
|
|
21
|
+
mapchete_eo/cli/s2_cat_results.py,sha256=tR5Qm6kiCAcDO5V9AkYhoUbHyEx7OItSf6IqtIjhRLg,4648
|
|
22
|
+
mapchete_eo/cli/s2_find_broken_products.py,sha256=4nhtMLkys75enPV5lETw4iOn3CxxNnc-sMyhk_kaKx0,2946
|
|
23
|
+
mapchete_eo/cli/s2_jp2_static_catalog.py,sha256=_j_rfFnYHtE8k79TS9KFuX_IxOx_e-r7u6beoLYyxPY,5911
|
|
24
|
+
mapchete_eo/cli/s2_mask.py,sha256=1K5BiyTKyClGBdh6-Vh_ORTcgJOr6R5JfXtpEpDEQvk,2243
|
|
25
|
+
mapchete_eo/cli/s2_mgrs.py,sha256=EJxbrg4I8IttWIcLUEmmqkjsTUbVLufgrkDVrRbbkYE,1276
|
|
26
|
+
mapchete_eo/cli/s2_rgb.py,sha256=nzK5m4s0m2VOXgtit0uUOOhNJ14wquikWmbEQON5WJw,3747
|
|
27
|
+
mapchete_eo/cli/s2_verify.py,sha256=atfJA5luHZjDSqiM2udaufmnQ2B4uW2Tur_OgtYLa2g,4080
|
|
28
|
+
mapchete_eo/cli/static_catalog.py,sha256=O7oCJB6mxWwnEMowPBo6UMtWOu52mSNUBB9tpSw91a4,3005
|
|
29
|
+
mapchete_eo/image_operations/__init__.py,sha256=c36Vs53mT5UQR9Iwd2Ln_vnT4K_Ni8g-K7J9Fl6UsJo,432
|
|
30
|
+
mapchete_eo/image_operations/blend_functions.py,sha256=YaKLOEapMbsUR-_mkBatXwPcmim8GKpIAVua_GOdaHs,24131
|
|
31
|
+
mapchete_eo/image_operations/color_correction.py,sha256=vMXxcdyH3qOJDSKcXGU3FgXWIcgQxMy-QVe2fGQTbdk,4447
|
|
32
|
+
mapchete_eo/image_operations/compositing.py,sha256=K0bGCZqJuS39IBsjrRiJz5zowItI6RsiA-EBUVlbboo,8449
|
|
33
|
+
mapchete_eo/image_operations/dtype_scale.py,sha256=_CCdTBkMLDkZdjV_PsVEkqLWT6jWuTJPeMzCcRwBOns,1243
|
|
34
|
+
mapchete_eo/image_operations/fillnodata.py,sha256=Xdd-kacfo7VKqX7HRP0hqnnvV5OwNqnHLRTW64QhG24,4856
|
|
35
|
+
mapchete_eo/image_operations/filters.py,sha256=sa_Igv0zMIANHLEALVi8mxohZMoSrdQc6XVjXDYwzy8,7201
|
|
36
|
+
mapchete_eo/image_operations/linear_normalization.py,sha256=-eQX3WLCUYWUv-um3s1u33rgjAwCz84Ht8gcPsMWtJE,2468
|
|
37
|
+
mapchete_eo/image_operations/sigmoidal.py,sha256=IKU8F89HhQJWGUVmIrnkuhqzn_ztlGrTf8xXZaVQWzU,3575
|
|
38
|
+
mapchete_eo/io/__init__.py,sha256=1-1g4cESZZREvyumEUABZhDwgVuSxtxdqbNlLuVKlow,950
|
|
39
|
+
mapchete_eo/io/assets.py,sha256=E7phBOu8JaqyRhuEwl7wnmWHvG1yY1sw38QaMCuYkB4,16998
|
|
40
|
+
mapchete_eo/io/items.py,sha256=5H2xWAyLptDuuHh0rjY1MXReJomV0XBZgMr_rpdpYXU,6244
|
|
41
|
+
mapchete_eo/io/levelled_cubes.py,sha256=ZF7BLn9MHnJCCDjAoR9D7MNbHdusjJ9EACdM7rKNlyM,9018
|
|
42
|
+
mapchete_eo/io/path.py,sha256=fCjvowd9rGJbGio5Z-jDLxa49M2BVurYM9hJy53Im48,4831
|
|
43
|
+
mapchete_eo/io/products.py,sha256=2gELjHoEe4QH-Yjk82tA2nZUnBgfPbaGvk5GMHUWs4w,14642
|
|
44
|
+
mapchete_eo/io/profiles.py,sha256=l9YiXbKYs674xz6NLy5EAq3fBEvHTVSf_gopXD-CuSY,935
|
|
45
|
+
mapchete_eo/platforms/sentinel2/__init__.py,sha256=dviB4f5k45t_iOScTtnPc1kaWcohWKRq-8RaQzdggmU,401
|
|
46
|
+
mapchete_eo/platforms/sentinel2/_mapper_registry.py,sha256=3kuyW2g_xJoFUzZrAmhKn9ASHl4gBKuOlHsSZnpt21w,2746
|
|
47
|
+
mapchete_eo/platforms/sentinel2/bandpass_adjustment.py,sha256=DA0cQtjr8UH7r_kizAD-EmBsZvEtVVThf0hD0SCY7b4,3202
|
|
48
|
+
mapchete_eo/platforms/sentinel2/config.py,sha256=ifmzxDEuOFfoGAvbGyWyUWwF3Dw0NxEUEA3bWNByNR0,8482
|
|
49
|
+
mapchete_eo/platforms/sentinel2/driver.py,sha256=hNc9jmLQMmnA2ciVBPSTmVW6H8clL6sydkxcy6BjO40,1166
|
|
50
|
+
mapchete_eo/platforms/sentinel2/masks.py,sha256=6ig8sQhXkm1u6Bwbe7n8ewW8gTdbVJp-iaDCk780Qxg,11220
|
|
51
|
+
mapchete_eo/platforms/sentinel2/preprocessing_tasks.py,sha256=DJhbvYhNDGZ2Er5polK2LXWcg-MASZMFparNOw56yDk,1645
|
|
52
|
+
mapchete_eo/platforms/sentinel2/processing_baseline.py,sha256=uTE7zgzgi_W3NENLIAnJbWjyUD5_znpGa3cV-VoaEws,5179
|
|
53
|
+
mapchete_eo/platforms/sentinel2/product.py,sha256=UUbngHI2qPri5GkMPdmaw-ZS0y6aIQUtgZ5oZXUcIJc,28095
|
|
54
|
+
mapchete_eo/platforms/sentinel2/source.py,sha256=qSziCNTpInGy2LWIMiC3M4EP9G9_VtbCRNXB1XAO1P4,4269
|
|
55
|
+
mapchete_eo/platforms/sentinel2/types.py,sha256=EV7TleKZnCNuDlEMJ0UtJjDsvOUGbvh_pZToeOULQ18,2122
|
|
56
|
+
mapchete_eo/platforms/sentinel2/brdf/__init__.py,sha256=W7zAJZ5F5Xla7j4ua2opRANUbcD3jZbhTwhW5_cpOUI,242
|
|
57
|
+
mapchete_eo/platforms/sentinel2/brdf/config.py,sha256=v3WCEu4r-tEPQgWBEkYNAPLgCQobvYtQ2YiDeAdImMk,1133
|
|
58
|
+
mapchete_eo/platforms/sentinel2/brdf/correction.py,sha256=vi-BwAA6Cbayzk6WOuOESLhRkGLhiUAbVYfJUSfYhXA,8651
|
|
59
|
+
mapchete_eo/platforms/sentinel2/brdf/hls.py,sha256=pzrRTNfJhuqRLidLQvKJpSnaz816x7jUesXt_aRgFjc,8818
|
|
60
|
+
mapchete_eo/platforms/sentinel2/brdf/models.py,sha256=BBwGX1OLMRLncK1jD2reidaF82IKR4sjgNGHdTgBXaw,1446
|
|
61
|
+
mapchete_eo/platforms/sentinel2/brdf/protocols.py,sha256=fxLDFeaL44eIPC2UwcbEQ-AXPREWgBv6Iytc74AQ5eA,752
|
|
62
|
+
mapchete_eo/platforms/sentinel2/brdf/ross_thick.py,sha256=OvuuQxqWN8qrKpz8kDhmCd8R1uMLa8h5LISJSJ_NvYk,4685
|
|
63
|
+
mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py,sha256=RxhQ8vBU0r5vsQ3FnyfDdYS0EGDGKCwAXND0Gn1m5mQ,2148
|
|
64
|
+
mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py,sha256=Vio87daAQ1eBQfDfA97c9byVnAUEPCidjTBg6jnZhXk,167
|
|
65
|
+
mapchete_eo/platforms/sentinel2/metadata_parser/base.py,sha256=AvPfsQEAyXOrwOQ_MQgWLUFFJL0VhTzzWHpuSe14IS0,1520
|
|
66
|
+
mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py,sha256=Gt9jUZwO3u_p6iH3hkOGNS5Tad5yn-Y1F7uADtalBSo,5344
|
|
67
|
+
mapchete_eo/platforms/sentinel2/metadata_parser/models.py,sha256=FOdEeIKk_Sa_O3iGRqoR-5YLQuwjPG6pNbdFRgQVYm8,2325
|
|
68
|
+
mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py,sha256=i6O9t0Bbb5BCiktKmXgDBKWdzdtVoGJ-Nt-xib-w3qw,23786
|
|
69
|
+
mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py,sha256=W-PFNuC0zXfF07uTUH_-r2Macv-_5qlz_YsP04Jxh8Y,1821
|
|
70
|
+
mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py,sha256=lkftO9NCJqwV663K3YqUFkNQxQWrCuwp_enbfTkjdbQ,3746
|
|
71
|
+
mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py,sha256=b_lvs6-rKQjb-8vBjOLV0Zi4a2ZXZxMqinyeRljA5lY,5957
|
|
72
|
+
mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py,sha256=8UbF5Riox1FZIYGuDMBiGgxNfAQkOmu07ZMc2Eqiel0,8647
|
|
73
|
+
mapchete_eo/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
+
mapchete_eo/processes/config.py,sha256=WorMzHJzJaZPh-aEyEEwcXN08ALxn6zAhVMrjkDUWDU,1745
|
|
75
|
+
mapchete_eo/processes/dtype_scale.py,sha256=7hJlIe2DsE6Kmk1c3PLwLr8rnn_4_9S6Cz7bmRuzP9M,4176
|
|
76
|
+
mapchete_eo/processes/eo_to_xarray.py,sha256=Gcp4qju2C9S8KeUnVY5f3nAsrdckPhGRguzsgRtWBFs,514
|
|
77
|
+
mapchete_eo/processes/merge_rasters.py,sha256=jSXUI8pEWXbQWrM9iMtrH4ACH2VkCaBUH9CQg4z6zLA,8486
|
|
78
|
+
mapchete_eo/search/__init__.py,sha256=bHvaqoHrtoNyO8_3kyJfvhh1yCz0hYvU2fzXhjOXNhU,528
|
|
79
|
+
mapchete_eo/search/base.py,sha256=Xdhq7js96erhV5qgAeKviUakF4qJmzUkbvprrTCP6Zc,9107
|
|
80
|
+
mapchete_eo/search/config.py,sha256=-3TzCBzjTIkCVpuoJMDDShHh4rm_9_QFKYruedEwbjY,2241
|
|
81
|
+
mapchete_eo/search/s2_mgrs.py,sha256=x12m-tJZNc41lFHNR2h7eSyEZ-ceBO4Gdz785btcVN8,10859
|
|
82
|
+
mapchete_eo/search/stac_search.py,sha256=p38veJiUZtEzXq7KJJ0YzqeEG1L87Q3CLdPIME1RQ3A,10504
|
|
83
|
+
mapchete_eo/search/stac_static.py,sha256=nPvJPayImPHkjVr4XEDp7ucOOhbDpUTlGyMJeaRFmAI,7260
|
|
84
|
+
mapchete_eo/search/utm_search.py,sha256=6T9HtbgT8mrWjzX_7_fkggmQBCWDBhf5i7BJ9dk6BZw,9230
|
|
85
|
+
mapchete_eo-2025.11.0.dist-info/METADATA,sha256=TkqXdAgVkEPhNiAbMS4VSXUwuhKGnITHET5MyvgGwDQ,3257
|
|
86
|
+
mapchete_eo-2025.11.0.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
87
|
+
mapchete_eo-2025.11.0.dist-info/entry_points.txt,sha256=edU29CA3j8hwBafEd6jO2Zcou1sGkyjNro-y4_LnAnw,320
|
|
88
|
+
mapchete_eo-2025.11.0.dist-info/licenses/LICENSE,sha256=TC5JwvBnFrUgsSQSCDFPc3cqlbth2N0q8MWrhY1EVd0,1089
|
|
89
|
+
mapchete_eo-2025.11.0.dist-info/RECORD,,
|
mapchete_eo/archives/__init__.py
DELETED
|
File without changes
|
mapchete_eo/archives/base.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Any, Callable, Dict, Generator, List, Optional, Union
|
|
4
|
-
|
|
5
|
-
from mapchete.io.vector import IndexedFeatures
|
|
6
|
-
from mapchete.types import Bounds
|
|
7
|
-
from pystac import Item
|
|
8
|
-
from shapely.errors import GEOSException
|
|
9
|
-
from shapely.geometry.base import BaseGeometry
|
|
10
|
-
|
|
11
|
-
from mapchete_eo.exceptions import ItemGeometryError
|
|
12
|
-
from mapchete_eo.search.base import CatalogSearcher
|
|
13
|
-
from mapchete_eo.types import TimeRange
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Archive(ABC):
|
|
19
|
-
"""
|
|
20
|
-
An archive combines a Catalog and a Storage.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
time: Union[TimeRange, List[TimeRange]]
|
|
24
|
-
area: BaseGeometry
|
|
25
|
-
catalog: CatalogSearcher
|
|
26
|
-
search_kwargs: Dict[str, Any]
|
|
27
|
-
_items: Optional[IndexedFeatures] = None
|
|
28
|
-
item_modifier_funcs: Optional[List[Callable[[Item], Item]]] = None
|
|
29
|
-
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
time: Union[TimeRange, List[TimeRange]],
|
|
33
|
-
bounds: Optional[Bounds] = None,
|
|
34
|
-
area: Optional[BaseGeometry] = None,
|
|
35
|
-
search_kwargs: Optional[Dict[str, Any]] = None,
|
|
36
|
-
catalog: Optional[CatalogSearcher] = None,
|
|
37
|
-
):
|
|
38
|
-
if bounds is None and area is None:
|
|
39
|
-
raise ValueError("either bounds or area have to be provided")
|
|
40
|
-
elif area is None:
|
|
41
|
-
area = Bounds.from_inp(bounds).geometry
|
|
42
|
-
self.time = time
|
|
43
|
-
self.area = area
|
|
44
|
-
self.search_kwargs = search_kwargs or {}
|
|
45
|
-
if catalog:
|
|
46
|
-
self.catalog = catalog
|
|
47
|
-
|
|
48
|
-
def get_catalog_config(self):
|
|
49
|
-
return self.catalog.config_cls(**self.search_kwargs)
|
|
50
|
-
|
|
51
|
-
def apply_item_modifier_funcs(self, item: Item) -> Item:
|
|
52
|
-
try:
|
|
53
|
-
for modifier in self.item_modifier_funcs or []:
|
|
54
|
-
item = modifier(item)
|
|
55
|
-
except GEOSException as exc:
|
|
56
|
-
raise ItemGeometryError(
|
|
57
|
-
f"item {item.get_self_href()} geometry could not be resolved: {str(exc)}"
|
|
58
|
-
)
|
|
59
|
-
return item
|
|
60
|
-
|
|
61
|
-
def items(self) -> Generator[Item, None, None]:
|
|
62
|
-
for item in self.catalog.search(
|
|
63
|
-
time=self.time, area=self.area, search_kwargs=self.search_kwargs
|
|
64
|
-
):
|
|
65
|
-
yield self.apply_item_modifier_funcs(item)
|