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
|
@@ -19,11 +19,13 @@ search:
|
|
|
19
19
|
type: StacSearch
|
|
20
20
|
results_entry: features
|
|
21
21
|
pagination:
|
|
22
|
-
next_page_query_obj: '{{"limit":{items_per_page},"
|
|
22
|
+
next_page_query_obj: '{{"limit":{items_per_page},"{next_page_token_key}":"{next_page_token}"}}'
|
|
23
23
|
total_items_nb_key_path: '$.numberMatched'
|
|
24
24
|
next_page_url_key_path: '$.links[?(@.rel="next")].href'
|
|
25
25
|
next_page_query_obj_key_path: '$.links[?(@.rel="next")].body'
|
|
26
26
|
next_page_merge_key_path: '$.links[?(@.rel="next")].merge'
|
|
27
|
+
# do not use 'page' as default key, guess from provider response
|
|
28
|
+
next_page_token_key: null
|
|
27
29
|
sort:
|
|
28
30
|
sort_by_tpl: '{{"sortby": [ {{"field": "{sort_param}", "direction": "{sort_order}" }} ] }}'
|
|
29
31
|
sort_order_mapping:
|
|
@@ -37,27 +39,27 @@ search:
|
|
|
37
39
|
- filter
|
|
38
40
|
- query
|
|
39
41
|
metadata_path: '$.properties.*'
|
|
40
|
-
|
|
42
|
+
discover_collections:
|
|
41
43
|
fetch_url: '{api_endpoint}/../collections'
|
|
42
44
|
result_type: json
|
|
43
45
|
results_entry: '$.collections[*]'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
keywords: '
|
|
46
|
+
generic_collection_id: '$.id'
|
|
47
|
+
generic_collection_parsable_properties:
|
|
48
|
+
_collection: '$.id'
|
|
49
|
+
generic_collection_parsable_metadata:
|
|
50
|
+
description: '$.description'
|
|
51
|
+
instruments: '$.summaries.instruments'
|
|
52
|
+
constellation: '{$.summaries.constellation#csv_list}'
|
|
53
|
+
platform: '{$.summaries.platform#csv_list}'
|
|
54
|
+
processing:level: '{$.summaries."processing:level"#csv_list}'
|
|
55
|
+
keywords: '$.keywords'
|
|
54
56
|
license: '$.license'
|
|
55
57
|
title: '$.title'
|
|
56
|
-
|
|
58
|
+
extent: '$.extent'
|
|
57
59
|
metadata_path: '$.properties.*'
|
|
58
60
|
discover_queryables:
|
|
59
61
|
fetch_url: '{api_endpoint}/../queryables'
|
|
60
|
-
|
|
62
|
+
collection_fetch_url: '{api_endpoint}/../collections/{provider_collection}/queryables'
|
|
61
63
|
result_type: json
|
|
62
64
|
results_entry: '$.properties[*]'
|
|
63
65
|
queryable_parsable_metadata:
|
|
@@ -67,94 +69,50 @@ search:
|
|
|
67
69
|
pattern: '$.pattern'
|
|
68
70
|
common_metadata_mapping_path: '$'
|
|
69
71
|
metadata_mapping:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- '{{"collections":["{
|
|
72
|
+
productType: '$.null'
|
|
73
|
+
_collection:
|
|
74
|
+
- '{{"collections":["{_collection}"]}}'
|
|
73
75
|
- '$.collection'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- '
|
|
80
|
-
platform:
|
|
81
|
-
- '{{"query":{{"constellation":{{"eq":"{platform}"}}}}}}'
|
|
76
|
+
# common metadata
|
|
77
|
+
gsd:
|
|
78
|
+
- '{{"query":{{"gsd":{{"eq":"{gsd}"}}}}}}'
|
|
79
|
+
- '$.properties.gsd'
|
|
80
|
+
constellation:
|
|
81
|
+
- '{{"query":{{"constellation":{{"eq":"{constellation}"}}}}}}'
|
|
82
82
|
- '$.properties.constellation'
|
|
83
|
-
|
|
84
|
-
- '{{"query":{{"platform":{{"eq":"{
|
|
83
|
+
platform:
|
|
84
|
+
- '{{"query":{{"platform":{{"eq":"{platform}"}}}}}}'
|
|
85
85
|
- '$.properties.platform'
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- '
|
|
89
|
-
- '{$.properties.instruments#csv_list}'
|
|
90
|
-
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
86
|
+
instruments:
|
|
87
|
+
- '{{"query":{{"instruments":{{"eq":{instruments}}}}}}}'
|
|
88
|
+
- '$.properties.instruments'
|
|
91
89
|
title: '$.id'
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- '
|
|
96
|
-
publicationDate:
|
|
97
|
-
- '{{"query":{{"published":{{"eq":"{publicationDate}"}}}}}}'
|
|
98
|
-
- '$.properties.published'
|
|
99
|
-
# OpenSearch Parameters for Product Search (Table 5)
|
|
100
|
-
orbitNumber:
|
|
101
|
-
- '{{"query":{{"sat:relative_orbit":{{"eq":"{orbitNumber}"}}}}}}'
|
|
102
|
-
- '$.properties."sat:relative_orbit"'
|
|
103
|
-
orbitDirection:
|
|
104
|
-
- '{{"query":{{"sat:orbit_state":{{"eq":"{orbitDirection}"}}}}}}'
|
|
105
|
-
- '{$.properties."sat:orbit_state"#to_lower}'
|
|
106
|
-
cloudCover:
|
|
107
|
-
- '{{"query":{{"eo:cloud_cover":{{"lte":"{cloudCover}"}}}}}}'
|
|
108
|
-
- '$.properties."eo:cloud_cover"'
|
|
109
|
-
sensorMode:
|
|
110
|
-
- '{{"query":{{"sar:instrument_mode":{{"eq":"{sensorMode}"}}}}}}'
|
|
111
|
-
- '$.properties."sar:instrument_mode"'
|
|
112
|
-
creationDate:
|
|
113
|
-
- '{{"query":{{"created":{{"eq":"{creationDate}"}}}}}}'
|
|
114
|
-
- '$.properties.created'
|
|
115
|
-
modificationDate:
|
|
116
|
-
- '{{"query":{{"updated":{{"eq":"{modificationDate}"}}}}}}'
|
|
117
|
-
- '$.properties.updated'
|
|
118
|
-
productVersion:
|
|
119
|
-
- '{{"query":{{"version":{{"eq":"{productVersion}"}}}}}}'
|
|
120
|
-
- '$.properties.version'
|
|
121
|
-
# OpenSearch Parameters for Acquistion Parameters Search (Table 6)
|
|
122
|
-
availabilityTime:
|
|
123
|
-
- '{{"query":{{"availabilityTime":{{"eq":"{availabilityTime}"}}}}}}'
|
|
124
|
-
- '$.properties.availabilityTime'
|
|
125
|
-
acquisitionStation:
|
|
126
|
-
- '{{"query":{{"acquisitionStation":{{"eq":"{acquisitionStation}"}}}}}}'
|
|
127
|
-
- '$.properties.acquisitionStation'
|
|
128
|
-
acquisitionSubType:
|
|
129
|
-
- '{{"query":{{"acquisitionSubType":{{"eq":"{acquisitionSubType}"}}}}}}'
|
|
130
|
-
- '$.properties.acquisitionSubType'
|
|
131
|
-
startTimeFromAscendingNode: '$.properties.datetime'
|
|
132
|
-
completionTimeFromAscendingNode:
|
|
133
|
-
- '{{"datetime":"{startTimeFromAscendingNode#to_iso_utc_datetime}/{completionTimeFromAscendingNode#to_iso_utc_datetime}"}}'
|
|
90
|
+
datetime: '$.properties.datetime'
|
|
91
|
+
start_datetime: '$.properties.start_datetime'
|
|
92
|
+
end_datetime:
|
|
93
|
+
- '{{"datetime":"{start_datetime#to_iso_utc_datetime}/{end_datetime#to_iso_utc_datetime}"}}'
|
|
134
94
|
- '$.properties.end_datetime'
|
|
135
|
-
illuminationAzimuthAngle:
|
|
136
|
-
- '{{"query":{{"view:sun_azimuth":{{"eq":"{illuminationAzimuthAngle}"}}}}}}'
|
|
137
|
-
- '$.properties."view:sun_azimuth"'
|
|
138
|
-
illuminationElevationAngle:
|
|
139
|
-
- '{{"query":{{"view:sun_elevation":{{"eq":"{illuminationElevationAngle}"}}}}}}'
|
|
140
|
-
- '$.properties."view:sun_elevation"'
|
|
141
|
-
polarizationChannels:
|
|
142
|
-
- '{{"query":{{"sar:polarizations":{{"eq":"{polarizationChannels}"}}}}}}'
|
|
143
|
-
- '{$.properties.sar:polarizations#csv_list(+)}'
|
|
144
|
-
dopplerFrequency:
|
|
145
|
-
- '{{"query":{{"sar:frequency_band":{{"eq":"{dopplerFrequency}"}}}}}}'
|
|
146
|
-
- '$.properties."sar:frequency_band"'
|
|
147
|
-
# Custom parameters (not defined in the base document referenced above)
|
|
148
95
|
id:
|
|
149
96
|
- '{{"ids":["{id}"]}}'
|
|
150
97
|
- '$.id'
|
|
151
98
|
geometry:
|
|
152
99
|
- '{{"intersects":{geometry#to_geojson}}}'
|
|
153
100
|
- '($.geometry.`str()`.`sub(/^None$/, POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90)))`)|($.geometry[*])'
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
101
|
+
# extensions parameters having specific mapping
|
|
102
|
+
sar:polarizations:
|
|
103
|
+
- '{{"query":{{"sar:polarizations":{{"eq":{sar:polarizations}}}}}}}'
|
|
104
|
+
- '$.properties.sar:polarizations'
|
|
105
|
+
sar:beam_ids:
|
|
106
|
+
- '{{"query":{{"sar:beam_ids":{{"eq":{sar:beam_ids}}}}}}}'
|
|
107
|
+
- '$.properties.sar:beam_ids'
|
|
108
|
+
eo:cloud_cover:
|
|
109
|
+
- '{{"query":{{"eo:cloud_cover":{{"lte":{eo:cloud_cover}}}}}}}'
|
|
110
|
+
- '$.properties."eo:cloud_cover"'
|
|
111
|
+
# eodag metadata
|
|
112
|
+
eodag:download_link: '$.links[?(@.rel="self")].href'
|
|
113
|
+
eodag:quicklook: '$.assets.quicklook.href'
|
|
114
|
+
eodag:thumbnail: '$.assets.thumbnail.href'
|
|
115
|
+
# order:status set to succeeded for consistency between providers
|
|
116
|
+
order:status: '{$.null#replace_str("Not Available","succeeded")}'
|
|
159
117
|
# Normalization code moves assets from properties to product's attr
|
|
160
118
|
assets: '$.assets'
|
|
@@ -107,6 +107,15 @@ dedt_lumi:
|
|
|
107
107
|
credentials:
|
|
108
108
|
username:
|
|
109
109
|
password:
|
|
110
|
+
dedt_mn5:
|
|
111
|
+
priority: # Lower value means lower priority (Default: 0)
|
|
112
|
+
search:
|
|
113
|
+
download:
|
|
114
|
+
output_dir:
|
|
115
|
+
auth:
|
|
116
|
+
credentials:
|
|
117
|
+
username:
|
|
118
|
+
password:
|
|
110
119
|
earth_search:
|
|
111
120
|
priority: # Lower value means lower priority (Default: 0)
|
|
112
121
|
search: # Search parameters configuration
|
eodag/types/__init__.py
CHANGED
|
@@ -153,6 +153,7 @@ def json_field_definition_to_python(
|
|
|
153
153
|
json_field_definition: dict[str, Any],
|
|
154
154
|
default_value: Optional[Any] = None,
|
|
155
155
|
required: Optional[bool] = False,
|
|
156
|
+
alias: Optional[str] = None,
|
|
156
157
|
) -> Annotated[Any, FieldInfo]:
|
|
157
158
|
"""Get python field definition from json object
|
|
158
159
|
|
|
@@ -180,6 +181,7 @@ def json_field_definition_to_python(
|
|
|
180
181
|
pattern=json_field_definition.get("pattern", PydanticUndefined),
|
|
181
182
|
le=json_field_definition.get("maximum", PydanticUndefined),
|
|
182
183
|
ge=json_field_definition.get("minimum", PydanticUndefined),
|
|
184
|
+
alias=alias or PydanticUndefined,
|
|
183
185
|
)
|
|
184
186
|
|
|
185
187
|
enum = json_field_definition.get("enum")
|
eodag/types/queryables.py
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2024, CS GROUP - France, https://www.csgroup.eu/
|
|
3
|
-
#
|
|
4
|
-
# This file is part of EODAG project
|
|
5
|
-
# https://www.github.com/CS-SI/EODAG
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
1
|
from __future__ import annotations
|
|
19
2
|
|
|
20
3
|
from collections import UserDict
|
|
@@ -36,14 +19,14 @@ Percentage = Annotated[PositiveInt, Lt(100)]
|
|
|
36
19
|
class CommonQueryables(BaseModel):
|
|
37
20
|
"""A class representing search common queryable properties."""
|
|
38
21
|
|
|
39
|
-
|
|
22
|
+
collection: Annotated[str, Field()]
|
|
40
23
|
|
|
41
24
|
@classmethod
|
|
42
25
|
def get_queryable_from_alias(cls, value: str) -> str:
|
|
43
26
|
"""Get queryable parameter from alias
|
|
44
27
|
|
|
45
|
-
>>> CommonQueryables.get_queryable_from_alias('
|
|
46
|
-
'
|
|
28
|
+
>>> CommonQueryables.get_queryable_from_alias('collection')
|
|
29
|
+
'collection'
|
|
47
30
|
"""
|
|
48
31
|
alias_map = {
|
|
49
32
|
field_info.alias: name
|
|
@@ -75,7 +58,7 @@ class Queryables(CommonQueryables):
|
|
|
75
58
|
str,
|
|
76
59
|
Field(
|
|
77
60
|
None,
|
|
78
|
-
alias="
|
|
61
|
+
alias="start_datetime",
|
|
79
62
|
description="Date/time as string in ISO 8601 format (e.g. '2024-06-10T12:00:00Z')",
|
|
80
63
|
),
|
|
81
64
|
]
|
|
@@ -83,7 +66,7 @@ class Queryables(CommonQueryables):
|
|
|
83
66
|
str,
|
|
84
67
|
Field(
|
|
85
68
|
None,
|
|
86
|
-
alias="
|
|
69
|
+
alias="end_datetime",
|
|
87
70
|
description="Date/time as string in ISO 8601 format (e.g. '2024-06-10T12:00:00Z')",
|
|
88
71
|
),
|
|
89
72
|
]
|
|
@@ -95,77 +78,57 @@ class Queryables(CommonQueryables):
|
|
|
95
78
|
description="Read EODAG documentation for all supported geometry format.",
|
|
96
79
|
),
|
|
97
80
|
]
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
81
|
+
# common metadata
|
|
82
|
+
constellation: Annotated[str, Field(None)]
|
|
83
|
+
created: Annotated[str, Field(None)]
|
|
84
|
+
description: Annotated[str, Field(None)]
|
|
85
|
+
gsd: Annotated[int, Field(None)]
|
|
86
|
+
id: Annotated[str, Field(None)]
|
|
87
|
+
instruments: Annotated[str, Field(None)]
|
|
88
|
+
keywords: Annotated[str, Field(None)]
|
|
89
|
+
license: Annotated[str, Field(None)]
|
|
101
90
|
platform: Annotated[str, Field(None)]
|
|
102
|
-
|
|
103
|
-
instrument: Annotated[str, Field(None)]
|
|
104
|
-
sensorType: Annotated[str, Field(None)]
|
|
105
|
-
compositeType: Annotated[str, Field(None)]
|
|
106
|
-
processingLevel: Annotated[str, Field(None)]
|
|
107
|
-
orbitType: Annotated[str, Field(None)]
|
|
108
|
-
spectralRange: Annotated[str, Field(None)]
|
|
109
|
-
wavelengths: Annotated[str, Field(None)]
|
|
110
|
-
hasSecurityConstraints: Annotated[str, Field(None)]
|
|
111
|
-
dissemination: Annotated[str, Field(None)]
|
|
112
|
-
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
91
|
+
providers: Annotated[str, Field(None)]
|
|
113
92
|
title: Annotated[str, Field(None)]
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
availabilityTime: Annotated[str, Field(None)]
|
|
154
|
-
acquisitionStation: Annotated[str, Field(None)]
|
|
155
|
-
acquisitionSubType: Annotated[str, Field(None)]
|
|
156
|
-
illuminationAzimuthAngle: Annotated[str, Field(None)]
|
|
157
|
-
illuminationZenithAngle: Annotated[str, Field(None)]
|
|
158
|
-
illuminationElevationAngle: Annotated[str, Field(None)]
|
|
159
|
-
polarizationMode: Annotated[str, Field(None)]
|
|
160
|
-
polarizationChannels: Annotated[str, Field(None)]
|
|
161
|
-
antennaLookDirection: Annotated[str, Field(None)]
|
|
162
|
-
minimumIncidenceAngle: Annotated[float, Field(None)]
|
|
163
|
-
maximumIncidenceAngle: Annotated[float, Field(None)]
|
|
164
|
-
dopplerFrequency: Annotated[float, Field(None)]
|
|
165
|
-
incidenceAngleVariation: Annotated[float, Field(None)]
|
|
166
|
-
# Custom parameters (not defined in the base document referenced above)
|
|
167
|
-
id: Annotated[str, Field(None)]
|
|
168
|
-
tileIdentifier: Annotated[str, Field(None, pattern=r"[0-9]{2}[A-Z]{3}")]
|
|
93
|
+
uid: Annotated[str, Field(None)]
|
|
94
|
+
updated: Annotated[str, Field(None)]
|
|
95
|
+
# eo extension
|
|
96
|
+
eo_cloud_cover: Annotated[Percentage, Field(None, alias="eo:cloud_cover")]
|
|
97
|
+
eo_snow_cover: Annotated[Percentage, Field(None, alias="eo:snow_cover")]
|
|
98
|
+
# grid extension
|
|
99
|
+
grid_code: Annotated[
|
|
100
|
+
str, Field(None, alias="grid:code", pattern=r"^[A-Z0-9]+-[-_.A-Za-z0-9]+$")
|
|
101
|
+
]
|
|
102
|
+
# mgrs extension
|
|
103
|
+
mgrs_grid_square: Annotated[str, Field(None, alias="mgrs:grid_square")]
|
|
104
|
+
mgrs_latitude_band: Annotated[str, Field(None, alias="mgrs:latitude_band")]
|
|
105
|
+
mgrs_utm_zone: Annotated[str, Field(None, alias="mgrs:utm_zone")]
|
|
106
|
+
# order extension
|
|
107
|
+
order_status: Annotated[str, Field(None, alias="order:status")]
|
|
108
|
+
# processing extension
|
|
109
|
+
processing_level: Annotated[str, Field(None, alias="processing:level")]
|
|
110
|
+
# product extension
|
|
111
|
+
product_acquisition_type: Annotated[
|
|
112
|
+
str, Field(None, alias="product:acquisition_type")
|
|
113
|
+
]
|
|
114
|
+
product_timeliness: Annotated[str, Field(None, alias="product:timeliness")]
|
|
115
|
+
product_type: Annotated[str, Field(None, alias="product:type")]
|
|
116
|
+
# sar extension
|
|
117
|
+
sar_beam_ids: Annotated[str, Field(None, alias="sar:beam_ids")]
|
|
118
|
+
sar_frequency_band: Annotated[float, Field(None, alias="sar:frequency_band")]
|
|
119
|
+
sar_instrument_mode: Annotated[str, Field(None, alias="sar:instrument_mode")]
|
|
120
|
+
sar_polarizations: Annotated[list[str], Field(None, alias="sar:polarizations")]
|
|
121
|
+
# sat extension
|
|
122
|
+
sat_absolute_orbit: Annotated[int, Field(None, alias="sat:absolute_orbit")]
|
|
123
|
+
sat_orbit_cycle: Annotated[int, Field(None, alias="sat:orbit_cycle")]
|
|
124
|
+
sat_orbit_state: Annotated[str, Field(None, alias="sat:orbit_state")]
|
|
125
|
+
sat_relative_orbit: Annotated[int, Field(None, alias="sat:relative_orbit")]
|
|
126
|
+
# sci extension
|
|
127
|
+
sci_doi: Annotated[str, Field(None, alias="sci:doi")]
|
|
128
|
+
# view extension
|
|
129
|
+
view_incidence_angle: Annotated[str, Field(None, alias="view:incidence_angle")]
|
|
130
|
+
view_sun_azimuth: Annotated[str, Field(None, alias="view:sun_azimuth")]
|
|
131
|
+
view_sun_elevation: Annotated[str, Field(None, alias="view:sun_elevation")]
|
|
169
132
|
|
|
170
133
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
171
134
|
|
|
@@ -190,6 +153,22 @@ class QueryablesDict(UserDict[str, Any]):
|
|
|
190
153
|
self.additional_properties = additional_properties
|
|
191
154
|
self.additional_information = additional_information
|
|
192
155
|
super().__init__(kwargs)
|
|
156
|
+
# sort queryables: first without then with extension prefix
|
|
157
|
+
no_prefix_queryables = {
|
|
158
|
+
key: self.data[key]
|
|
159
|
+
for key in sorted(self.data)
|
|
160
|
+
if ":"
|
|
161
|
+
not in str(
|
|
162
|
+
getattr(self.data[key], "__metadata__", [Field()])[0].alias or key
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
with_prefix_queryables = {
|
|
166
|
+
key: self.data[key]
|
|
167
|
+
for key in sorted(self.data)
|
|
168
|
+
if ":"
|
|
169
|
+
in str(getattr(self.data[key], "__metadata__", [Field()])[0].alias or key)
|
|
170
|
+
}
|
|
171
|
+
self.data = no_prefix_queryables | with_prefix_queryables
|
|
193
172
|
|
|
194
173
|
def _repr_html_(self, embedded: bool = False) -> str:
|
|
195
174
|
add_info = (
|
eodag/types/search_args.py
CHANGED
|
@@ -43,7 +43,7 @@ class SearchArgs(BaseModel):
|
|
|
43
43
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
44
44
|
|
|
45
45
|
provider: Optional[str] = Field(None)
|
|
46
|
-
|
|
46
|
+
collection: str = Field()
|
|
47
47
|
id: Optional[str] = Field(None)
|
|
48
48
|
start: Optional[str] = Field(None)
|
|
49
49
|
end: Optional[str] = Field(None)
|
eodag/utils/__init__.py
CHANGED
|
@@ -63,6 +63,8 @@ from typing import (
|
|
|
63
63
|
from urllib.parse import urlparse, urlsplit
|
|
64
64
|
from urllib.request import url2pathname
|
|
65
65
|
|
|
66
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
67
|
+
|
|
66
68
|
if sys.version_info >= (3, 12):
|
|
67
69
|
from typing import Unpack # type: ignore # noqa
|
|
68
70
|
else:
|
|
@@ -94,7 +96,7 @@ logger = py_logging.getLogger("eodag.utils")
|
|
|
94
96
|
|
|
95
97
|
DEFAULT_PROJ = "EPSG:4326"
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
GENERIC_COLLECTION = "GENERIC_COLLECTION"
|
|
98
100
|
GENERIC_STAC_PROVIDER = "generic_stac_provider"
|
|
99
101
|
|
|
100
102
|
STAC_SEARCH_PLUGINS = [
|
|
@@ -129,7 +131,7 @@ DEFAULT_ITEMS_PER_PAGE = 20
|
|
|
129
131
|
# (DEFAULT_ITEMS_PER_PAGE) to increase it to the known and current minimum value (mundi)
|
|
130
132
|
DEFAULT_MAX_ITEMS_PER_PAGE = 50
|
|
131
133
|
|
|
132
|
-
# default
|
|
134
|
+
# default collections start date
|
|
133
135
|
DEFAULT_MISSION_START_DATE = "2015-01-01T00:00:00.000Z"
|
|
134
136
|
|
|
135
137
|
# default geometry / whole world bounding box
|
|
@@ -138,6 +140,9 @@ DEFAULT_SHAPELY_GEOMETRY = box(-180, -90, 180, 90)
|
|
|
138
140
|
# default token expiration margin in seconds
|
|
139
141
|
DEFAULT_TOKEN_EXPIRATION_MARGIN = 60
|
|
140
142
|
|
|
143
|
+
# knwown next page token keys used to guess key in STAC providers next link responses
|
|
144
|
+
KNOWN_NEXT_PAGE_TOKEN_KEYS = ["token", "next", "page", "skip"]
|
|
145
|
+
|
|
141
146
|
# update missing mimetypes
|
|
142
147
|
mimetypes.add_type("text/xml", ".xsd")
|
|
143
148
|
mimetypes.add_type("application/x-grib", ".grib")
|
|
@@ -340,11 +345,11 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
|
|
|
340
345
|
|
|
341
346
|
Do its best to detect the key in ``mapping1`` to override. For example:
|
|
342
347
|
|
|
343
|
-
>>> mapping2 = {"
|
|
344
|
-
>>> mapping1 = {"keyA": "obsolete"}
|
|
348
|
+
>>> mapping2 = {"ext_keya": "new"}
|
|
349
|
+
>>> mapping1 = {"ext:keyA": "obsolete"}
|
|
345
350
|
>>> merge_mappings(mapping1, mapping2)
|
|
346
351
|
>>> mapping1
|
|
347
|
-
{'keyA': 'new'}
|
|
352
|
+
{'ext:keyA': 'new'}
|
|
348
353
|
|
|
349
354
|
If ``mapping2`` has a key that cannot be detected in ``mapping1``, this new key is
|
|
350
355
|
added to ``mapping1`` as is.
|
|
@@ -353,7 +358,7 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
|
|
|
353
358
|
:param mapping2: The mapping containing values that will override the first mapping
|
|
354
359
|
"""
|
|
355
360
|
# A mapping between mapping1 keys as lowercase strings and original mapping1 keys
|
|
356
|
-
m1_keys_lowercase = {key.lower(): key for key in mapping1}
|
|
361
|
+
m1_keys_lowercase = {key.lower().replace(":", "_"): key for key in mapping1}
|
|
357
362
|
for key, value in mapping2.items():
|
|
358
363
|
if isinstance(value, dict):
|
|
359
364
|
try:
|
|
@@ -478,20 +483,6 @@ class ProgressCallback(tqdm):
|
|
|
478
483
|
return ProgressCallback(*args, **dict(self.kwargs, **kwargs))
|
|
479
484
|
|
|
480
485
|
|
|
481
|
-
@_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
|
|
482
|
-
class NotebookProgressCallback(tqdm):
|
|
483
|
-
"""A custom progress bar to be used inside Jupyter notebooks"""
|
|
484
|
-
|
|
485
|
-
pass
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
@_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
|
|
489
|
-
def get_progress_callback() -> tqdm:
|
|
490
|
-
"""Get progress_callback"""
|
|
491
|
-
|
|
492
|
-
return tqdm()
|
|
493
|
-
|
|
494
|
-
|
|
495
486
|
def repeatfunc(func: Callable[..., Any], n: int, *args: Any) -> starmap:
|
|
496
487
|
"""Call ``func`` ``n`` times with ``args``"""
|
|
497
488
|
return starmap(func, repeat(args, n))
|
|
@@ -975,7 +966,9 @@ def string_to_jsonpath(*args: Any, force: bool = False) -> Union[str, JSONPath]:
|
|
|
975
966
|
return path_str
|
|
976
967
|
|
|
977
968
|
|
|
978
|
-
def format_string(
|
|
969
|
+
def format_string(
|
|
970
|
+
key: Optional[str], str_to_format: Any, **format_variables: Any
|
|
971
|
+
) -> Any:
|
|
979
972
|
"""Format ``"{foo}"``-like string
|
|
980
973
|
|
|
981
974
|
>>> format_string(None, "foo {bar}, {baz} ?", **{"bar": "qux", "baz": "quux"})
|
|
@@ -999,6 +992,27 @@ def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
|
|
|
999
992
|
# defaultdict usage will return "" for missing keys in format_args
|
|
1000
993
|
try:
|
|
1001
994
|
result = str_to_format.format_map(defaultdict(str, **format_variables))
|
|
995
|
+
except (ValueError, TypeError) as e:
|
|
996
|
+
if not re.search(r"{[\w-]*:[\w-]*}", str_to_format):
|
|
997
|
+
raise MisconfiguredError(
|
|
998
|
+
f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
|
|
999
|
+
)
|
|
1000
|
+
# retry parsing colons
|
|
1001
|
+
try:
|
|
1002
|
+
str_without_colons = re.sub(
|
|
1003
|
+
r"{([\w-]*):([\w-]*)}",
|
|
1004
|
+
r"{\1_COLON_\2}",
|
|
1005
|
+
str_to_format,
|
|
1006
|
+
)
|
|
1007
|
+
result = str_without_colons.format_map(
|
|
1008
|
+
defaultdict(
|
|
1009
|
+
str,
|
|
1010
|
+
**{
|
|
1011
|
+
k.replace(":", "_COLON_"): v
|
|
1012
|
+
for k, v in format_variables.items()
|
|
1013
|
+
},
|
|
1014
|
+
)
|
|
1015
|
+
)
|
|
1002
1016
|
except (ValueError, TypeError) as e:
|
|
1003
1017
|
raise MisconfiguredError(
|
|
1004
1018
|
f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
|
|
@@ -1643,3 +1657,65 @@ def parse_le_uint16(data: bytes) -> int:
|
|
|
1643
1657
|
65535
|
|
1644
1658
|
"""
|
|
1645
1659
|
return struct.unpack("<H", data)[0]
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
def format_pydantic_error(e: PydanticValidationError) -> str:
|
|
1663
|
+
"""Format Pydantic ValidationError
|
|
1664
|
+
|
|
1665
|
+
:param e: A Pydantic ValidationError object
|
|
1666
|
+
:type e: PydanticValidationError
|
|
1667
|
+
"""
|
|
1668
|
+
error_header = f"{e.error_count()} error(s). "
|
|
1669
|
+
|
|
1670
|
+
error_messages = [
|
|
1671
|
+
f'{err["loc"][0]}: {err["msg"]}' if err["loc"] else err["msg"]
|
|
1672
|
+
for err in e.errors()
|
|
1673
|
+
]
|
|
1674
|
+
return error_header + "; ".join(set(error_messages))
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
def get_collection_dates(
|
|
1678
|
+
collection_dict: dict[str, Any]
|
|
1679
|
+
) -> tuple[Optional[str], Optional[str]]:
|
|
1680
|
+
"""Extract mission start and end dates from collection configuration.
|
|
1681
|
+
|
|
1682
|
+
Extracts dates from the extent.temporal.interval structure.
|
|
1683
|
+
|
|
1684
|
+
:param collection_dict: Collection configuration dictionary
|
|
1685
|
+
:returns: Tuple of (mission_start_date, mission_end_date) as ISO strings or None
|
|
1686
|
+
|
|
1687
|
+
Example:
|
|
1688
|
+
>>> get_collection_dates({
|
|
1689
|
+
... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", "2023-12-31T23:59:59Z"]]}}
|
|
1690
|
+
... })
|
|
1691
|
+
('2017-10-13T00:00:00Z', '2023-12-31T23:59:59Z')
|
|
1692
|
+
|
|
1693
|
+
>>> get_collection_dates({
|
|
1694
|
+
... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", None]]}}
|
|
1695
|
+
... })
|
|
1696
|
+
('2017-10-13T00:00:00Z', None)
|
|
1697
|
+
|
|
1698
|
+
>>> get_collection_dates({})
|
|
1699
|
+
(None, None)
|
|
1700
|
+
"""
|
|
1701
|
+
extent_interval = (
|
|
1702
|
+
collection_dict.get("extent", {})
|
|
1703
|
+
.get("temporal", {})
|
|
1704
|
+
.get("interval", [[None, None]])
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
if not extent_interval or len(extent_interval) == 0:
|
|
1708
|
+
return None, None
|
|
1709
|
+
|
|
1710
|
+
mission_start = (
|
|
1711
|
+
extent_interval[0][0]
|
|
1712
|
+
if len(extent_interval) > 0 and len(extent_interval[0]) > 0
|
|
1713
|
+
else None
|
|
1714
|
+
)
|
|
1715
|
+
mission_end = (
|
|
1716
|
+
extent_interval[0][1]
|
|
1717
|
+
if len(extent_interval) > 0 and len(extent_interval[0]) > 1
|
|
1718
|
+
else None
|
|
1719
|
+
)
|
|
1720
|
+
|
|
1721
|
+
return mission_start, mission_end
|
eodag/utils/dates.py
CHANGED
|
@@ -35,18 +35,6 @@ RFC3339_PATTERN = (
|
|
|
35
35
|
r"(Z|([+-])(\d{2}):(\d{2}))?)?$"
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
# yyyy-mm-dd
|
|
39
|
-
DATE_PATTERN = r"\d{4}-(0[1-9]|1[1,2])-(0[1-9]|[12][0-9]|3[01])"
|
|
40
|
-
|
|
41
|
-
# yyyymmdd
|
|
42
|
-
COMPACT_DATE_PATTERN = r"\d{4}(0[1-9]|1[1,2])(0[1-9]|[12][0-9]|3[01])"
|
|
43
|
-
|
|
44
|
-
# yyyy-mm-dd/yyyy-mm-dd, yyyy-mm-dd/to/yyyy-mm-dd
|
|
45
|
-
DATE_RANGE_PATTERN = DATE_PATTERN + r"(/to/|/)" + DATE_PATTERN
|
|
46
|
-
|
|
47
|
-
# yyyymmdd/yyyymmdd, yyyymmdd/to/yyyymmdd
|
|
48
|
-
COMPACT_DATE_RANGE_PATTERN = COMPACT_DATE_PATTERN + r"(/to/|/)" + COMPACT_DATE_PATTERN
|
|
49
|
-
|
|
50
38
|
|
|
51
39
|
def get_timestamp(date_time: str) -> float:
|
|
52
40
|
"""Return the Unix timestamp of an ISO8601 date/datetime in seconds.
|