eodag 3.9.1__py3-none-any.whl → 4.0.0a1__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/core.py +378 -419
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +68 -40
- eodag/api/product/drivers/__init__.py +3 -5
- eodag/api/product/drivers/base.py +1 -18
- eodag/api/product/metadata_mapping.py +151 -215
- eodag/api/search_result.py +13 -7
- eodag/cli.py +72 -395
- eodag/config.py +46 -50
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +20 -21
- eodag/plugins/apis/usgs.py +37 -33
- eodag/plugins/authentication/aws_auth.py +36 -1
- eodag/plugins/authentication/base.py +18 -3
- eodag/plugins/authentication/sas_auth.py +15 -0
- 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 +45 -41
- 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 +3 -4
- eodag/plugins/search/base.py +128 -77
- eodag/plugins/search/build_search_result.py +105 -107
- eodag/plugins/search/cop_marine.py +44 -47
- eodag/plugins/search/csw.py +33 -33
- eodag/plugins/search/qssearch.py +335 -354
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +31 -31
- eodag/resources/{product_types.yml → collections.yml} +2353 -2429
- eodag/resources/ext_collections.json +1 -0
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2432 -2714
- eodag/resources/stac_provider.yml +46 -90
- eodag/types/queryables.py +55 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +94 -21
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/METADATA +11 -88
- eodag-4.0.0a1.dist-info/RECORD +92 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -64
- 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.9.1.dist-info/RECORD +0 -115
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -37,27 +37,27 @@ search:
|
|
|
37
37
|
- filter
|
|
38
38
|
- query
|
|
39
39
|
metadata_path: '$.properties.*'
|
|
40
|
-
|
|
40
|
+
discover_collections:
|
|
41
41
|
fetch_url: '{api_endpoint}/../collections'
|
|
42
42
|
result_type: json
|
|
43
43
|
results_entry: '$.collections[*]'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
generic_collection_id: '$.id'
|
|
45
|
+
generic_collection_parsable_properties:
|
|
46
|
+
_collection: '$.id'
|
|
47
|
+
generic_collection_parsable_metadata:
|
|
48
|
+
description: '$.description'
|
|
49
|
+
instruments: '$.summaries.instruments'
|
|
50
|
+
constellation: '{$.summaries.constellation#csv_list}'
|
|
51
|
+
platform: '{$.summaries.platform#csv_list}'
|
|
52
|
+
processing:level: '{$.summaries."processing:level"#csv_list}'
|
|
53
53
|
keywords: '{$.keywords#csv_list}'
|
|
54
54
|
license: '$.license'
|
|
55
55
|
title: '$.title'
|
|
56
|
-
|
|
56
|
+
extent: '$.extent'
|
|
57
57
|
metadata_path: '$.properties.*'
|
|
58
58
|
discover_queryables:
|
|
59
59
|
fetch_url: '{api_endpoint}/../queryables'
|
|
60
|
-
|
|
60
|
+
collection_fetch_url: '{api_endpoint}/../collections/{provider_collection}/queryables'
|
|
61
61
|
result_type: json
|
|
62
62
|
results_entry: '$.properties[*]'
|
|
63
63
|
queryable_parsable_metadata:
|
|
@@ -67,94 +67,50 @@ search:
|
|
|
67
67
|
pattern: '$.pattern'
|
|
68
68
|
common_metadata_mapping_path: '$'
|
|
69
69
|
metadata_mapping:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- '{{"collections":["{
|
|
70
|
+
productType: '$.null'
|
|
71
|
+
_collection:
|
|
72
|
+
- '{{"collections":["{_collection}"]}}'
|
|
73
73
|
- '$.collection'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- '
|
|
80
|
-
platform:
|
|
81
|
-
- '{{"query":{{"constellation":{{"eq":"{platform}"}}}}}}'
|
|
74
|
+
# common metadata
|
|
75
|
+
gsd:
|
|
76
|
+
- '{{"query":{{"gsd":{{"eq":"{gsd}"}}}}}}'
|
|
77
|
+
- '$.properties.gsd'
|
|
78
|
+
constellation:
|
|
79
|
+
- '{{"query":{{"constellation":{{"eq":"{constellation}"}}}}}}'
|
|
82
80
|
- '$.properties.constellation'
|
|
83
|
-
|
|
84
|
-
- '{{"query":{{"platform":{{"eq":"{
|
|
81
|
+
platform:
|
|
82
|
+
- '{{"query":{{"platform":{{"eq":"{platform}"}}}}}}'
|
|
85
83
|
- '$.properties.platform'
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- '
|
|
89
|
-
- '{$.properties.instruments#csv_list}'
|
|
90
|
-
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
84
|
+
instruments:
|
|
85
|
+
- '{{"query":{{"instruments":{{"eq":{instruments}}}}}}}'
|
|
86
|
+
- '$.properties.instruments'
|
|
91
87
|
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}"}}'
|
|
88
|
+
datetime: '$.properties.datetime'
|
|
89
|
+
start_datetime: '$.properties.start_datetime'
|
|
90
|
+
end_datetime:
|
|
91
|
+
- '{{"datetime":"{start_datetime#to_iso_utc_datetime}/{end_datetime#to_iso_utc_datetime}"}}'
|
|
134
92
|
- '$.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
93
|
id:
|
|
149
94
|
- '{{"ids":["{id}"]}}'
|
|
150
95
|
- '$.id'
|
|
151
96
|
geometry:
|
|
152
97
|
- '{{"intersects":{geometry#to_geojson}}}'
|
|
153
98
|
- '($.geometry.`str()`.`sub(/^None$/, POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90)))`)|($.geometry[*])'
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
99
|
+
# extensions parameters having specific mapping
|
|
100
|
+
sar:polarizations:
|
|
101
|
+
- '{{"query":{{"sar:polarizations":{{"eq":{sar:polarizations}}}}}}}'
|
|
102
|
+
- '$.properties.sar:polarizations'
|
|
103
|
+
sar:beam_ids:
|
|
104
|
+
- '{{"query":{{"sar:beam_ids":{{"eq":{sar:beam_ids}}}}}}}'
|
|
105
|
+
- '$.properties.sar:beam_ids'
|
|
106
|
+
eo:cloud_cover:
|
|
107
|
+
- '{{"query":{{"eo:cloud_cover":{{"lte":{eo:cloud_cover}}}}}}}'
|
|
108
|
+
- '$.properties."eo:cloud_cover"'
|
|
109
|
+
# eodag metadata
|
|
110
|
+
eodag:download_link: '$.links[?(@.rel="self")].href'
|
|
111
|
+
eodag:quicklook: '$.assets.quicklook.href'
|
|
112
|
+
eodag:thumbnail: '$.assets.thumbnail.href'
|
|
113
|
+
# order:status set to succeeded for consistency between providers
|
|
114
|
+
order:status: '{$.null#replace_str("Not Available","succeeded")}'
|
|
159
115
|
# Normalization code moves assets from properties to product's attr
|
|
160
116
|
assets: '$.assets'
|
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
|
]
|
|
@@ -91,80 +74,61 @@ class Queryables(CommonQueryables):
|
|
|
91
74
|
Union[str, dict[str, float], BaseGeometry],
|
|
92
75
|
Field(
|
|
93
76
|
None,
|
|
77
|
+
alias="geometry",
|
|
94
78
|
description="Read EODAG documentation for all supported geometry format.",
|
|
95
79
|
),
|
|
96
80
|
]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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)]
|
|
100
90
|
platform: Annotated[str, Field(None)]
|
|
101
|
-
|
|
102
|
-
instrument: Annotated[str, Field(None)]
|
|
103
|
-
sensorType: Annotated[str, Field(None)]
|
|
104
|
-
compositeType: Annotated[str, Field(None)]
|
|
105
|
-
processingLevel: Annotated[str, Field(None)]
|
|
106
|
-
orbitType: Annotated[str, Field(None)]
|
|
107
|
-
spectralRange: Annotated[str, Field(None)]
|
|
108
|
-
wavelengths: Annotated[str, Field(None)]
|
|
109
|
-
hasSecurityConstraints: Annotated[str, Field(None)]
|
|
110
|
-
dissemination: Annotated[str, Field(None)]
|
|
111
|
-
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
91
|
+
providers: Annotated[str, Field(None)]
|
|
112
92
|
title: Annotated[str, Field(None)]
|
|
113
|
-
|
|
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
|
-
availabilityTime: Annotated[str, Field(None)]
|
|
153
|
-
acquisitionStation: Annotated[str, Field(None)]
|
|
154
|
-
acquisitionSubType: Annotated[str, Field(None)]
|
|
155
|
-
illuminationAzimuthAngle: Annotated[str, Field(None)]
|
|
156
|
-
illuminationZenithAngle: Annotated[str, Field(None)]
|
|
157
|
-
illuminationElevationAngle: Annotated[str, Field(None)]
|
|
158
|
-
polarizationMode: Annotated[str, Field(None)]
|
|
159
|
-
polarizationChannels: Annotated[str, Field(None)]
|
|
160
|
-
antennaLookDirection: Annotated[str, Field(None)]
|
|
161
|
-
minimumIncidenceAngle: Annotated[float, Field(None)]
|
|
162
|
-
maximumIncidenceAngle: Annotated[float, Field(None)]
|
|
163
|
-
dopplerFrequency: Annotated[float, Field(None)]
|
|
164
|
-
incidenceAngleVariation: Annotated[float, Field(None)]
|
|
165
|
-
# Custom parameters (not defined in the base document referenced above)
|
|
166
|
-
id: Annotated[str, Field(None)]
|
|
167
|
-
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")]
|
|
168
132
|
|
|
169
133
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
170
134
|
|
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 token expiration margin in seconds
|
|
@@ -337,11 +339,11 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
|
|
|
337
339
|
|
|
338
340
|
Do its best to detect the key in ``mapping1`` to override. For example:
|
|
339
341
|
|
|
340
|
-
>>> mapping2 = {"
|
|
341
|
-
>>> mapping1 = {"keyA": "obsolete"}
|
|
342
|
+
>>> mapping2 = {"ext_keya": "new"}
|
|
343
|
+
>>> mapping1 = {"ext:keyA": "obsolete"}
|
|
342
344
|
>>> merge_mappings(mapping1, mapping2)
|
|
343
345
|
>>> mapping1
|
|
344
|
-
{'keyA': 'new'}
|
|
346
|
+
{'ext:keyA': 'new'}
|
|
345
347
|
|
|
346
348
|
If ``mapping2`` has a key that cannot be detected in ``mapping1``, this new key is
|
|
347
349
|
added to ``mapping1`` as is.
|
|
@@ -350,7 +352,7 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
|
|
|
350
352
|
:param mapping2: The mapping containing values that will override the first mapping
|
|
351
353
|
"""
|
|
352
354
|
# A mapping between mapping1 keys as lowercase strings and original mapping1 keys
|
|
353
|
-
m1_keys_lowercase = {key.lower(): key for key in mapping1}
|
|
355
|
+
m1_keys_lowercase = {key.lower().replace(":", "_"): key for key in mapping1}
|
|
354
356
|
for key, value in mapping2.items():
|
|
355
357
|
if isinstance(value, dict):
|
|
356
358
|
try:
|
|
@@ -475,20 +477,6 @@ class ProgressCallback(tqdm):
|
|
|
475
477
|
return ProgressCallback(*args, **dict(self.kwargs, **kwargs))
|
|
476
478
|
|
|
477
479
|
|
|
478
|
-
@_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
|
|
479
|
-
class NotebookProgressCallback(tqdm):
|
|
480
|
-
"""A custom progress bar to be used inside Jupyter notebooks"""
|
|
481
|
-
|
|
482
|
-
pass
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
@_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
|
|
486
|
-
def get_progress_callback() -> tqdm:
|
|
487
|
-
"""Get progress_callback"""
|
|
488
|
-
|
|
489
|
-
return tqdm()
|
|
490
|
-
|
|
491
|
-
|
|
492
480
|
def repeatfunc(func: Callable[..., Any], n: int, *args: Any) -> starmap:
|
|
493
481
|
"""Call ``func`` ``n`` times with ``args``"""
|
|
494
482
|
return starmap(func, repeat(args, n))
|
|
@@ -972,7 +960,9 @@ def string_to_jsonpath(*args: Any, force: bool = False) -> Union[str, JSONPath]:
|
|
|
972
960
|
return path_str
|
|
973
961
|
|
|
974
962
|
|
|
975
|
-
def format_string(
|
|
963
|
+
def format_string(
|
|
964
|
+
key: Optional[str], str_to_format: Any, **format_variables: Any
|
|
965
|
+
) -> Any:
|
|
976
966
|
"""Format ``"{foo}"``-like string
|
|
977
967
|
|
|
978
968
|
>>> format_string(None, "foo {bar}, {baz} ?", **{"bar": "qux", "baz": "quux"})
|
|
@@ -996,6 +986,27 @@ def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
|
|
|
996
986
|
# defaultdict usage will return "" for missing keys in format_args
|
|
997
987
|
try:
|
|
998
988
|
result = str_to_format.format_map(defaultdict(str, **format_variables))
|
|
989
|
+
except (ValueError, TypeError) as e:
|
|
990
|
+
if not re.search(r"{[\w-]*:[\w-]*}", str_to_format):
|
|
991
|
+
raise MisconfiguredError(
|
|
992
|
+
f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
|
|
993
|
+
)
|
|
994
|
+
# retry parsing colons
|
|
995
|
+
try:
|
|
996
|
+
str_without_colons = re.sub(
|
|
997
|
+
r"{([\w-]*):([\w-]*)}",
|
|
998
|
+
r"{\1_COLON_\2}",
|
|
999
|
+
str_to_format,
|
|
1000
|
+
)
|
|
1001
|
+
result = str_without_colons.format_map(
|
|
1002
|
+
defaultdict(
|
|
1003
|
+
str,
|
|
1004
|
+
**{
|
|
1005
|
+
k.replace(":", "_COLON_"): v
|
|
1006
|
+
for k, v in format_variables.items()
|
|
1007
|
+
},
|
|
1008
|
+
)
|
|
1009
|
+
)
|
|
999
1010
|
except (ValueError, TypeError) as e:
|
|
1000
1011
|
raise MisconfiguredError(
|
|
1001
1012
|
f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
|
|
@@ -1601,3 +1612,65 @@ def parse_le_uint16(data: bytes) -> int:
|
|
|
1601
1612
|
65535
|
|
1602
1613
|
"""
|
|
1603
1614
|
return struct.unpack("<H", data)[0]
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
def format_pydantic_error(e: PydanticValidationError) -> str:
|
|
1618
|
+
"""Format Pydantic ValidationError
|
|
1619
|
+
|
|
1620
|
+
:param e: A Pydantic ValidationError object
|
|
1621
|
+
:type e: PydanticValidationError
|
|
1622
|
+
"""
|
|
1623
|
+
error_header = f"{e.error_count()} error(s). "
|
|
1624
|
+
|
|
1625
|
+
error_messages = [
|
|
1626
|
+
f'{err["loc"][0]}: {err["msg"]}' if err["loc"] else err["msg"]
|
|
1627
|
+
for err in e.errors()
|
|
1628
|
+
]
|
|
1629
|
+
return error_header + "; ".join(set(error_messages))
|
|
1630
|
+
|
|
1631
|
+
|
|
1632
|
+
def get_collection_dates(
|
|
1633
|
+
collection_dict: dict[str, Any]
|
|
1634
|
+
) -> tuple[Optional[str], Optional[str]]:
|
|
1635
|
+
"""Extract mission start and end dates from collection configuration.
|
|
1636
|
+
|
|
1637
|
+
Extracts dates from the extent.temporal.interval structure.
|
|
1638
|
+
|
|
1639
|
+
:param collection_dict: Collection configuration dictionary
|
|
1640
|
+
:returns: Tuple of (mission_start_date, mission_end_date) as ISO strings or None
|
|
1641
|
+
|
|
1642
|
+
Example:
|
|
1643
|
+
>>> get_collection_dates({
|
|
1644
|
+
... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", "2023-12-31T23:59:59Z"]]}}
|
|
1645
|
+
... })
|
|
1646
|
+
('2017-10-13T00:00:00Z', '2023-12-31T23:59:59Z')
|
|
1647
|
+
|
|
1648
|
+
>>> get_collection_dates({
|
|
1649
|
+
... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", None]]}}
|
|
1650
|
+
... })
|
|
1651
|
+
('2017-10-13T00:00:00Z', None)
|
|
1652
|
+
|
|
1653
|
+
>>> get_collection_dates({})
|
|
1654
|
+
(None, None)
|
|
1655
|
+
"""
|
|
1656
|
+
extent_interval = (
|
|
1657
|
+
collection_dict.get("extent", {})
|
|
1658
|
+
.get("temporal", {})
|
|
1659
|
+
.get("interval", [[None, None]])
|
|
1660
|
+
)
|
|
1661
|
+
|
|
1662
|
+
if not extent_interval or len(extent_interval) == 0:
|
|
1663
|
+
return None, None
|
|
1664
|
+
|
|
1665
|
+
mission_start = (
|
|
1666
|
+
extent_interval[0][0]
|
|
1667
|
+
if len(extent_interval) > 0 and len(extent_interval[0]) > 0
|
|
1668
|
+
else None
|
|
1669
|
+
)
|
|
1670
|
+
mission_end = (
|
|
1671
|
+
extent_interval[0][1]
|
|
1672
|
+
if len(extent_interval) > 0 and len(extent_interval[0]) > 1
|
|
1673
|
+
else None
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
return mission_start, mission_end
|
eodag/utils/exceptions.py
CHANGED
|
@@ -49,11 +49,11 @@ class UnsupportedProvider(EodagError):
|
|
|
49
49
|
"""An error indicating that eodag does not support a provider"""
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
class
|
|
53
|
-
"""An error indicating that eodag does not support a
|
|
52
|
+
class UnsupportedCollection(EodagError):
|
|
53
|
+
"""An error indicating that eodag does not support a collection"""
|
|
54
54
|
|
|
55
|
-
def __init__(self,
|
|
56
|
-
self.
|
|
55
|
+
def __init__(self, collection: str) -> None:
|
|
56
|
+
self.collection = collection
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class UnsupportedDatasetAddressScheme(EodagError):
|
|
@@ -70,8 +70,8 @@ class NotAvailableError(EodagError):
|
|
|
70
70
|
"""An error indicating that the product is not available for download"""
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
class
|
|
74
|
-
"""An error indicating that eodag was unable to derive a
|
|
73
|
+
class NoMatchingCollection(EodagError):
|
|
74
|
+
"""An error indicating that eodag was unable to derive a collection from a set
|
|
75
75
|
of search parameters"""
|
|
76
76
|
|
|
77
77
|
|
eodag/utils/free_text_search.py
CHANGED
|
@@ -197,17 +197,17 @@ def compile_free_text_query(query: str) -> Callable[[dict[str, str]], bool]:
|
|
|
197
197
|
>>> evaluator = compile_free_text_query('("FooAndBar" OR BAR) AND "FOOBAR collection"')
|
|
198
198
|
>>> evaluator({
|
|
199
199
|
... "title": "titleFOOBAR - Lorem FOOBAR collection",
|
|
200
|
-
... "
|
|
200
|
+
... "description": "abstract FOOBAR - This is FOOBAR. FooAndBar"
|
|
201
201
|
... })
|
|
202
202
|
True
|
|
203
203
|
>>> evaluator({
|
|
204
204
|
... "title": "collection FOOBAR",
|
|
205
|
-
... "
|
|
205
|
+
... "description": "abstract FOOBAR - This is FOOBAR. FooAndBar"
|
|
206
206
|
... })
|
|
207
207
|
False
|
|
208
208
|
>>> evaluator({
|
|
209
209
|
... "title": "titleFOOBAR - Lorem FOOBAR ",
|
|
210
|
-
... "
|
|
210
|
+
... "description": "abstract FOOBAR - This is FOOBAR."
|
|
211
211
|
... })
|
|
212
212
|
False
|
|
213
213
|
>>> evaluator({"title": "Only Bar here"})
|