eodag 3.0.1__py3-none-any.whl → 3.1.0b2__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.
Files changed (87) hide show
  1. eodag/api/core.py +164 -127
  2. eodag/api/product/_assets.py +11 -11
  3. eodag/api/product/_product.py +45 -30
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +101 -85
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +78 -81
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +46 -22
  15. eodag/plugins/apis/usgs.py +16 -15
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +16 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +6 -7
  32. eodag/plugins/download/aws.py +58 -78
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +87 -44
  40. eodag/plugins/search/build_search_result.py +1067 -329
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +16 -15
  45. eodag/plugins/search/qssearch.py +103 -187
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +3 -3
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +663 -304
  50. eodag/resources/providers.yml +823 -1749
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +11 -0
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +40 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +15 -16
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +75 -28
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +152 -50
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +208 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/METADATA +77 -76
  81. eodag-3.1.0b2.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,7 @@ import logging
22
22
  import os
23
23
  import re
24
24
  import tempfile
25
- from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
25
+ from typing import TYPE_CHECKING, Any, Optional, Union
26
26
 
27
27
  import requests
28
28
  from requests import RequestException
@@ -38,7 +38,7 @@ try:
38
38
  except ImportError:
39
39
  from eodag.api.product._assets import AssetsDict
40
40
 
41
- from eodag.api.product.drivers import DRIVERS, NoDriver
41
+ from eodag.api.product.drivers import DRIVERS, LEGACY_DRIVERS, NoDriver
42
42
  from eodag.api.product.metadata_mapping import (
43
43
  DEFAULT_GEOMETRY,
44
44
  NOT_AVAILABLE,
@@ -113,17 +113,20 @@ class EOProduct:
113
113
  """
114
114
 
115
115
  provider: str
116
- properties: Dict[str, Any]
116
+ properties: dict[str, Any]
117
117
  product_type: Optional[str]
118
118
  location: str
119
+ filename: str
119
120
  remote_location: str
120
121
  search_kwargs: Any
121
122
  geometry: BaseGeometry
122
123
  search_intersection: Optional[BaseGeometry]
123
124
  assets: AssetsDict
125
+ #: Driver enables additional methods to be called on the EOProduct
126
+ driver: DatasetDriver
124
127
 
125
128
  def __init__(
126
- self, provider: str, properties: Dict[str, Any], **kwargs: Any
129
+ self, provider: str, properties: dict[str, Any], **kwargs: Any
127
130
  ) -> None:
128
131
  self.provider = provider
129
132
  self.product_type = kwargs.get("productType")
@@ -174,7 +177,7 @@ class EOProduct:
174
177
  self.downloader: Optional[Union[Api, Download]] = None
175
178
  self.downloader_auth: Optional[Authentication] = None
176
179
 
177
- def as_dict(self) -> Dict[str, Any]:
180
+ def as_dict(self) -> dict[str, Any]:
178
181
  """Builds a representation of EOProduct as a dictionary to enable its geojson
179
182
  serialization
180
183
 
@@ -185,7 +188,7 @@ class EOProduct:
185
188
  if self.search_intersection is not None:
186
189
  search_intersection = geometry.mapping(self.search_intersection)
187
190
 
188
- geojson_repr: Dict[str, Any] = {
191
+ geojson_repr: dict[str, Any] = {
189
192
  "type": "Feature",
190
193
  "geometry": geometry.mapping(self.geometry),
191
194
  "id": self.properties["id"],
@@ -205,7 +208,7 @@ class EOProduct:
205
208
  return geojson_repr
206
209
 
207
210
  @classmethod
208
- def from_geojson(cls, feature: Dict[str, Any]) -> EOProduct:
211
+ def from_geojson(cls, feature: dict[str, Any]) -> EOProduct:
209
212
  """Builds an :class:`~eodag.api.product._product.EOProduct` object from its
210
213
  representation as geojson
211
214
 
@@ -281,8 +284,8 @@ class EOProduct:
281
284
  def download(
282
285
  self,
283
286
  progress_callback: Optional[ProgressCallback] = None,
284
- wait: int = DEFAULT_DOWNLOAD_WAIT,
285
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
287
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
288
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
286
289
  **kwargs: Unpack[DownloadConf],
287
290
  ) -> str:
288
291
  """Download the EO product using the provided download plugin and the
@@ -355,7 +358,7 @@ class EOProduct:
355
358
 
356
359
  def _init_progress_bar(
357
360
  self, progress_callback: Optional[ProgressCallback]
358
- ) -> Tuple[ProgressCallback, bool]:
361
+ ) -> tuple[ProgressCallback, bool]:
359
362
  # progress bar init
360
363
  if progress_callback is None:
361
364
  progress_callback = ProgressCallback(position=1)
@@ -462,12 +465,20 @@ class EOProduct:
462
465
  )
463
466
  if not isinstance(auth, AuthBase):
464
467
  auth = None
468
+ # Read the ssl_verify parameter used on the provider config
469
+ # to ensure the same behavior for get_quicklook as other download functions
470
+ ssl_verify = (
471
+ getattr(self.downloader.config, "ssl_verify", True)
472
+ if self.downloader
473
+ else True
474
+ )
465
475
  with requests.get(
466
476
  self.properties["quicklook"],
467
477
  stream=True,
468
478
  auth=auth,
469
479
  headers=USER_AGENT,
470
480
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
481
+ verify=ssl_verify,
471
482
  ) as stream:
472
483
  try:
473
484
  stream.raise_for_status()
@@ -497,11 +508,16 @@ class EOProduct:
497
508
  try:
498
509
  for driver_conf in DRIVERS:
499
510
  if all([criteria(self) for criteria in driver_conf["criteria"]]):
500
- return driver_conf["driver"]
511
+ driver = driver_conf["driver"]
512
+ break
513
+ # use legacy driver for deprecated get_data method usage
514
+ for lecacy_conf in LEGACY_DRIVERS:
515
+ if all([criteria(self) for criteria in lecacy_conf["criteria"]]):
516
+ driver.legacy = lecacy_conf["driver"]
517
+ break
518
+ return driver
501
519
  except TypeError:
502
- logger.warning(
503
- "Drivers definition seems out-of-date, please update eodag-cube"
504
- )
520
+ logger.info("No driver matching")
505
521
  pass
506
522
  return NoDriver()
507
523
 
@@ -525,22 +541,21 @@ class EOProduct:
525
541
  <tr style='background-color: transparent;'>
526
542
  <td style='text-align: left; vertical-align: top;'>
527
543
  {dict_to_html_table({
528
- "provider": self.provider,
529
- "product_type": self.product_type,
530
- "properties[&quot;id&quot;]": self.properties.get('id', None),
531
- "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
532
- 'startTimeFromAscendingNode', None
533
- ),
534
- "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
535
- 'completionTimeFromAscendingNode', None
536
- ),
537
- }, brackets=False)}
538
- <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({
539
- len(self.properties)
540
- })</summary>{dict_to_html_table(self.properties, depth=1)}</details>
541
- <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({
542
- len(self.assets)
543
- })</summary>{self.assets._repr_html_(embeded=True)}</details>
544
+ "provider": self.provider,
545
+ "product_type": self.product_type,
546
+ "properties[&quot;id&quot;]": self.properties.get('id', None),
547
+ "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
548
+ 'startTimeFromAscendingNode', None
549
+ ),
550
+ "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
551
+ 'completionTimeFromAscendingNode', None
552
+ ),
553
+ }, brackets=False)}
554
+ <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({len(
555
+ self.properties)})</summary>{
556
+ dict_to_html_table(self.properties, depth=1)}</details>
557
+ <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({len(
558
+ self.assets)})</summary>{self.assets._repr_html_(embeded=True)}</details>
544
559
  </td>
545
560
  <td {geom_style} title='geometry'>geometry<br />{self.geometry._repr_svg_()}</td>
546
561
  <td {thumbnail_style} title='properties[&quot;thumbnail&quot;]'>{thumbnail_html}</td>
@@ -16,14 +16,91 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  """EODAG drivers package"""
19
+ from __future__ import annotations
20
+
21
+ from typing import Callable, TypedDict
22
+
19
23
  from eodag.api.product.drivers.base import DatasetDriver, NoDriver
24
+ from eodag.api.product.drivers.generic import GenericDriver
25
+ from eodag.api.product.drivers.sentinel1 import Sentinel1Driver
26
+ from eodag.api.product.drivers.sentinel2 import Sentinel2Driver
20
27
 
21
28
  try:
22
- from eodag_cube.api.product.drivers import ( # pyright: ignore[reportMissingImports]
23
- DRIVERS,
29
+ # import from eodag-cube if installed
30
+ from eodag_cube.api.product.drivers.generic import ( # pyright: ignore[reportMissingImports]; isort: skip
31
+ GenericDriver as GenericDriver_cube,
32
+ )
33
+ from eodag_cube.api.product.drivers.sentinel2_l1c import ( # pyright: ignore[reportMissingImports]; isort: skip
34
+ Sentinel2L1C as Sentinel2L1C_cube,
35
+ )
36
+ from eodag_cube.api.product.drivers.stac_assets import ( # pyright: ignore[reportMissingImports]; isort: skip
37
+ StacAssets as StacAssets_cube,
24
38
  )
25
39
  except ImportError:
26
- DRIVERS = []
40
+ GenericDriver_cube = NoDriver
41
+ Sentinel2L1C_cube = NoDriver
42
+ StacAssets_cube = NoDriver
43
+
44
+
45
+ class DriverCriteria(TypedDict):
46
+ """Driver criteria definition"""
47
+
48
+ #: Function that returns True if the driver is suitable for the given :class:`~eodag.api.product._product.EOProduct`
49
+ criteria: list[Callable[..., bool]]
50
+ #: driver to use
51
+ driver: DatasetDriver
52
+
53
+
54
+ #: list of drivers and their criteria
55
+ DRIVERS: list[DriverCriteria] = [
56
+ {
57
+ "criteria": [
58
+ lambda prod: True
59
+ if (prod.product_type or "").startswith("S2_MSI_")
60
+ else False
61
+ ],
62
+ "driver": Sentinel2Driver(),
63
+ },
64
+ {
65
+ "criteria": [
66
+ lambda prod: True
67
+ if (prod.product_type or "").startswith("S1_SAR_")
68
+ else False
69
+ ],
70
+ "driver": Sentinel1Driver(),
71
+ },
72
+ {
73
+ "criteria": [lambda prod: True],
74
+ "driver": GenericDriver(),
75
+ },
76
+ ]
77
+
78
+
79
+ #: list of legacy drivers and their criteria
80
+ LEGACY_DRIVERS: list[DriverCriteria] = [
81
+ {
82
+ "criteria": [
83
+ lambda prod: True if len(getattr(prod, "assets", {})) > 0 else False
84
+ ],
85
+ "driver": StacAssets_cube(),
86
+ },
87
+ {
88
+ "criteria": [lambda prod: True if "assets" in prod.properties else False],
89
+ "driver": StacAssets_cube(),
90
+ },
91
+ {
92
+ "criteria": [
93
+ lambda prod: True
94
+ if getattr(prod, "product_type") == "S2_MSI_L1C"
95
+ else False
96
+ ],
97
+ "driver": Sentinel2L1C_cube(),
98
+ },
99
+ {
100
+ "criteria": [lambda prod: True],
101
+ "driver": GenericDriver_cube(),
102
+ },
103
+ ]
27
104
 
28
105
  # exportable content
29
- __all__ = ["DRIVERS", "DatasetDriver", "NoDriver"]
106
+ __all__ = ["DRIVERS", "DatasetDriver", "GenericDriver", "NoDriver", "Sentinel2Driver"]
@@ -17,15 +17,72 @@
17
17
  # limitations under the License.
18
18
  from __future__ import annotations
19
19
 
20
- from typing import TYPE_CHECKING
20
+ import logging
21
+ import re
22
+ from typing import TYPE_CHECKING, Optional, TypedDict
23
+
24
+ from eodag.utils import _deprecated
21
25
 
22
26
  if TYPE_CHECKING:
23
27
  from eodag.api.product import EOProduct
24
28
 
25
29
 
30
+ class AssetPatterns(TypedDict):
31
+ """Asset patterns definition"""
32
+
33
+ #: pattern to match and extract asset key
34
+ pattern: re.Pattern
35
+ #: roles associated to the asset key
36
+ roles: list[str]
37
+
38
+
39
+ logger = logging.getLogger("eodag.driver.base")
40
+
41
+
26
42
  class DatasetDriver(metaclass=type):
27
- """Dataset driver"""
43
+ """Parent class for all dataset drivers.
44
+
45
+ Drivers will provide methods adapted to a given :class:`~eodag.api.product._product.EOProduct` related to predefined
46
+ criteria.
47
+ """
48
+
49
+ #: legacy driver for deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method usage
50
+ legacy: DatasetDriver
28
51
 
52
+ #: list of patterns to match asset keys and roles
53
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = []
54
+
55
+ #: strip non-alphanumeric characters at the beginning and end of the key
56
+ STRIP_SPECIAL_PATTERN = re.compile(r"^[^A-Z0-9]+|[^A-Z0-9]+$", re.IGNORECASE)
57
+
58
+ def _normalize_key(self, key, eo_product):
59
+ # default cleanup
60
+ norm_key = key.replace(eo_product.properties.get("id", ""), "")
61
+ norm_key = re.sub(self.STRIP_SPECIAL_PATTERN, "", norm_key)
62
+
63
+ return norm_key
64
+
65
+ def guess_asset_key_and_roles(
66
+ self, href: str, eo_product: EOProduct
67
+ ) -> tuple[Optional[str], Optional[list[str]]]:
68
+ """Guess the asset key and roles from the given href.
69
+
70
+ :param href: The asset href
71
+ :param eo_product: The product to which the asset belongs
72
+ :returns: The asset key and roles
73
+ """
74
+ for pattern_dict in self.ASSET_KEYS_PATTERNS_ROLES:
75
+ if matched := pattern_dict["pattern"].match(href):
76
+ extracted_key, roles = (
77
+ "".join([m for m in matched.groups() if m is not None]),
78
+ pattern_dict.get("roles"),
79
+ )
80
+ normalized_key = self._normalize_key(extracted_key, eo_product)
81
+ return normalized_key or extracted_key, roles
82
+ logger.debug(f"No key & roles could be guessed for {href}")
83
+ return None, None
84
+
85
+ @_deprecated(reason="Method used by deprecated get_data", version="3.1.0")
29
86
  def get_data_address(self, eo_product: EOProduct, band: str) -> str:
30
87
  """Retrieve the address of the dataset represented by `eo_product`.
31
88
 
@@ -34,12 +91,16 @@ class DatasetDriver(metaclass=type):
34
91
  :returns: An address for the dataset
35
92
  :raises: :class:`~eodag.utils.exceptions.AddressNotFound`
36
93
  :raises: :class:`~eodag.utils.exceptions.UnsupportedDatasetAddressScheme`
94
+
95
+ .. deprecated:: 3.1.0
96
+ Method used by deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data`
37
97
  """
38
98
  raise NotImplementedError
39
99
 
40
100
 
41
101
  class NoDriver(DatasetDriver):
42
- """A default driver that does not implement any of the methods it should implement, used for all product types for
43
- which the :meth:`~eodag.api.product._product.EOProduct.get_data` method is not yet implemented in eodag. Expect a
102
+ """A default :attr:`~eodag.api.product.drivers.base.DatasetDriver.legacy` driver that does not implement any of the
103
+ methods it should implement, used for all product types for which the deprecated
104
+ :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method is not implemented. Expect a
44
105
  :exc:`NotImplementedError` when trying to get the data in that case.
45
106
  """
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2021, CS GROUP - France, http://www.c-s.fr
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
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import re
22
+
23
+ from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
24
+
25
+ logger = logging.getLogger("eodag.driver.generic")
26
+
27
+
28
+ class GenericDriver(DatasetDriver):
29
+ """Generic default Driver"""
30
+
31
+ #: list of patterns to match asset keys and roles
32
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
33
+ # data
34
+ {
35
+ "pattern": re.compile(
36
+ r"^(?:.*[/\\])?([^/\\]+)(\.jp2|\.tiff?|\.dat|\.nc|\.grib2?)$",
37
+ re.IGNORECASE,
38
+ ),
39
+ "roles": ["data"],
40
+ },
41
+ # metadata
42
+ {
43
+ "pattern": re.compile(
44
+ r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
45
+ ),
46
+ "roles": ["metadata"],
47
+ },
48
+ # thumbnail
49
+ {
50
+ "pattern": re.compile(
51
+ r"^(?:.*[/\\])?(thumbnail)(\.jpg|\.jpeg|\.png)$", re.IGNORECASE
52
+ ),
53
+ "roles": ["thumbnail"],
54
+ },
55
+ # quicklook
56
+ {
57
+ "pattern": re.compile(
58
+ r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpg|\.jpeg|\.png)$",
59
+ re.IGNORECASE,
60
+ ),
61
+ "roles": ["overview"],
62
+ },
63
+ # default
64
+ {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
65
+ ]
@@ -0,0 +1,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2025, CS GROUP - France, http://www.c-s.fr
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
+ from __future__ import annotations
19
+
20
+ import re
21
+ from typing import TYPE_CHECKING
22
+
23
+ from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
24
+
25
+ if TYPE_CHECKING:
26
+ from eodag.api.product._product import EOProduct
27
+
28
+
29
+ class Sentinel1Driver(DatasetDriver):
30
+ """Driver for Sentinel1 products"""
31
+
32
+ #: pattern to match data-role keys
33
+ DATA_PATTERN = re.compile(r"[vh]{2}", re.IGNORECASE)
34
+
35
+ #: list of patterns to replace in asset keys
36
+ REPLACE_PATTERNS = [
37
+ (re.compile(r"s1a?", re.IGNORECASE), ""),
38
+ (re.compile(r"grd", re.IGNORECASE), ""),
39
+ (re.compile(r"slc", re.IGNORECASE), ""),
40
+ (re.compile(r"ocn", re.IGNORECASE), ""),
41
+ (re.compile(r"iw", re.IGNORECASE), ""),
42
+ (re.compile(r"ew", re.IGNORECASE), ""),
43
+ (re.compile(r"wv", re.IGNORECASE), ""),
44
+ (re.compile(r"sm", re.IGNORECASE), ""),
45
+ (re.compile(r"raw([-_]s)?", re.IGNORECASE), ""),
46
+ (re.compile(r"[t?0-9]{3,}", re.IGNORECASE), ""),
47
+ (re.compile(r"-+"), "-"),
48
+ (re.compile(r"-+\."), "."),
49
+ (re.compile(r"_+"), "_"),
50
+ (re.compile(r"_+\."), "."),
51
+ ]
52
+
53
+ #: list of patterns to match asset keys and roles
54
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
55
+ # data
56
+ {
57
+ "pattern": re.compile(
58
+ r"^.*?([vh]{2}).*\.(?:jp2|tiff?|dat)$", re.IGNORECASE
59
+ ),
60
+ "roles": ["data"],
61
+ },
62
+ # metadata
63
+ {
64
+ "pattern": re.compile(
65
+ r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
66
+ ),
67
+ "roles": ["metadata"],
68
+ },
69
+ # thumbnail
70
+ {
71
+ "pattern": re.compile(
72
+ r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)$", re.IGNORECASE
73
+ ),
74
+ "roles": ["thumbnail"],
75
+ },
76
+ # quicklook
77
+ {
78
+ "pattern": re.compile(
79
+ r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpe?g|\.png)$",
80
+ re.IGNORECASE,
81
+ ),
82
+ "roles": ["overview"],
83
+ },
84
+ # default
85
+ {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
86
+ ]
87
+
88
+ def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
89
+ if self.DATA_PATTERN.fullmatch(key):
90
+ return key.upper()
91
+
92
+ key = super()._normalize_key(key, eo_product)
93
+
94
+ for pattern, replacement in self.REPLACE_PATTERNS:
95
+ key = pattern.sub(replacement, key)
96
+
97
+ return super()._normalize_key(key, eo_product)
@@ -0,0 +1,95 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2021, CS GROUP - France, http://www.c-s.fr
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
+ from __future__ import annotations
19
+
20
+ import re
21
+ from typing import TYPE_CHECKING
22
+
23
+ from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
24
+
25
+ if TYPE_CHECKING:
26
+ from eodag.api.product._product import EOProduct
27
+
28
+
29
+ class Sentinel2Driver(DatasetDriver):
30
+ """Driver for Sentinel2 products"""
31
+
32
+ #: Band keys associated with their default Ground Sampling Distance (GSD)
33
+ BANDS_DEFAULT_GSD = {
34
+ "10M": ("B02", "B03", "B04", "B08", "TCI"),
35
+ "20M": ("B05", "B06", "B07", "B11", "B12", "B8A"),
36
+ "60M": ("B01", "B09", "B10"),
37
+ }
38
+
39
+ #: list of patterns to match asset keys and roles
40
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
41
+ # masks
42
+ {
43
+ "pattern": re.compile(r"^.*?(MSK_[^/\\]+)\.(?:jp2|tiff?)$", re.IGNORECASE),
44
+ "roles": ["data-mask"],
45
+ },
46
+ # visual
47
+ {
48
+ "pattern": re.compile(
49
+ r"^.*?(TCI)(_[0-9]+m)?\.(?:jp2|tiff?)$", re.IGNORECASE
50
+ ),
51
+ "roles": ["visual"],
52
+ },
53
+ # bands
54
+ {
55
+ "pattern": re.compile(
56
+ r"^.*?([A-Z]+[0-9]*[A-Z]?)(_[0-9]+m)?\.(?:jp2|tiff?)$", re.IGNORECASE
57
+ ),
58
+ "roles": ["data"],
59
+ },
60
+ # metadata
61
+ {
62
+ "pattern": re.compile(
63
+ r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
64
+ ),
65
+ "roles": ["metadata"],
66
+ },
67
+ # thumbnail
68
+ {
69
+ "pattern": re.compile(
70
+ r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)$", re.IGNORECASE
71
+ ),
72
+ "roles": ["thumbnail"],
73
+ },
74
+ # quicklook
75
+ {
76
+ "pattern": re.compile(
77
+ r"^(?:.*[/\\])?[^/\\]+(-ql|preview|quick-?look)(\.jpe?g|\.png)$",
78
+ re.IGNORECASE,
79
+ ),
80
+ "roles": ["overview"],
81
+ },
82
+ # default
83
+ {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
84
+ ]
85
+
86
+ def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
87
+ upper_key = key.upper()
88
+ # check if key matched any normalized
89
+ for res in self.BANDS_DEFAULT_GSD:
90
+ if res in upper_key:
91
+ for norm_key in self.BANDS_DEFAULT_GSD[res]:
92
+ if norm_key in upper_key:
93
+ return norm_key
94
+
95
+ return super()._normalize_key(key, eo_product)