eodag 3.10.1__py3-none-any.whl → 4.0.0a2__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/__init__.py +6 -1
- eodag/api/collection.py +353 -0
- eodag/api/core.py +606 -641
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +74 -56
- eodag/api/product/drivers/__init__.py +4 -46
- eodag/api/product/drivers/base.py +0 -28
- eodag/api/product/metadata_mapping.py +178 -216
- eodag/api/search_result.py +156 -15
- eodag/cli.py +83 -403
- eodag/config.py +81 -51
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +36 -25
- eodag/plugins/apis/usgs.py +55 -40
- eodag/plugins/authentication/base.py +1 -3
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +46 -42
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +6 -4
- eodag/plugins/search/base.py +131 -80
- eodag/plugins/search/build_search_result.py +245 -173
- eodag/plugins/search/cop_marine.py +87 -56
- eodag/plugins/search/csw.py +47 -37
- eodag/plugins/search/qssearch.py +653 -429
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +43 -44
- eodag/resources/{product_types.yml → collections.yml} +2594 -2453
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2706 -2733
- eodag/resources/stac_provider.yml +50 -92
- eodag/resources/user_conf_template.yml +9 -0
- eodag/types/__init__.py +2 -0
- eodag/types/queryables.py +70 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +97 -21
- eodag/utils/dates.py +0 -12
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- eodag/utils/repr.py +2 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
- eodag-4.0.0a2.dist-info/RECORD +93 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -71
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.10.1.dist-info/RECORD +0 -116
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
|
@@ -43,7 +43,6 @@ from eodag.types.queryables import Queryables
|
|
|
43
43
|
from eodag.utils import (
|
|
44
44
|
DEFAULT_PROJ,
|
|
45
45
|
DEFAULT_SHAPELY_GEOMETRY,
|
|
46
|
-
_deprecated,
|
|
47
46
|
deepcopy,
|
|
48
47
|
dict_items_recursive_apply,
|
|
49
48
|
format_string,
|
|
@@ -59,6 +58,8 @@ from eodag.utils.dates import get_timestamp
|
|
|
59
58
|
from eodag.utils.exceptions import ValidationError
|
|
60
59
|
|
|
61
60
|
if TYPE_CHECKING:
|
|
61
|
+
from collections.abc import Mapping, Sequence
|
|
62
|
+
|
|
62
63
|
from shapely.geometry.base import BaseGeometry
|
|
63
64
|
|
|
64
65
|
from eodag.config import PluginConfig
|
|
@@ -71,9 +72,9 @@ INGEST_CONVERSION_REGEX = re.compile(
|
|
|
71
72
|
)
|
|
72
73
|
NOT_AVAILABLE = "Not Available"
|
|
73
74
|
NOT_MAPPED = "Not Mapped"
|
|
74
|
-
ONLINE_STATUS = "
|
|
75
|
-
STAGING_STATUS = "
|
|
76
|
-
OFFLINE_STATUS = "
|
|
75
|
+
ONLINE_STATUS = "succeeded"
|
|
76
|
+
STAGING_STATUS = "ordered"
|
|
77
|
+
OFFLINE_STATUS = "orderable"
|
|
77
78
|
COORDS_ROUNDING_PRECISION = 4
|
|
78
79
|
WKT_MAX_LEN = 1600
|
|
79
80
|
COMPLEX_QS_REGEX = re.compile(r"^(.+=)?([^=]*)({.+})+([^=&]*)$")
|
|
@@ -97,23 +98,23 @@ def get_metadata_path(
|
|
|
97
98
|
search:
|
|
98
99
|
...
|
|
99
100
|
metadata_mapping:
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
- $.properties.
|
|
101
|
+
platform:
|
|
102
|
+
- platform
|
|
103
|
+
- $.properties.platform
|
|
103
104
|
id: $.properties.id
|
|
104
105
|
...
|
|
105
106
|
...
|
|
106
107
|
...
|
|
107
108
|
|
|
108
|
-
Then the metadata `id` is not queryable for this provider meanwhile `
|
|
109
|
-
is queryable. The first value of the `metadata_mapping.
|
|
110
|
-
eodag search parameter `
|
|
109
|
+
Then the metadata `id` is not queryable for this provider meanwhile `platform`
|
|
110
|
+
is queryable. The first value of the `metadata_mapping.platform` is how the
|
|
111
|
+
eodag search parameter `platform` is interpreted in the
|
|
111
112
|
:class:`~eodag.plugins.search.base.Search` plugin implemented by `provider`, and is
|
|
112
113
|
used when eodag delegates search process to the corresponding plugin.
|
|
113
114
|
|
|
114
115
|
:param map_value: The value originating from the definition of `metadata_mapping`
|
|
115
116
|
in the provider search config. For example, it is the list
|
|
116
|
-
`['
|
|
117
|
+
`['platform', '$.properties.platform']` with the sample
|
|
117
118
|
above. Or the string `$.properties.id`.
|
|
118
119
|
:returns: Either, None and the path to the metadata value, or a list of converter
|
|
119
120
|
and its args, and the path to the metadata value.
|
|
@@ -151,6 +152,7 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
151
152
|
|
|
152
153
|
The currently understood converters are:
|
|
153
154
|
- ``ceda_collection_name``: generate a CEDA collection name from a string
|
|
155
|
+
- ``wekeo_to_cop_collection``: converts the name of a collection from the WEkEO format to the Copernicus format
|
|
154
156
|
- ``csv_list``: convert to a comma separated list
|
|
155
157
|
- ``datetime_to_timestamp_milliseconds``: converts a utc date string to a timestamp in milliseconds
|
|
156
158
|
- ``dict_filter_and_sub``: filter dict items using jsonpath and then apply recursive_sub_str
|
|
@@ -160,15 +162,20 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
160
162
|
- ``from_georss``: convert GeoRSS to shapely geometry / WKT in DEFAULT_PROJ
|
|
161
163
|
- ``get_ecmwf_time``: get the time of a datetime string in the ECMWF format
|
|
162
164
|
- ``get_group_name``: get the matching regex group name
|
|
165
|
+
- ``literalize_unicode``: convert a string to its raw Unicode literal form
|
|
166
|
+
- ``not_available``: replace value with "Not Available"
|
|
163
167
|
- ``recursive_sub_str``: recursively substitue in the structure (e.g. dict) values matching a regex
|
|
164
168
|
- ``remove_extension``: on a string that contains dots, only take the first part of the list obtained by
|
|
165
169
|
splitting the string on dots
|
|
166
170
|
- ``replace_str``: execute "string".replace(old, new)
|
|
171
|
+
- ``replace_str_tuple``: apply multiple replacements on a string (parts or complete)
|
|
172
|
+
- ``replace_tuple``: apply multiple replacements matching whole value
|
|
167
173
|
- ``s2msil2a_title_to_aws_productinfo``: used to generate SAFE format metadata for data from AWS
|
|
168
174
|
- ``sanitize``: sanitize string
|
|
169
175
|
- ``slice_str``: slice a string (equivalent to s[start, end, step])
|
|
176
|
+
- ``split``: split a string using given separator
|
|
170
177
|
- ``split_cop_dem_id``: get the bbox by splitting the product id
|
|
171
|
-
- ``split_corine_id``: get the
|
|
178
|
+
- ``split_corine_id``: get the collection by splitting the product id
|
|
172
179
|
- ``to_bounds_lists``: convert to list(s) of bounds
|
|
173
180
|
- ``to_datetime_dict``: convert a datetime string to a dictionary where values are either a string or a list
|
|
174
181
|
- ``to_ewkt``: convert to EWKT (Extended Well-Known text)
|
|
@@ -200,6 +207,44 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
200
207
|
self.custom_converter: Optional[Callable] = None
|
|
201
208
|
self.custom_args: Optional[str] = None
|
|
202
209
|
|
|
210
|
+
def parse(self, format_string: str):
|
|
211
|
+
"""
|
|
212
|
+
Rewrite field names in the template before the base parser sees them.
|
|
213
|
+
Replaces `{foo:bar}` with `{foo__bar}`.
|
|
214
|
+
"""
|
|
215
|
+
pattern = re.compile(r"{([^{}]+)}")
|
|
216
|
+
|
|
217
|
+
def rewrite_field(field: str) -> str:
|
|
218
|
+
# If there's a format spec (e.g., {foo:bar:.2f}), preserve it
|
|
219
|
+
if ":" in field and not field.lstrip().startswith(("!", ".", ":")):
|
|
220
|
+
before_colon, *after = field.split(":")
|
|
221
|
+
# Don't confuse format spec with field name colons
|
|
222
|
+
if len(after) == 1 and "." in after[0]:
|
|
223
|
+
# It's a format specifier, leave it
|
|
224
|
+
return field
|
|
225
|
+
return field.replace(":", "__", 1)
|
|
226
|
+
return field
|
|
227
|
+
|
|
228
|
+
# Replace in string (but not in format_spec itself)
|
|
229
|
+
safe_template = pattern.sub(
|
|
230
|
+
lambda m: "{" + rewrite_field(m.group(1)) + "}", format_string
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Yield from base class
|
|
234
|
+
yield from super().parse(safe_template)
|
|
235
|
+
|
|
236
|
+
def get_value(
|
|
237
|
+
self, key: Any, args: "Sequence[Any]", kwargs: "Mapping[str, Any]"
|
|
238
|
+
) -> Any:
|
|
239
|
+
"""
|
|
240
|
+
Look up rewritten field name in kwargs by converting __ back to :
|
|
241
|
+
"""
|
|
242
|
+
if isinstance(key, str):
|
|
243
|
+
original_key = key.replace("__", ":")
|
|
244
|
+
key_with_COLON = key.replace("__", "_COLON_")
|
|
245
|
+
return kwargs.get(original_key) or kwargs.get(key_with_COLON)
|
|
246
|
+
return super().get_value(key, args, kwargs)
|
|
247
|
+
|
|
203
248
|
def get_field(self, field_name: str, args: Any, kwargs: Any) -> Any:
|
|
204
249
|
conversion_func_spec = self.CONVERSION_REGEX.match(field_name)
|
|
205
250
|
# Register a custom converter if any for later use (see convert_field)
|
|
@@ -209,6 +254,9 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
209
254
|
field_name = conversion_func_spec.groupdict()["field_name"]
|
|
210
255
|
converter = conversion_func_spec.groupdict()["converter"]
|
|
211
256
|
self.custom_args = conversion_func_spec.groupdict()["args"]
|
|
257
|
+
# converts back "_COLON_" to ":"
|
|
258
|
+
if self.custom_args is not None and "_COLON_" in self.custom_args:
|
|
259
|
+
self.custom_args = self.custom_args.replace("_COLON_", ":")
|
|
212
260
|
self.custom_converter = getattr(self, "convert_{}".format(converter))
|
|
213
261
|
|
|
214
262
|
return super(MetadataFormatter, self).get_field(field_name, args, kwargs)
|
|
@@ -532,11 +580,15 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
532
580
|
return re.sub(old, new, value)
|
|
533
581
|
|
|
534
582
|
@staticmethod
|
|
535
|
-
def convert_replace_str_tuple(
|
|
583
|
+
def convert_replace_str_tuple(
|
|
584
|
+
value: Union[str, dict[Any, Any]], args: str
|
|
585
|
+
) -> str:
|
|
536
586
|
"""
|
|
537
|
-
Apply multiple replacements on a string.
|
|
538
|
-
|
|
539
|
-
|
|
587
|
+
Apply multiple replacements on a string (parts or complete).
|
|
588
|
+
|
|
589
|
+
:param value: input string or dict.
|
|
590
|
+
:param args: string representing a list/tuple of (old, new) pairs, like
|
|
591
|
+
``'(("old1", "new1"), ("old2", "new2"))'``
|
|
540
592
|
"""
|
|
541
593
|
if isinstance(value, dict):
|
|
542
594
|
value = MetadataFormatter.convert_to_geojson(value)
|
|
@@ -560,13 +612,70 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
560
612
|
|
|
561
613
|
return value
|
|
562
614
|
|
|
615
|
+
@staticmethod
|
|
616
|
+
def convert_replace_tuple(value: Any, args: str) -> Any:
|
|
617
|
+
"""
|
|
618
|
+
Apply multiple replacements matching whole value.
|
|
619
|
+
|
|
620
|
+
:param value: input to replace
|
|
621
|
+
:param args: string representing a list/tuple of (old, new) pairs, like
|
|
622
|
+
``'((["old1"], "new1"), ("old2", ["new2"]))'``
|
|
623
|
+
"""
|
|
624
|
+
# args sera une chaîne représentant une liste/tuple de tuples
|
|
625
|
+
replacements = ast.literal_eval(args)
|
|
626
|
+
|
|
627
|
+
if not isinstance(replacements, (list, tuple)):
|
|
628
|
+
raise TypeError(
|
|
629
|
+
f"convert_replace_str_tuple expects a list/tuple of (old,new) pairs. "
|
|
630
|
+
f"Got {type(replacements)}: {replacements}"
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
for old, new in replacements:
|
|
634
|
+
if old == value:
|
|
635
|
+
return new
|
|
636
|
+
|
|
637
|
+
return value
|
|
638
|
+
|
|
639
|
+
@staticmethod
|
|
640
|
+
def convert_not_available(value: Any) -> str:
|
|
641
|
+
"""Convert any value to "Not Available".
|
|
642
|
+
|
|
643
|
+
This is more useful than "$.null" to keep original jsonpath while parsing in metadata_mapping.
|
|
644
|
+
"""
|
|
645
|
+
return NOT_AVAILABLE
|
|
646
|
+
|
|
647
|
+
@staticmethod
|
|
648
|
+
def convert_split(value: str, separator: str) -> list[str]:
|
|
649
|
+
"""Split a string using given separator"""
|
|
650
|
+
if value == NOT_AVAILABLE:
|
|
651
|
+
return [NOT_AVAILABLE]
|
|
652
|
+
if not isinstance(value, str):
|
|
653
|
+
logger.warning(
|
|
654
|
+
"Could not split non-string value %s (type %s)", value, type(value)
|
|
655
|
+
)
|
|
656
|
+
return [NOT_AVAILABLE]
|
|
657
|
+
if not isinstance(separator, str):
|
|
658
|
+
logger.warning(
|
|
659
|
+
"Could not split string using non-string separator %s (type %s)",
|
|
660
|
+
separator,
|
|
661
|
+
type(separator),
|
|
662
|
+
)
|
|
663
|
+
return [NOT_AVAILABLE]
|
|
664
|
+
return value.split(separator)
|
|
665
|
+
|
|
563
666
|
@staticmethod
|
|
564
667
|
def convert_ceda_collection_name(value: str) -> str:
|
|
565
668
|
data_regex = re.compile(r"/data/(?P<name>.+?)/?$")
|
|
566
669
|
match = data_regex.search(value)
|
|
567
670
|
if match:
|
|
568
671
|
return match.group("name").replace("/", "_").upper()
|
|
569
|
-
return
|
|
672
|
+
return NOT_AVAILABLE
|
|
673
|
+
|
|
674
|
+
@staticmethod
|
|
675
|
+
def convert_literalize_unicode(value: str) -> str:
|
|
676
|
+
if value == NOT_AVAILABLE:
|
|
677
|
+
return value
|
|
678
|
+
return value.encode("raw_unicode_escape").decode("utf-8")
|
|
570
679
|
|
|
571
680
|
@staticmethod
|
|
572
681
|
def convert_recursive_sub_str(
|
|
@@ -656,7 +765,7 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
656
765
|
int(x.strip()) if x.strip().lstrip("-").isdigit() else None
|
|
657
766
|
for x in args.split(",")
|
|
658
767
|
]
|
|
659
|
-
return string[cmin:cmax:cstep]
|
|
768
|
+
return string[cmin:cmax:cstep] or NOT_AVAILABLE
|
|
660
769
|
|
|
661
770
|
@staticmethod
|
|
662
771
|
def convert_to_lower(string: str) -> str:
|
|
@@ -705,7 +814,7 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
705
814
|
if id_match:
|
|
706
815
|
id_dict = id_match.groupdict()
|
|
707
816
|
return (
|
|
708
|
-
"https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/%s/%s/%s/%s/%s/%s/0/{
|
|
817
|
+
"https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/%s/%s/%s/%s/%s/%s/0/{_collection}.json"
|
|
709
818
|
% (
|
|
710
819
|
id_dict["tile1"],
|
|
711
820
|
id_dict["tile2"],
|
|
@@ -719,49 +828,10 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
719
828
|
logger.error("Could not extract title infos from %s" % string)
|
|
720
829
|
return NOT_AVAILABLE
|
|
721
830
|
|
|
722
|
-
@staticmethod
|
|
723
|
-
@_deprecated(
|
|
724
|
-
reason="Method that was used in previous wekeo provider configuration, but not used anymore",
|
|
725
|
-
version="3.7.1",
|
|
726
|
-
)
|
|
727
|
-
def convert_split_id_into_s1_params(product_id: str) -> dict[str, str]:
|
|
728
|
-
parts: list[str] = re.split(r"_(?!_)", product_id)
|
|
729
|
-
if len(parts) < 9:
|
|
730
|
-
logger.error(
|
|
731
|
-
"id %s does not match expected Sentinel-1 id format", product_id
|
|
732
|
-
)
|
|
733
|
-
raise ValueError
|
|
734
|
-
params = {"sensorMode": parts[1]}
|
|
735
|
-
level = "LEVEL" + parts[3][0]
|
|
736
|
-
params["processingLevel"] = level
|
|
737
|
-
start_date = datetime.strptime(parts[4], "%Y%m%dT%H%M%S") - timedelta(
|
|
738
|
-
seconds=1
|
|
739
|
-
)
|
|
740
|
-
params["startDate"] = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
741
|
-
end_date = datetime.strptime(parts[5], "%Y%m%dT%H%M%S") + timedelta(
|
|
742
|
-
seconds=1
|
|
743
|
-
)
|
|
744
|
-
params["endDate"] = end_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
745
|
-
product_type = parts[2][:3]
|
|
746
|
-
if product_type == "GRD" and parts[-1] == "COG":
|
|
747
|
-
product_type = "GRD-COG"
|
|
748
|
-
elif product_type == "GRD" and parts[-2] == "CARD" and parts[-1] == "BS":
|
|
749
|
-
product_type = "CARD-BS"
|
|
750
|
-
params["productType"] = product_type
|
|
751
|
-
polarisation_mapping = {
|
|
752
|
-
"SV": "VV",
|
|
753
|
-
"SH": "HH",
|
|
754
|
-
"DH": "HH+HV",
|
|
755
|
-
"DV": "VV+VH",
|
|
756
|
-
}
|
|
757
|
-
polarisation = polarisation_mapping[parts[3][2:]]
|
|
758
|
-
params["polarisation"] = polarisation
|
|
759
|
-
return params
|
|
760
|
-
|
|
761
831
|
@staticmethod
|
|
762
832
|
def convert_split_id_into_s3_params(product_id: str) -> dict[str, str]:
|
|
763
833
|
parts: list[str] = re.split(r"_(?!_)", product_id)
|
|
764
|
-
params = {"
|
|
834
|
+
params = {"collection": product_id[4:15]}
|
|
765
835
|
dates = re.findall("[0-9]{8}T[0-9]{6}", product_id)
|
|
766
836
|
start_date = datetime.strptime(dates[0], "%Y%m%dT%H%M%S") - timedelta(
|
|
767
837
|
seconds=1
|
|
@@ -775,48 +845,6 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
775
845
|
params["sat"] = "Sentinel-" + parts[0][1:]
|
|
776
846
|
return params
|
|
777
847
|
|
|
778
|
-
@staticmethod
|
|
779
|
-
@_deprecated(
|
|
780
|
-
reason="Method that was used in previous wekeo provider configuration, but not used anymore",
|
|
781
|
-
version="3.7.1",
|
|
782
|
-
)
|
|
783
|
-
def convert_split_id_into_s5p_params(product_id: str) -> dict[str, str]:
|
|
784
|
-
parts: list[str] = re.split(r"_(?!_)", product_id)
|
|
785
|
-
params = {
|
|
786
|
-
"productType": product_id[9:19],
|
|
787
|
-
"processingMode": parts[1],
|
|
788
|
-
"processingLevel": parts[2].replace("_", ""),
|
|
789
|
-
}
|
|
790
|
-
start_date = datetime.strptime(parts[-6], "%Y%m%dT%H%M%S") - timedelta(
|
|
791
|
-
seconds=10
|
|
792
|
-
)
|
|
793
|
-
params["startDate"] = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
794
|
-
end_date = datetime.strptime(parts[-5], "%Y%m%dT%H%M%S") + timedelta(
|
|
795
|
-
seconds=10
|
|
796
|
-
)
|
|
797
|
-
params["endDate"] = end_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
798
|
-
return params
|
|
799
|
-
|
|
800
|
-
@staticmethod
|
|
801
|
-
@_deprecated(
|
|
802
|
-
reason="Method that was used in previous wekeo provider configuration, but not used anymore",
|
|
803
|
-
version="3.7.1",
|
|
804
|
-
)
|
|
805
|
-
def convert_split_cop_dem_id(product_id: str) -> list[int]:
|
|
806
|
-
parts = product_id.split("_")
|
|
807
|
-
lattitude = parts[3]
|
|
808
|
-
longitude = parts[5]
|
|
809
|
-
if lattitude[0] == "N":
|
|
810
|
-
lat_num = int(lattitude[1:])
|
|
811
|
-
else:
|
|
812
|
-
lat_num = -1 * int(lattitude[1:])
|
|
813
|
-
if longitude[0] == "E":
|
|
814
|
-
long_num = int(longitude[1:])
|
|
815
|
-
else:
|
|
816
|
-
long_num = -1 * int(longitude[1:])
|
|
817
|
-
bbox = [long_num - 1, lat_num - 1, long_num + 1, lat_num + 1]
|
|
818
|
-
return bbox
|
|
819
|
-
|
|
820
848
|
@staticmethod
|
|
821
849
|
def convert_dates_from_cmems_id(product_id: str):
|
|
822
850
|
date_format_1 = "[0-9]{10}"
|
|
@@ -1037,10 +1065,29 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
1037
1065
|
assets_dict[asset_basename] = assets_dict.pop(asset_name)
|
|
1038
1066
|
return assets_dict
|
|
1039
1067
|
|
|
1068
|
+
@staticmethod
|
|
1069
|
+
def convert_wekeo_to_cop_collection(val: str, prefix: str) -> str:
|
|
1070
|
+
"""Converts the name of a collection from the WEkEO format to the Copernicus format."""
|
|
1071
|
+
return val.removeprefix(prefix).lower().replace("_", "-")
|
|
1072
|
+
|
|
1040
1073
|
# if stac extension colon separator `:` is in search params, parse it to prevent issues with vformat
|
|
1041
|
-
if re.search(r"{[\w-]*:[\w#-]
|
|
1042
|
-
search_param = re.sub(
|
|
1074
|
+
if re.search(r"{[\w-]*:[\w#-]*\(?.*}", search_param):
|
|
1075
|
+
search_param = re.sub(
|
|
1076
|
+
r"{([\w-]*):([\w#-]*\(?.*)}",
|
|
1077
|
+
r"{\1_COLON_\2}",
|
|
1078
|
+
search_param,
|
|
1079
|
+
)
|
|
1043
1080
|
kwargs = {k.replace(":", "_COLON_"): v for k, v in kwargs.items()}
|
|
1081
|
+
# convert colons `:` in the parameters passed to the converter (e.g. 'foo#boo(fun:with:colons)')
|
|
1082
|
+
if re.search(r"{[\w-]*#[\w-]*\([^)]*:.*}", search_param):
|
|
1083
|
+
search_param = re.sub(
|
|
1084
|
+
r"({[\w-]*#[\w-]*)\(([^)]*)(.*})",
|
|
1085
|
+
lambda m: m.group(1)
|
|
1086
|
+
+ "("
|
|
1087
|
+
+ m.group(2).replace(":", "_COLON_")
|
|
1088
|
+
+ m.group(3),
|
|
1089
|
+
search_param,
|
|
1090
|
+
)
|
|
1044
1091
|
|
|
1045
1092
|
return MetadataFormatter().vformat(search_param, args, kwargs)
|
|
1046
1093
|
|
|
@@ -1062,6 +1109,7 @@ def properties_from_json(
|
|
|
1062
1109
|
`discovery_path` (String representation of jsonpath)
|
|
1063
1110
|
:returns: The metadata of the :class:`~eodag.api.product._product.EOProduct`
|
|
1064
1111
|
"""
|
|
1112
|
+
extracted_value: Any
|
|
1065
1113
|
properties: dict[str, Any] = {}
|
|
1066
1114
|
templates = {}
|
|
1067
1115
|
used_jsonpaths = []
|
|
@@ -1072,7 +1120,7 @@ def properties_from_json(
|
|
|
1072
1120
|
else:
|
|
1073
1121
|
conversion_or_none, path_or_text = value
|
|
1074
1122
|
if isinstance(path_or_text, str):
|
|
1075
|
-
if re.search(r"
|
|
1123
|
+
if re.search(r"{[^{}]+}", path_or_text):
|
|
1076
1124
|
templates[metadata] = path_or_text
|
|
1077
1125
|
else:
|
|
1078
1126
|
properties[metadata] = path_or_text
|
|
@@ -1081,11 +1129,13 @@ def properties_from_json(
|
|
|
1081
1129
|
match = path_or_text.find(json)
|
|
1082
1130
|
except KeyError:
|
|
1083
1131
|
match = []
|
|
1084
|
-
if len(match) ==
|
|
1132
|
+
if len(match) == 0:
|
|
1133
|
+
extracted_value = NOT_AVAILABLE
|
|
1134
|
+
elif len(match) == 1:
|
|
1085
1135
|
extracted_value = match[0].value
|
|
1086
1136
|
used_jsonpaths.append(match[0].full_path)
|
|
1087
1137
|
else:
|
|
1088
|
-
extracted_value =
|
|
1138
|
+
extracted_value = [m.value for m in match]
|
|
1089
1139
|
if extracted_value is None:
|
|
1090
1140
|
properties[metadata] = None
|
|
1091
1141
|
else:
|
|
@@ -1157,6 +1207,7 @@ def properties_from_json(
|
|
|
1157
1207
|
if isinstance(discovery_jsonpath, JSONPath)
|
|
1158
1208
|
else []
|
|
1159
1209
|
)
|
|
1210
|
+
mtd_prefix = discovery_config.get("metadata_prefix", "provider")
|
|
1160
1211
|
for found_jsonpath in discovered_properties:
|
|
1161
1212
|
if "metadata_path_id" in discovery_config.keys():
|
|
1162
1213
|
found_key_paths = string_to_jsonpath(
|
|
@@ -1178,8 +1229,13 @@ def properties_from_json(
|
|
|
1178
1229
|
if (
|
|
1179
1230
|
re.compile(discovery_pattern).match(found_key)
|
|
1180
1231
|
and found_key not in properties.keys()
|
|
1232
|
+
and f"{mtd_prefix}:{found_key}" not in properties.keys()
|
|
1181
1233
|
and used_jsonpath not in used_jsonpaths
|
|
1182
1234
|
):
|
|
1235
|
+
# prepend with default STAC prefix if none is already used
|
|
1236
|
+
if ":" not in found_key:
|
|
1237
|
+
found_key = f"{mtd_prefix}:{found_key}"
|
|
1238
|
+
|
|
1183
1239
|
if "metadata_path_value" in discovery_config.keys():
|
|
1184
1240
|
found_value_path = string_to_jsonpath(
|
|
1185
1241
|
discovery_config["metadata_path_value"], force=True
|
|
@@ -1404,7 +1460,7 @@ def mtd_cfg_as_conversion_and_querypath(
|
|
|
1404
1460
|
|
|
1405
1461
|
|
|
1406
1462
|
def format_query_params(
|
|
1407
|
-
|
|
1463
|
+
collection: str,
|
|
1408
1464
|
config: PluginConfig,
|
|
1409
1465
|
query_dict: dict[str, Any],
|
|
1410
1466
|
error_context: str = "",
|
|
@@ -1415,14 +1471,14 @@ def format_query_params(
|
|
|
1415
1471
|
# . not allowed in eodag_search_key, replaced with %2E
|
|
1416
1472
|
query_dict = {k.replace(".", "%2E"): v for k, v in query_dict.items()}
|
|
1417
1473
|
|
|
1418
|
-
|
|
1474
|
+
collection_metadata_mapping = dict(
|
|
1419
1475
|
config.metadata_mapping,
|
|
1420
|
-
**config.products.get(
|
|
1476
|
+
**config.products.get(collection, {}).get("metadata_mapping", {}),
|
|
1421
1477
|
)
|
|
1422
1478
|
|
|
1423
1479
|
# Raise error if non-queryables parameters are used and raise_mtd_discovery_error configured
|
|
1424
1480
|
if (
|
|
1425
|
-
raise_mtd_discovery_error := config.products.get(
|
|
1481
|
+
raise_mtd_discovery_error := config.products.get(collection, {})
|
|
1426
1482
|
.get("discover_metadata", {})
|
|
1427
1483
|
.get("raise_mtd_discovery_error")
|
|
1428
1484
|
) is None:
|
|
@@ -1436,7 +1492,7 @@ def format_query_params(
|
|
|
1436
1492
|
queryables = _get_queryables(
|
|
1437
1493
|
query_dict,
|
|
1438
1494
|
config,
|
|
1439
|
-
|
|
1495
|
+
collection_metadata_mapping,
|
|
1440
1496
|
raise_mtd_discovery_error,
|
|
1441
1497
|
error_context,
|
|
1442
1498
|
)
|
|
@@ -1461,7 +1517,7 @@ def format_query_params(
|
|
|
1461
1517
|
parts = provider_search_param.split("=")
|
|
1462
1518
|
if len(parts) == 1:
|
|
1463
1519
|
formatted_query_param = format_metadata(
|
|
1464
|
-
provider_search_param,
|
|
1520
|
+
provider_search_param, collection, **query_dict
|
|
1465
1521
|
)
|
|
1466
1522
|
formatted_query_param = formatted_query_param.replace("'", '"')
|
|
1467
1523
|
if "{{" in provider_search_param:
|
|
@@ -1472,6 +1528,11 @@ def format_query_params(
|
|
|
1472
1528
|
formatted_query_param = remove_str_array_quotes(
|
|
1473
1529
|
formatted_query_param
|
|
1474
1530
|
)
|
|
1531
|
+
if NOT_AVAILABLE in formatted_query_param:
|
|
1532
|
+
raise ValidationError(
|
|
1533
|
+
"Could not parse %s query parameter, got %s"
|
|
1534
|
+
% (eodag_search_key, formatted_query_param)
|
|
1535
|
+
)
|
|
1475
1536
|
|
|
1476
1537
|
# json query string (for POST request)
|
|
1477
1538
|
update_nested_dict(
|
|
@@ -1485,7 +1546,7 @@ def format_query_params(
|
|
|
1485
1546
|
else:
|
|
1486
1547
|
provider_search_key, provider_value = parts
|
|
1487
1548
|
query_params[provider_search_key] = format_metadata(
|
|
1488
|
-
provider_value,
|
|
1549
|
+
provider_value, collection, **query_dict
|
|
1489
1550
|
)
|
|
1490
1551
|
else:
|
|
1491
1552
|
query_params[provider_search_param] = user_input
|
|
@@ -1499,12 +1560,12 @@ def format_query_params(
|
|
|
1499
1560
|
# Now add formatted free text search parameters (this is for cases where a
|
|
1500
1561
|
# complex query through a free text search parameter is available for the
|
|
1501
1562
|
# provider and needed for the consumer)
|
|
1502
|
-
|
|
1563
|
+
collection_metadata_mapping = dict(
|
|
1503
1564
|
config.metadata_mapping,
|
|
1504
|
-
**config.products.get(
|
|
1565
|
+
**config.products.get(collection, {}).get("metadata_mapping", {}),
|
|
1505
1566
|
)
|
|
1506
1567
|
literal_search_params.update(
|
|
1507
|
-
_format_free_text_search(config,
|
|
1568
|
+
_format_free_text_search(config, collection_metadata_mapping, **query_dict)
|
|
1508
1569
|
)
|
|
1509
1570
|
for provider_search_key, provider_value in literal_search_params.items():
|
|
1510
1571
|
if isinstance(provider_value, list):
|
|
@@ -1601,7 +1662,7 @@ def _get_queryables(
|
|
|
1601
1662
|
# raise an error when a query param not allowed by the provider is found
|
|
1602
1663
|
if not isinstance(md_mapping, list) and raise_mtd_discovery_error:
|
|
1603
1664
|
raise ValidationError(
|
|
1604
|
-
"Search parameters which are not queryable are disallowed for this
|
|
1665
|
+
"Search parameters which are not queryable are disallowed for this collection on this provider: "
|
|
1605
1666
|
f"please remove '{eodag_search_key}' from your search parameters. {error_context}",
|
|
1606
1667
|
{eodag_search_key},
|
|
1607
1668
|
)
|
|
@@ -1723,102 +1784,3 @@ def get_provider_queryable_key(
|
|
|
1723
1784
|
return ""
|
|
1724
1785
|
else:
|
|
1725
1786
|
return eodag_key
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
# Keys taken from OpenSearch extension for Earth Observation http://docs.opengeospatial.org/is/13-026r9/13-026r9.html
|
|
1729
|
-
# For a metadata to be queryable, The way to query it must be specified in the
|
|
1730
|
-
# provider metadata_mapping configuration parameter. It will be automatically
|
|
1731
|
-
# detected as queryable by eodag when this is done
|
|
1732
|
-
OSEO_METADATA_MAPPING = {
|
|
1733
|
-
# Opensearch resource identifier within the search engine context (in our case
|
|
1734
|
-
# within the context of the data provider)
|
|
1735
|
-
"uid": "$.uid",
|
|
1736
|
-
# OpenSearch Parameters for Collection Search (Table 3)
|
|
1737
|
-
"productType": "$.properties.productType",
|
|
1738
|
-
"doi": "$.properties.doi",
|
|
1739
|
-
"platform": "$.properties.platform",
|
|
1740
|
-
"platformSerialIdentifier": "$.properties.platformSerialIdentifier",
|
|
1741
|
-
"instrument": "$.properties.instrument",
|
|
1742
|
-
"sensorType": "$.properties.sensorType",
|
|
1743
|
-
"compositeType": "$.properties.compositeType",
|
|
1744
|
-
"processingLevel": "$.properties.processingLevel",
|
|
1745
|
-
"orbitType": "$.properties.orbitType",
|
|
1746
|
-
"spectralRange": "$.properties.spectralRange",
|
|
1747
|
-
"wavelengths": "$.properties.wavelengths",
|
|
1748
|
-
"hasSecurityConstraints": "$.properties.hasSecurityConstraints",
|
|
1749
|
-
"dissemination": "$.properties.dissemination",
|
|
1750
|
-
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
1751
|
-
"title": "$.properties.title",
|
|
1752
|
-
"topicCategory": "$.properties.topicCategory",
|
|
1753
|
-
"keyword": "$.properties.keyword",
|
|
1754
|
-
"abstract": "$.properties.abstract",
|
|
1755
|
-
"resolution": "$.properties.resolution",
|
|
1756
|
-
"organisationName": "$.properties.organisationName",
|
|
1757
|
-
"organisationRole": "$.properties.organisationRole",
|
|
1758
|
-
"publicationDate": "$.properties.publicationDate",
|
|
1759
|
-
"lineage": "$.properties.lineage",
|
|
1760
|
-
"useLimitation": "$.properties.useLimitation",
|
|
1761
|
-
"accessConstraint": "$.properties.accessConstraint",
|
|
1762
|
-
"otherConstraint": "$.properties.otherConstraint",
|
|
1763
|
-
"classification": "$.properties.classification",
|
|
1764
|
-
"language": "$.properties.language",
|
|
1765
|
-
"specification": "$.properties.specification",
|
|
1766
|
-
# OpenSearch Parameters for Product Search (Table 5)
|
|
1767
|
-
"parentIdentifier": "$.properties.parentIdentifier",
|
|
1768
|
-
"productionStatus": "$.properties.productionStatus",
|
|
1769
|
-
"acquisitionType": "$.properties.acquisitionType",
|
|
1770
|
-
"orbitNumber": "$.properties.orbitNumber",
|
|
1771
|
-
"orbitDirection": "$.properties.orbitDirection",
|
|
1772
|
-
"track": "$.properties.track",
|
|
1773
|
-
"frame": "$.properties.frame",
|
|
1774
|
-
"swathIdentifier": "$.properties.swathIdentifier",
|
|
1775
|
-
"cloudCover": "$.properties.cloudCover",
|
|
1776
|
-
"snowCover": "$.properties.snowCover",
|
|
1777
|
-
"lowestLocation": "$.properties.lowestLocation",
|
|
1778
|
-
"highestLocation": "$.properties.highestLocation",
|
|
1779
|
-
"productVersion": "$.properties.productVersion",
|
|
1780
|
-
"productQualityStatus": "$.properties.productQualityStatus",
|
|
1781
|
-
"productQualityDegradationTag": "$.properties.productQualityDegradationTag",
|
|
1782
|
-
"processorName": "$.properties.processorName",
|
|
1783
|
-
"processingCenter": "$.properties.processingCenter",
|
|
1784
|
-
"creationDate": "$.properties.creationDate",
|
|
1785
|
-
"modificationDate": "$.properties.modificationDate",
|
|
1786
|
-
"processingDate": "$.properties.processingDate",
|
|
1787
|
-
"sensorMode": "$.properties.sensorMode",
|
|
1788
|
-
"archivingCenter": "$.properties.archivingCenter",
|
|
1789
|
-
"processingMode": "$.properties.processingMode",
|
|
1790
|
-
# OpenSearch Parameters for Acquistion Parameters Search (Table 6)
|
|
1791
|
-
"availabilityTime": "$.properties.availabilityTime",
|
|
1792
|
-
"acquisitionStation": "$.properties.acquisitionStation",
|
|
1793
|
-
"acquisitionSubType": "$.properties.acquisitionSubType",
|
|
1794
|
-
"startTimeFromAscendingNode": "$.properties.startTimeFromAscendingNode",
|
|
1795
|
-
"completionTimeFromAscendingNode": "$.properties.completionTimeFromAscendingNode",
|
|
1796
|
-
"illuminationAzimuthAngle": "$.properties.illuminationAzimuthAngle",
|
|
1797
|
-
"illuminationZenithAngle": "$.properties.illuminationZenithAngle",
|
|
1798
|
-
"illuminationElevationAngle": "$.properties.illuminationElevationAngle",
|
|
1799
|
-
"polarizationMode": "$.properties.polarizationMode",
|
|
1800
|
-
"polarizationChannels": "$.properties.polarizationChannels",
|
|
1801
|
-
"antennaLookDirection": "$.properties.antennaLookDirection",
|
|
1802
|
-
"minimumIncidenceAngle": "$.properties.minimumIncidenceAngle",
|
|
1803
|
-
"maximumIncidenceAngle": "$.properties.maximumIncidenceAngle",
|
|
1804
|
-
"dopplerFrequency": "$.properties.dopplerFrequency",
|
|
1805
|
-
"incidenceAngleVariation": "$.properties.incidenceAngleVariation",
|
|
1806
|
-
}
|
|
1807
|
-
DEFAULT_METADATA_MAPPING = dict(
|
|
1808
|
-
OSEO_METADATA_MAPPING,
|
|
1809
|
-
**{
|
|
1810
|
-
# Custom parameters (not defined in the base document referenced above)
|
|
1811
|
-
# id differs from uid. The id is an identifier by which a product which is
|
|
1812
|
-
# distributed by many providers can be retrieved (a property that it has in common
|
|
1813
|
-
# in the catalogues of all the providers on which it is referenced)
|
|
1814
|
-
"id": "$.id",
|
|
1815
|
-
# The geographic extent of the product
|
|
1816
|
-
"geometry": "$.geometry",
|
|
1817
|
-
# The url of the quicklook
|
|
1818
|
-
"quicklook": "$.properties.quicklook",
|
|
1819
|
-
# The url to download the product "as is" (literal or as a template to be completed
|
|
1820
|
-
# either after the search result is obtained from the provider or during the eodag
|
|
1821
|
-
# download phase)
|
|
1822
|
-
"downloadLink": "$.properties.downloadLink",
|
|
1823
|
-
},
|
|
1824
|
-
)
|