eodag 4.0.0a5__py3-none-any.whl → 4.0.0b1__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.
- eodag/api/collection.py +65 -1
- eodag/api/core.py +48 -16
- eodag/api/product/_assets.py +1 -1
- eodag/api/product/_product.py +108 -15
- eodag/api/product/drivers/__init__.py +3 -1
- eodag/api/product/drivers/base.py +3 -1
- eodag/api/product/drivers/generic.py +9 -5
- eodag/api/product/drivers/sentinel1.py +14 -9
- eodag/api/product/drivers/sentinel2.py +14 -7
- eodag/api/product/metadata_mapping.py +5 -2
- eodag/api/provider.py +1 -0
- eodag/api/search_result.py +4 -1
- eodag/cli.py +7 -7
- eodag/config.py +22 -4
- eodag/plugins/download/aws.py +3 -1
- eodag/plugins/download/http.py +4 -10
- eodag/plugins/search/base.py +8 -3
- eodag/plugins/search/build_search_result.py +108 -120
- eodag/plugins/search/cop_marine.py +3 -1
- eodag/plugins/search/qssearch.py +7 -6
- eodag/resources/collections.yml +255 -0
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +60 -25
- eodag/resources/user_conf_template.yml +6 -0
- eodag/types/__init__.py +22 -16
- eodag/types/download_args.py +3 -1
- eodag/types/queryables.py +125 -55
- eodag/types/stac_extensions.py +408 -0
- eodag/types/stac_metadata.py +312 -0
- eodag/utils/__init__.py +42 -4
- eodag/utils/dates.py +202 -2
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/METADATA +7 -13
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/RECORD +38 -36
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/WHEEL +1 -1
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/entry_points.txt +1 -1
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/top_level.txt +0 -0
eodag/api/collection.py
CHANGED
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
22
|
from collections import UserDict, UserList
|
|
23
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
24
24
|
|
|
25
25
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
26
26
|
from pydantic import ValidationError as PydanticValidationError
|
|
@@ -29,6 +29,9 @@ from pydantic_core import ErrorDetails, InitErrorDetails, PydanticCustomError
|
|
|
29
29
|
from stac_pydantic.collection import Extent, Provider, SpatialExtent, TimeInterval
|
|
30
30
|
from stac_pydantic.links import Links
|
|
31
31
|
|
|
32
|
+
from eodag.types.queryables import CommonStacMetadata
|
|
33
|
+
from eodag.types.stac_metadata import create_stac_metadata_model
|
|
34
|
+
from eodag.utils import STAC_VERSION
|
|
32
35
|
from eodag.utils.env import is_env_var_true
|
|
33
36
|
from eodag.utils.exceptions import ValidationError
|
|
34
37
|
from eodag.utils.repr import dict_to_html_table
|
|
@@ -91,6 +94,11 @@ class Collection(BaseModel):
|
|
|
91
94
|
repr=False,
|
|
92
95
|
)
|
|
93
96
|
|
|
97
|
+
# path to external collection metadata file (required by stac-fastapi-eodag)
|
|
98
|
+
eodag_stac_collection: Optional[str] = Field(
|
|
99
|
+
default=None, alias="stacCollection", exclude=True, repr=False
|
|
100
|
+
)
|
|
101
|
+
|
|
94
102
|
# Private property to store the eodag internal id value. Not part of the model schema.
|
|
95
103
|
_id: str = PrivateAttr()
|
|
96
104
|
_dag: Optional[EODataAccessGateway] = PrivateAttr(default=None)
|
|
@@ -276,6 +284,62 @@ class Collection(BaseModel):
|
|
|
276
284
|
|
|
277
285
|
return dag.list_queryables(collection=self.id, **kwargs)
|
|
278
286
|
|
|
287
|
+
def serialize(self) -> dict[str, Any]:
|
|
288
|
+
"""Serialize the Collection instance to a STAC dictionary.
|
|
289
|
+
|
|
290
|
+
:returns: A STAC dictionary representation of the Collection instance.
|
|
291
|
+
"""
|
|
292
|
+
stac_dict: dict[str, Any] = {
|
|
293
|
+
"stac_version": STAC_VERSION,
|
|
294
|
+
"type": "Collection",
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
stac_dict |= self.model_dump(mode="json", exclude_none=True, exclude={"alias"})
|
|
298
|
+
|
|
299
|
+
stac_dict.setdefault("links", [])
|
|
300
|
+
stac_dict.setdefault("providers", [])
|
|
301
|
+
|
|
302
|
+
not_in_summaries = [
|
|
303
|
+
"stac_version",
|
|
304
|
+
"type",
|
|
305
|
+
"id",
|
|
306
|
+
"title",
|
|
307
|
+
"description",
|
|
308
|
+
"extent",
|
|
309
|
+
"keywords",
|
|
310
|
+
"license",
|
|
311
|
+
"links",
|
|
312
|
+
"providers",
|
|
313
|
+
]
|
|
314
|
+
summaries = dict()
|
|
315
|
+
for k, v in stac_dict.items():
|
|
316
|
+
if k not in not_in_summaries:
|
|
317
|
+
if isinstance(v, list):
|
|
318
|
+
summaries[k] = v
|
|
319
|
+
elif isinstance(v, str):
|
|
320
|
+
summaries[k] = v.split(",")
|
|
321
|
+
else:
|
|
322
|
+
summaries[k] = [v]
|
|
323
|
+
stac_dict["summaries"] = summaries
|
|
324
|
+
|
|
325
|
+
# Remove empty items and items moved to summaries
|
|
326
|
+
keys_to_remove = [
|
|
327
|
+
k
|
|
328
|
+
for k in stac_dict.keys()
|
|
329
|
+
if k not in not_in_summaries and k != "summaries"
|
|
330
|
+
]
|
|
331
|
+
for k in keys_to_remove:
|
|
332
|
+
del stac_dict[k]
|
|
333
|
+
|
|
334
|
+
# add extensions
|
|
335
|
+
summaries_model = cast(CommonStacMetadata, create_stac_metadata_model())
|
|
336
|
+
summaries_validated = summaries_model.model_construct(
|
|
337
|
+
_fields_set=None, **summaries
|
|
338
|
+
)
|
|
339
|
+
stac_dict["stac_extensions"] = summaries_validated.get_conformance_classes()
|
|
340
|
+
|
|
341
|
+
return stac_dict
|
|
342
|
+
|
|
279
343
|
|
|
280
344
|
class CollectionsDict(UserDict[str, Collection]):
|
|
281
345
|
"""A UserDict object which values are :class:`~eodag.api.collection.Collection` objects, keyed by provider ``id``.
|
eodag/api/core.py
CHANGED
|
@@ -30,6 +30,7 @@ from copy import deepcopy
|
|
|
30
30
|
from importlib.metadata import version
|
|
31
31
|
from importlib.resources import files as res_files
|
|
32
32
|
from operator import attrgetter, itemgetter
|
|
33
|
+
from pathlib import Path
|
|
33
34
|
from typing import TYPE_CHECKING, Any, Iterator, Optional, Union, cast
|
|
34
35
|
|
|
35
36
|
import geojson
|
|
@@ -154,11 +155,14 @@ class EODataAccessGateway:
|
|
|
154
155
|
user_conf_file_path = os.getenv(env_var_name)
|
|
155
156
|
if user_conf_file_path is None:
|
|
156
157
|
user_conf_file_path = standard_configuration_path
|
|
157
|
-
|
|
158
|
+
source = str(
|
|
159
|
+
res_files("eodag") / "resources" / "user_conf_template.yml"
|
|
160
|
+
)
|
|
161
|
+
if os.path.isfile(source) and not os.path.isfile(
|
|
162
|
+
standard_configuration_path
|
|
163
|
+
):
|
|
158
164
|
shutil.copy(
|
|
159
|
-
|
|
160
|
-
res_files("eodag") / "resources" / "user_conf_template.yml"
|
|
161
|
-
),
|
|
165
|
+
source,
|
|
162
166
|
standard_configuration_path,
|
|
163
167
|
)
|
|
164
168
|
self._providers.update_from_config_file(user_conf_file_path)
|
|
@@ -1783,14 +1787,15 @@ class EODataAccessGateway:
|
|
|
1783
1787
|
|
|
1784
1788
|
# remove None values and convert param names to their pydantic alias if any
|
|
1785
1789
|
search_params = {}
|
|
1790
|
+
queryables_fields = Queryables.from_stac_models().model_fields
|
|
1786
1791
|
ecmwf_queryables = [
|
|
1787
1792
|
f"{ECMWF_PREFIX[:-1]}_{k}" for k in ECMWF_ALLOWED_KEYWORDS
|
|
1788
1793
|
]
|
|
1789
1794
|
for param, value in kwargs.items():
|
|
1790
1795
|
if value is None:
|
|
1791
1796
|
continue
|
|
1792
|
-
if param in
|
|
1793
|
-
param_alias =
|
|
1797
|
+
if param in queryables_fields:
|
|
1798
|
+
param_alias = queryables_fields[param].alias or param
|
|
1794
1799
|
search_params[param_alias] = value
|
|
1795
1800
|
elif param in ecmwf_queryables:
|
|
1796
1801
|
# alias equivalent for ECMWF queryables
|
|
@@ -1849,14 +1854,6 @@ class EODataAccessGateway:
|
|
|
1849
1854
|
else:
|
|
1850
1855
|
eo_product.collection = guesses[0].id
|
|
1851
1856
|
|
|
1852
|
-
try:
|
|
1853
|
-
if eo_product.collection is not None:
|
|
1854
|
-
eo_product.collection = self.get_collection_from_alias(
|
|
1855
|
-
eo_product.collection
|
|
1856
|
-
)
|
|
1857
|
-
except NoMatchingCollection:
|
|
1858
|
-
logger.debug("collection %s not found", eo_product.collection)
|
|
1859
|
-
|
|
1860
1857
|
if eo_product.search_intersection is not None:
|
|
1861
1858
|
eo_product._register_downloader_from_manager(self._plugins_manager)
|
|
1862
1859
|
|
|
@@ -1993,8 +1990,42 @@ class EODataAccessGateway:
|
|
|
1993
1990
|
:param filename: (optional) The name of the file to generate
|
|
1994
1991
|
:returns: The name of the created file
|
|
1995
1992
|
"""
|
|
1993
|
+
search_result_dict = search_result.as_geojson_object()
|
|
1994
|
+
# add self link
|
|
1995
|
+
search_result_dict.setdefault("links", [])
|
|
1996
|
+
search_result_dict["links"].append(
|
|
1997
|
+
{
|
|
1998
|
+
"rel": "self",
|
|
1999
|
+
"href": f"{filename}",
|
|
2000
|
+
"type": "application/json",
|
|
2001
|
+
},
|
|
2002
|
+
)
|
|
2003
|
+
# write search results
|
|
1996
2004
|
with open(filename, "w") as fh:
|
|
1997
|
-
geojson.dump(
|
|
2005
|
+
geojson.dump(search_result_dict, fh)
|
|
2006
|
+
logger.debug("Search results saved to %s", filename)
|
|
2007
|
+
# write collection(s)
|
|
2008
|
+
if search_result._dag is None:
|
|
2009
|
+
return filename
|
|
2010
|
+
collections = set(p.collection for p in search_result)
|
|
2011
|
+
for collection in collections:
|
|
2012
|
+
collection_obj = search_result._dag.collections_config.get(
|
|
2013
|
+
collection, Collection(id=collection)
|
|
2014
|
+
)
|
|
2015
|
+
collection_dict = collection_obj.serialize()
|
|
2016
|
+
# add links
|
|
2017
|
+
collection_dict.setdefault("links", [])
|
|
2018
|
+
collection_dict["links"].append(
|
|
2019
|
+
{
|
|
2020
|
+
"rel": "self",
|
|
2021
|
+
"href": f"{collection}.json",
|
|
2022
|
+
"type": "application/json",
|
|
2023
|
+
},
|
|
2024
|
+
)
|
|
2025
|
+
with open(Path(filename).parent / f"{collection}.json", "w") as fh:
|
|
2026
|
+
geojson.dump(collection_dict, fh)
|
|
2027
|
+
logger.debug("Collection '%s' saved to %s", collection, fh.name)
|
|
2028
|
+
|
|
1998
2029
|
return filename
|
|
1999
2030
|
|
|
2000
2031
|
@staticmethod
|
|
@@ -2198,7 +2229,8 @@ class EODataAccessGateway:
|
|
|
2198
2229
|
|
|
2199
2230
|
# use queryables aliases
|
|
2200
2231
|
kwargs_alias = {**kwargs}
|
|
2201
|
-
|
|
2232
|
+
queryables_fields = Queryables.from_stac_models().model_fields
|
|
2233
|
+
for search_param, field_info in queryables_fields.items():
|
|
2202
2234
|
if search_param in kwargs and field_info.alias:
|
|
2203
2235
|
kwargs_alias[field_info.alias] = kwargs_alias.pop(search_param)
|
|
2204
2236
|
|
eodag/api/product/_assets.py
CHANGED
eodag/api/product/_product.py
CHANGED
|
@@ -23,14 +23,18 @@ import os
|
|
|
23
23
|
import re
|
|
24
24
|
import tempfile
|
|
25
25
|
from datetime import datetime
|
|
26
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast
|
|
27
27
|
|
|
28
|
+
import orjson
|
|
28
29
|
import requests
|
|
29
30
|
from requests import RequestException
|
|
30
31
|
from requests.auth import AuthBase
|
|
31
32
|
from shapely import geometry
|
|
32
33
|
from shapely.errors import ShapelyError
|
|
33
34
|
|
|
35
|
+
from eodag.types.queryables import CommonStacMetadata
|
|
36
|
+
from eodag.types.stac_metadata import create_stac_metadata_model
|
|
37
|
+
|
|
34
38
|
try:
|
|
35
39
|
# import from eodag-cube if installed
|
|
36
40
|
from eodag_cube.api.product import ( # pyright: ignore[reportMissingImports]
|
|
@@ -52,6 +56,7 @@ from eodag.utils import (
|
|
|
52
56
|
DEFAULT_DOWNLOAD_WAIT,
|
|
53
57
|
DEFAULT_SHAPELY_GEOMETRY,
|
|
54
58
|
DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
59
|
+
STAC_VERSION,
|
|
55
60
|
USER_AGENT,
|
|
56
61
|
ProgressCallback,
|
|
57
62
|
format_string,
|
|
@@ -146,6 +151,13 @@ class EOProduct:
|
|
|
146
151
|
and not key.startswith("_")
|
|
147
152
|
and value is not None
|
|
148
153
|
}
|
|
154
|
+
self.properties.setdefault(
|
|
155
|
+
"datetime",
|
|
156
|
+
self.properties.get("start_datetime")
|
|
157
|
+
or self.properties.get("end_datetime"),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# sort properties to have common stac properties first
|
|
149
161
|
common_stac_properties = {
|
|
150
162
|
key: self.properties[key]
|
|
151
163
|
for key in sorted(self.properties)
|
|
@@ -205,25 +217,71 @@ class EOProduct:
|
|
|
205
217
|
"""
|
|
206
218
|
search_intersection = None
|
|
207
219
|
if self.search_intersection is not None:
|
|
208
|
-
search_intersection =
|
|
220
|
+
search_intersection = orjson.loads(
|
|
221
|
+
orjson.dumps(self.search_intersection.__geo_interface__)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# product properties
|
|
225
|
+
stac_properties = {
|
|
226
|
+
**{
|
|
227
|
+
key: value
|
|
228
|
+
for key, value in self.properties.items()
|
|
229
|
+
if key not in ("geometry", "id")
|
|
230
|
+
},
|
|
231
|
+
"eodag:provider": self.provider,
|
|
232
|
+
"eodag:search_intersection": search_intersection,
|
|
233
|
+
}
|
|
234
|
+
stac_providers = self.properties.get("providers", [])
|
|
235
|
+
if not any("host" in p.get("roles", []) for p in stac_providers):
|
|
236
|
+
stac_providers.append({"name": self.provider, "roles": ["host"]})
|
|
237
|
+
stac_properties["providers"] = stac_providers
|
|
238
|
+
|
|
239
|
+
props_model = cast(type[CommonStacMetadata], create_stac_metadata_model())
|
|
240
|
+
props_validated = props_model.safe_validate(stac_properties)
|
|
241
|
+
stac_extensions: set[str] = set(props_validated.get_conformance_classes())
|
|
242
|
+
|
|
243
|
+
# skip invalid properties
|
|
244
|
+
invalid_properties = {
|
|
245
|
+
k
|
|
246
|
+
for k in stac_properties.keys()
|
|
247
|
+
if k not in props_validated.to_dict() and props_model.has_field(k)
|
|
248
|
+
}
|
|
249
|
+
for key in invalid_properties:
|
|
250
|
+
stac_properties.pop(key, None)
|
|
251
|
+
|
|
252
|
+
# get conformance classes for assets properties
|
|
253
|
+
assets_dict = {**self.assets.as_dict()}
|
|
254
|
+
for asset_key, asset_properties in self.assets.as_dict().items():
|
|
255
|
+
asset_props_validated = props_model.safe_validate(asset_properties)
|
|
256
|
+
stac_extensions.update(asset_props_validated.get_conformance_classes())
|
|
257
|
+
|
|
258
|
+
# skip invalid assets properties
|
|
259
|
+
invalid_asset_properties = {
|
|
260
|
+
k
|
|
261
|
+
for k in asset_properties.keys()
|
|
262
|
+
if k not in asset_props_validated.to_dict() and props_model.has_field(k)
|
|
263
|
+
}
|
|
264
|
+
for key in invalid_asset_properties:
|
|
265
|
+
assets_dict[asset_key].pop(key, None)
|
|
209
266
|
|
|
210
267
|
geojson_repr: dict[str, Any] = {
|
|
211
268
|
"type": "Feature",
|
|
212
|
-
"geometry":
|
|
269
|
+
"geometry": orjson.loads(orjson.dumps(self.geometry.__geo_interface__)),
|
|
270
|
+
"bbox": list(self.geometry.bounds),
|
|
213
271
|
"id": self.properties["id"],
|
|
214
|
-
"assets":
|
|
215
|
-
"properties":
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
for key, value in self.properties.items()
|
|
222
|
-
if key not in ("geometry", "id")
|
|
272
|
+
"assets": assets_dict,
|
|
273
|
+
"properties": stac_properties,
|
|
274
|
+
"links": [
|
|
275
|
+
{
|
|
276
|
+
"rel": "collection",
|
|
277
|
+
"href": f"{self.collection}.json",
|
|
278
|
+
"type": "application/json",
|
|
223
279
|
},
|
|
224
|
-
|
|
280
|
+
],
|
|
281
|
+
"stac_extensions": list(stac_extensions),
|
|
282
|
+
"stac_version": STAC_VERSION,
|
|
283
|
+
"collection": self.collection,
|
|
225
284
|
}
|
|
226
|
-
|
|
227
285
|
return geojson_repr
|
|
228
286
|
|
|
229
287
|
@classmethod
|
|
@@ -237,11 +295,11 @@ class EOProduct:
|
|
|
237
295
|
:raises: :class:`~eodag.utils.exceptions.ValidationError`
|
|
238
296
|
"""
|
|
239
297
|
try:
|
|
298
|
+
collection = feature.get("collection")
|
|
240
299
|
properties = feature["properties"]
|
|
241
300
|
properties["geometry"] = feature["geometry"]
|
|
242
301
|
properties["id"] = feature["id"]
|
|
243
302
|
provider = properties.pop("eodag:provider")
|
|
244
|
-
collection = properties.pop("eodag:collection")
|
|
245
303
|
search_intersection = properties.pop("eodag:search_intersection")
|
|
246
304
|
except KeyError as e:
|
|
247
305
|
raise ValidationError(
|
|
@@ -663,3 +721,38 @@ class EOProduct:
|
|
|
663
721
|
<td {thumbnail_style} title='properties["thumbnail"]'>{thumbnail_html}</td>
|
|
664
722
|
</tr>
|
|
665
723
|
</table>"""
|
|
724
|
+
|
|
725
|
+
def to_xarray(
|
|
726
|
+
self,
|
|
727
|
+
asset_key: Optional[str] = None,
|
|
728
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
729
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
730
|
+
roles: Iterable[str] = {"data", "data-mask"},
|
|
731
|
+
**xarray_kwargs: Any,
|
|
732
|
+
):
|
|
733
|
+
"""
|
|
734
|
+
Return product data as a dictionary of :class:`xarray.Dataset`.
|
|
735
|
+
|
|
736
|
+
:param asset_key: (optional) key of the asset. If not specified the whole
|
|
737
|
+
product data will be retrieved
|
|
738
|
+
:param wait: (optional) If order is needed, wait time in minutes between two
|
|
739
|
+
order status check
|
|
740
|
+
:param timeout: (optional) If order is needed, maximum time in minutes before
|
|
741
|
+
stop checking order status
|
|
742
|
+
:param roles: (optional) roles of assets that must be fetched
|
|
743
|
+
:param xarray_kwargs: (optional) keyword arguments passed to :func:`xarray.open_dataset`
|
|
744
|
+
:returns: a dictionary of :class:`xarray.Dataset`
|
|
745
|
+
"""
|
|
746
|
+
raise NotImplementedError("Install eodag-cube to make this method available.")
|
|
747
|
+
|
|
748
|
+
def augment_from_xarray(
|
|
749
|
+
self,
|
|
750
|
+
roles: Iterable[str] = {"data", "data-mask"},
|
|
751
|
+
) -> EOProduct:
|
|
752
|
+
"""
|
|
753
|
+
Annotate the product properties and assets with STAC metadata got by fetching its xarray representation.
|
|
754
|
+
|
|
755
|
+
:param roles: (optional) roles of assets that must be fetched
|
|
756
|
+
:returns: updated EOProduct
|
|
757
|
+
"""
|
|
758
|
+
raise NotImplementedError("Install eodag-cube to make this method available.")
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"""EODAG drivers package"""
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
from typing import Callable
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
from typing_extensions import TypedDict
|
|
22
24
|
|
|
23
25
|
from eodag.api.product.drivers.base import DatasetDriver
|
|
24
26
|
from eodag.api.product.drivers.generic import GenericDriver
|
|
@@ -19,7 +19,9 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
|
-
from typing import TYPE_CHECKING, Optional
|
|
22
|
+
from typing import TYPE_CHECKING, Optional
|
|
23
|
+
|
|
24
|
+
from typing_extensions import TypedDict
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING:
|
|
25
27
|
from eodag.api.product import EOProduct
|
|
@@ -36,7 +36,7 @@ class GenericDriver(DatasetDriver):
|
|
|
36
36
|
(
|
|
37
37
|
r"^(?:.*[/\\])?([^/\\]+)"
|
|
38
38
|
r"(\.jp2|\.tiff?|\.dat|\.nc|\.grib2?|"
|
|
39
|
-
r"\.zarr|\.nat|\.covjson|\.parquet|\.zip|\.tar|\.gz)
|
|
39
|
+
r"\.zarr|\.nat|\.covjson|\.parquet|\.zip|\.tar|\.gz)(?:\?.*)?$"
|
|
40
40
|
),
|
|
41
41
|
re.IGNORECASE,
|
|
42
42
|
),
|
|
@@ -45,25 +45,29 @@ class GenericDriver(DatasetDriver):
|
|
|
45
45
|
# metadata
|
|
46
46
|
{
|
|
47
47
|
"pattern": re.compile(
|
|
48
|
-
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)
|
|
48
|
+
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)(?:\?.*)?$",
|
|
49
|
+
re.IGNORECASE,
|
|
49
50
|
),
|
|
50
51
|
"roles": ["metadata"],
|
|
51
52
|
},
|
|
52
53
|
# thumbnail
|
|
53
54
|
{
|
|
54
55
|
"pattern": re.compile(
|
|
55
|
-
r"^(?:.*[/\\])?(thumbnail)(\.jpg|\.jpeg|\.png)
|
|
56
|
+
r"^(?:.*[/\\])?(thumbnail)(\.jpg|\.jpeg|\.png)(?:\?.*)?$", re.IGNORECASE
|
|
56
57
|
),
|
|
57
58
|
"roles": ["thumbnail"],
|
|
58
59
|
},
|
|
59
60
|
# quicklook
|
|
60
61
|
{
|
|
61
62
|
"pattern": re.compile(
|
|
62
|
-
r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpg|\.jpeg|\.png)
|
|
63
|
+
r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpg|\.jpeg|\.png)(?:\?.*)?$",
|
|
63
64
|
re.IGNORECASE,
|
|
64
65
|
),
|
|
65
66
|
"roles": ["overview"],
|
|
66
67
|
},
|
|
67
68
|
# default
|
|
68
|
-
{
|
|
69
|
+
{
|
|
70
|
+
"pattern": re.compile(r"^(?:.*[/\\])?([^/\\?]+)(?:\?.*)?$"),
|
|
71
|
+
"roles": ["auxiliary"],
|
|
72
|
+
},
|
|
69
73
|
]
|
|
@@ -38,12 +38,13 @@ class Sentinel1Driver(DatasetDriver):
|
|
|
38
38
|
(re.compile(r"grd", re.IGNORECASE), ""),
|
|
39
39
|
(re.compile(r"slc", re.IGNORECASE), ""),
|
|
40
40
|
(re.compile(r"ocn", re.IGNORECASE), ""),
|
|
41
|
-
(re.compile(r"iw", re.IGNORECASE), ""),
|
|
42
|
-
(re.compile(r"ew", re.IGNORECASE), ""),
|
|
41
|
+
(re.compile(r"(?<![A-Za-z])iw(?![A-Za-z])", re.IGNORECASE), ""),
|
|
42
|
+
(re.compile(r"(?<![A-Za-z])ew(?![A-Za-z])", re.IGNORECASE), ""),
|
|
43
43
|
(re.compile(r"wv", re.IGNORECASE), ""),
|
|
44
|
-
(re.compile(r"sm", re.IGNORECASE), ""),
|
|
45
|
-
(re.compile(r"raw([-_]s)?", re.IGNORECASE), ""),
|
|
44
|
+
(re.compile(r"(?<![A-Za-z])sm(?![A-Za-z])", re.IGNORECASE), ""),
|
|
45
|
+
(re.compile(r"(?<![A-Za-z])raw([-_]s)?(?![A-Za-z])", re.IGNORECASE), ""),
|
|
46
46
|
(re.compile(r"[t?0-9]{3,}", re.IGNORECASE), ""),
|
|
47
|
+
(re.compile(r"\b[0-9A-F]{3,}\b", re.IGNORECASE), ""),
|
|
47
48
|
(re.compile(r"-+"), "-"),
|
|
48
49
|
(re.compile(r"-+\."), "."),
|
|
49
50
|
(re.compile(r"_+"), "_"),
|
|
@@ -55,34 +56,38 @@ class Sentinel1Driver(DatasetDriver):
|
|
|
55
56
|
# data
|
|
56
57
|
{
|
|
57
58
|
"pattern": re.compile(
|
|
58
|
-
r"^.*?([vh]{2}).*\.(?:jp2|tiff?|dat)
|
|
59
|
+
r"^.*?([vh]{2}).*\.(?:jp2|tiff?|dat)(?:\?.*)?$", re.IGNORECASE
|
|
59
60
|
),
|
|
60
61
|
"roles": ["data"],
|
|
61
62
|
},
|
|
62
63
|
# metadata
|
|
63
64
|
{
|
|
64
65
|
"pattern": re.compile(
|
|
65
|
-
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)
|
|
66
|
+
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)(?:\?.*)?$",
|
|
67
|
+
re.IGNORECASE,
|
|
66
68
|
),
|
|
67
69
|
"roles": ["metadata"],
|
|
68
70
|
},
|
|
69
71
|
# thumbnail
|
|
70
72
|
{
|
|
71
73
|
"pattern": re.compile(
|
|
72
|
-
r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)
|
|
74
|
+
r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)(?:\?.*)?$", re.IGNORECASE
|
|
73
75
|
),
|
|
74
76
|
"roles": ["thumbnail"],
|
|
75
77
|
},
|
|
76
78
|
# quicklook
|
|
77
79
|
{
|
|
78
80
|
"pattern": re.compile(
|
|
79
|
-
r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpe?g|\.png)
|
|
81
|
+
r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpe?g|\.png)(?:\?.*)?$",
|
|
80
82
|
re.IGNORECASE,
|
|
81
83
|
),
|
|
82
84
|
"roles": ["overview"],
|
|
83
85
|
},
|
|
84
86
|
# default
|
|
85
|
-
{
|
|
87
|
+
{
|
|
88
|
+
"pattern": re.compile(r"^(?:.*[/\\])?([^/\\?]+)(?:\?.*)?$"),
|
|
89
|
+
"roles": ["auxiliary"],
|
|
90
|
+
},
|
|
86
91
|
]
|
|
87
92
|
|
|
88
93
|
def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
|
|
@@ -40,47 +40,54 @@ class Sentinel2Driver(DatasetDriver):
|
|
|
40
40
|
ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
|
|
41
41
|
# masks
|
|
42
42
|
{
|
|
43
|
-
"pattern": re.compile(
|
|
43
|
+
"pattern": re.compile(
|
|
44
|
+
r"^.*?(MSK_[^/\\]+)\.(?:jp2|tiff?)(?:\?.*)?$", re.IGNORECASE
|
|
45
|
+
),
|
|
44
46
|
"roles": ["data-mask"],
|
|
45
47
|
},
|
|
46
48
|
# visual
|
|
47
49
|
{
|
|
48
50
|
"pattern": re.compile(
|
|
49
|
-
r"^.*?(TCI)(_[0-9]+m)?\.(?:jp2|tiff?)
|
|
51
|
+
r"^.*?(TCI)(_[0-9]+m)?\.(?:jp2|tiff?)(?:\?.*)?$", re.IGNORECASE
|
|
50
52
|
),
|
|
51
53
|
"roles": ["visual"],
|
|
52
54
|
},
|
|
53
55
|
# bands
|
|
54
56
|
{
|
|
55
57
|
"pattern": re.compile(
|
|
56
|
-
r"^.*?([A-Z]+[0-9]*[A-Z]?)(_[0-9]+m)?\.(?:jp2|tiff?)
|
|
58
|
+
r"^.*?([A-Z]+[0-9]*[A-Z]?)(_[0-9]+m)?\.(?:jp2|tiff?)(?:\?.*)?$",
|
|
59
|
+
re.IGNORECASE,
|
|
57
60
|
),
|
|
58
61
|
"roles": ["data"],
|
|
59
62
|
},
|
|
60
63
|
# metadata
|
|
61
64
|
{
|
|
62
65
|
"pattern": re.compile(
|
|
63
|
-
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)
|
|
66
|
+
r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)(?:\?.*)?$",
|
|
67
|
+
re.IGNORECASE,
|
|
64
68
|
),
|
|
65
69
|
"roles": ["metadata"],
|
|
66
70
|
},
|
|
67
71
|
# thumbnail
|
|
68
72
|
{
|
|
69
73
|
"pattern": re.compile(
|
|
70
|
-
r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)
|
|
74
|
+
r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)(?:\?.*)?$", re.IGNORECASE
|
|
71
75
|
),
|
|
72
76
|
"roles": ["thumbnail"],
|
|
73
77
|
},
|
|
74
78
|
# quicklook
|
|
75
79
|
{
|
|
76
80
|
"pattern": re.compile(
|
|
77
|
-
r"^(?:.*[/\\])?[^/\\]+(-ql|preview|quick-?look)(\.jpe?g|\.png)
|
|
81
|
+
r"^(?:.*[/\\])?[^/\\]+(-ql|preview|quick-?look)(\.jpe?g|\.png)(?:\?.*)?$",
|
|
78
82
|
re.IGNORECASE,
|
|
79
83
|
),
|
|
80
84
|
"roles": ["overview"],
|
|
81
85
|
},
|
|
82
86
|
# default
|
|
83
|
-
{
|
|
87
|
+
{
|
|
88
|
+
"pattern": re.compile(r"^(?:.*[/\\])?([^/\\?]+)(?:\?.*)?$"),
|
|
89
|
+
"roles": ["auxiliary"],
|
|
90
|
+
},
|
|
84
91
|
]
|
|
85
92
|
|
|
86
93
|
def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
|
|
@@ -1731,16 +1731,19 @@ def get_queryable_from_provider(
|
|
|
1731
1731
|
mapping_values = [
|
|
1732
1732
|
v[0] if isinstance(v, list) else "" for v in metadata_mapping.values()
|
|
1733
1733
|
]
|
|
1734
|
+
StacQueryables = Queryables.from_stac_models()
|
|
1734
1735
|
if provider_queryable in mapping_values:
|
|
1735
1736
|
ind = mapping_values.index(provider_queryable)
|
|
1736
|
-
return
|
|
1737
|
+
return StacQueryables.get_queryable_from_alias(
|
|
1738
|
+
list(metadata_mapping.keys())[ind]
|
|
1739
|
+
)
|
|
1737
1740
|
for param, param_conf in metadata_mapping.items():
|
|
1738
1741
|
if (
|
|
1739
1742
|
isinstance(param_conf, list)
|
|
1740
1743
|
and param_conf[0]
|
|
1741
1744
|
and re.search(pattern, param_conf[0])
|
|
1742
1745
|
):
|
|
1743
|
-
return
|
|
1746
|
+
return StacQueryables.get_queryable_from_alias(param)
|
|
1744
1747
|
return None
|
|
1745
1748
|
|
|
1746
1749
|
|
eodag/api/provider.py
CHANGED
eodag/api/search_result.py
CHANGED
|
@@ -32,7 +32,7 @@ from eodag.plugins.crunch.filter_latest_intersect import FilterLatestIntersect
|
|
|
32
32
|
from eodag.plugins.crunch.filter_latest_tpl_name import FilterLatestByName
|
|
33
33
|
from eodag.plugins.crunch.filter_overlap import FilterOverlap
|
|
34
34
|
from eodag.plugins.crunch.filter_property import FilterProperty
|
|
35
|
-
from eodag.utils import GENERIC_STAC_PROVIDER, STAC_SEARCH_PLUGINS
|
|
35
|
+
from eodag.utils import GENERIC_STAC_PROVIDER, STAC_SEARCH_PLUGINS, STAC_VERSION
|
|
36
36
|
from eodag.utils.exceptions import MisconfiguredError
|
|
37
37
|
|
|
38
38
|
if TYPE_CHECKING:
|
|
@@ -206,6 +206,9 @@ class SearchResult(UserList[EOProduct]):
|
|
|
206
206
|
"eodag:search_params": geojson_search_params or None,
|
|
207
207
|
"eodag:raise_errors": self.raise_errors,
|
|
208
208
|
},
|
|
209
|
+
"links": [],
|
|
210
|
+
"stac_extensions": [],
|
|
211
|
+
"stac_version": STAC_VERSION,
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
def as_shapely_geometry_object(self) -> GeometryCollection:
|