mapchete-eo 2025.10.1__py2.py3-none-any.whl → 2025.11.0__py2.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 (63) hide show
  1. mapchete_eo/__init__.py +1 -1
  2. mapchete_eo/base.py +94 -54
  3. mapchete_eo/cli/options_arguments.py +11 -27
  4. mapchete_eo/cli/s2_brdf.py +1 -1
  5. mapchete_eo/cli/s2_cat_results.py +4 -20
  6. mapchete_eo/cli/s2_find_broken_products.py +4 -20
  7. mapchete_eo/cli/s2_jp2_static_catalog.py +2 -2
  8. mapchete_eo/cli/static_catalog.py +4 -45
  9. mapchete_eo/eostac.py +1 -1
  10. mapchete_eo/io/assets.py +7 -7
  11. mapchete_eo/io/items.py +36 -23
  12. mapchete_eo/io/path.py +19 -8
  13. mapchete_eo/io/products.py +22 -24
  14. mapchete_eo/platforms/sentinel2/__init__.py +1 -1
  15. mapchete_eo/platforms/sentinel2/_mapper_registry.py +89 -0
  16. mapchete_eo/platforms/sentinel2/brdf/correction.py +1 -1
  17. mapchete_eo/platforms/sentinel2/brdf/hls.py +1 -1
  18. mapchete_eo/platforms/sentinel2/brdf/models.py +1 -1
  19. mapchete_eo/platforms/sentinel2/brdf/protocols.py +1 -1
  20. mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +1 -1
  21. mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +1 -1
  22. mapchete_eo/platforms/sentinel2/config.py +73 -13
  23. mapchete_eo/platforms/sentinel2/driver.py +0 -39
  24. mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +6 -0
  25. mapchete_eo/platforms/sentinel2/{path_mappers → metadata_parser}/base.py +1 -1
  26. mapchete_eo/platforms/sentinel2/{path_mappers/metadata_xml.py → metadata_parser/default_path_mapper.py} +2 -2
  27. mapchete_eo/platforms/sentinel2/metadata_parser/models.py +78 -0
  28. mapchete_eo/platforms/sentinel2/{metadata_parser.py → metadata_parser/s2metadata.py} +51 -144
  29. mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +57 -0
  30. mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +108 -0
  31. mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +171 -0
  32. mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +217 -0
  33. mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +22 -1
  34. mapchete_eo/platforms/sentinel2/processing_baseline.py +3 -0
  35. mapchete_eo/platforms/sentinel2/product.py +83 -18
  36. mapchete_eo/platforms/sentinel2/source.py +114 -0
  37. mapchete_eo/platforms/sentinel2/types.py +5 -0
  38. mapchete_eo/product.py +14 -8
  39. mapchete_eo/protocols.py +5 -0
  40. mapchete_eo/search/__init__.py +3 -3
  41. mapchete_eo/search/base.py +105 -92
  42. mapchete_eo/search/config.py +25 -4
  43. mapchete_eo/search/s2_mgrs.py +8 -9
  44. mapchete_eo/search/stac_search.py +96 -77
  45. mapchete_eo/search/stac_static.py +47 -91
  46. mapchete_eo/search/utm_search.py +36 -49
  47. mapchete_eo/settings.py +1 -0
  48. mapchete_eo/sort.py +4 -6
  49. mapchete_eo/source.py +107 -0
  50. {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/METADATA +2 -1
  51. mapchete_eo-2025.11.0.dist-info/RECORD +89 -0
  52. {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/entry_points.txt +1 -1
  53. mapchete_eo/archives/__init__.py +0 -0
  54. mapchete_eo/archives/base.py +0 -65
  55. mapchete_eo/geometry.py +0 -271
  56. mapchete_eo/known_catalogs.py +0 -42
  57. mapchete_eo/platforms/sentinel2/archives.py +0 -190
  58. mapchete_eo/platforms/sentinel2/path_mappers/__init__.py +0 -29
  59. mapchete_eo/platforms/sentinel2/path_mappers/earthsearch.py +0 -34
  60. mapchete_eo/platforms/sentinel2/path_mappers/sinergise.py +0 -105
  61. mapchete_eo-2025.10.1.dist-info/RECORD +0 -88
  62. {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/WHEEL +0 -0
  63. {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2025.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,22 +1,21 @@
1
1
  from functools import cached_property
2
2
  import logging
3
3
  import warnings
4
- from typing import Any, Callable, Dict, Generator, List, Optional, Union
4
+ from typing import Any, Dict, Generator, List, Optional, Union
5
5
 
6
6
  from mapchete import Bounds
7
7
  from mapchete.types import BoundsLike
8
8
  from pystac import Item, Catalog, Collection
9
9
  from mapchete.io.vector import bounds_intersect
10
- from mapchete.path import MPathLike
11
10
  from pystac.stac_io import StacIO
12
- from pystac_client import Client
11
+ from pystac_client import CollectionClient
13
12
  from shapely.geometry import shape
14
13
  from shapely.geometry.base import BaseGeometry
15
14
 
16
15
  from mapchete_eo.search.base import (
17
- CatalogSearcher,
16
+ CollectionSearcher,
18
17
  FSSpecStacIO,
19
- StaticCatalogWriterMixin,
18
+ StaticCollectionWriterMixin,
20
19
  filter_items,
21
20
  )
22
21
  from mapchete_eo.search.config import StacStaticConfig
@@ -29,21 +28,42 @@ logger = logging.getLogger(__name__)
29
28
  StacIO.set_default(FSSpecStacIO)
30
29
 
31
30
 
32
- class STACStaticCatalog(StaticCatalogWriterMixin, CatalogSearcher):
31
+ class STACStaticCollection(StaticCollectionWriterMixin, CollectionSearcher):
33
32
  config_cls = StacStaticConfig
34
33
 
35
- def __init__(
36
- self,
37
- baseurl: MPathLike,
38
- stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None,
39
- ):
40
- self.client = Client.from_file(str(baseurl), stac_io=FSSpecStacIO())
41
- self.collections = [c.id for c in self.client.get_children()]
42
- self.stac_item_modifiers = stac_item_modifiers
34
+ @cached_property
35
+ def client(self) -> CollectionClient:
36
+ return CollectionClient.from_file(str(self.collection), stac_io=FSSpecStacIO())
43
37
 
44
38
  @cached_property
45
39
  def eo_bands(self) -> List[str]:
46
- return self._eo_bands()
40
+ eo_bands = self.client.extra_fields.get("properties", {}).get("eo:bands")
41
+ if eo_bands:
42
+ return eo_bands
43
+ else:
44
+ warnings.warn(
45
+ "Unable to read eo:bands definition from collection. "
46
+ "Trying now to get information from assets ..."
47
+ )
48
+ # see if eo:bands can be found in properties
49
+ try:
50
+ item = next(self.client.get_items(recursive=True))
51
+ eo_bands = item.properties.get("eo:bands")
52
+ if eo_bands:
53
+ return eo_bands
54
+
55
+ # look through the assets and collect eo:bands
56
+ out = {}
57
+ for asset in item.assets.values():
58
+ for eo_band in asset.extra_fields.get("eo:bands", []):
59
+ out[eo_band["name"]] = eo_band
60
+ if out:
61
+ return [v for v in out.values()]
62
+ except StopIteration:
63
+ pass
64
+
65
+ logger.debug("cannot find eo:bands definition")
66
+ return []
47
67
 
48
68
  @cached_property
49
69
  def id(self) -> str:
@@ -62,16 +82,13 @@ class STACStaticCatalog(StaticCatalogWriterMixin, CatalogSearcher):
62
82
  time: Optional[Union[TimeRange, List[TimeRange]]] = None,
63
83
  bounds: Optional[BoundsLike] = None,
64
84
  area: Optional[BaseGeometry] = None,
85
+ query: Optional[str] = None,
65
86
  search_kwargs: Optional[Dict[str, Any]] = None,
66
87
  ) -> Generator[Item, None, None]:
67
- config = self.config_cls(**search_kwargs or {})
68
88
  if area is None and bounds:
69
89
  bounds = Bounds.from_inp(bounds)
70
90
  area = shape(bounds)
71
- for item in filter_items(
72
- self._raw_search(time=time, area=area),
73
- max_cloud_cover=config.max_cloud_cover,
74
- ):
91
+ for item in filter_items(self._raw_search(time=time, area=area), query=query):
75
92
  yield item
76
93
 
77
94
  def _raw_search(
@@ -82,83 +99,22 @@ class STACStaticCatalog(StaticCatalogWriterMixin, CatalogSearcher):
82
99
  if area is not None and area.is_empty:
83
100
  return
84
101
  logger.debug("iterate through children")
85
- for collection in self.client.get_collections():
86
- if time:
87
- for time_range in time if isinstance(time, list) else [time]:
88
- for item in _all_intersecting_items(
89
- collection,
90
- area=area,
91
- time_range=time_range,
92
- ):
93
- item.make_asset_hrefs_absolute()
94
- yield item
95
- else:
102
+ if time:
103
+ for time_range in time if isinstance(time, list) else [time]:
96
104
  for item in _all_intersecting_items(
97
- collection,
105
+ self.client,
98
106
  area=area,
107
+ time_range=time_range,
99
108
  ):
100
109
  item.make_asset_hrefs_absolute()
101
110
  yield item
102
-
103
- def _eo_bands(self) -> List[str]:
104
- for collection in self.client.get_children():
105
- eo_bands = collection.extra_fields.get("properties", {}).get("eo:bands")
106
- if eo_bands:
107
- return eo_bands
108
- else:
109
- warnings.warn(
110
- "Unable to read eo:bands definition from collections. "
111
- "Trying now to get information from assets ..."
112
- )
113
-
114
- # see if eo:bands can be found in properties
115
- item = _get_first_item(self.client.get_children())
116
- eo_bands = item.properties.get("eo:bands")
117
- if eo_bands:
118
- return eo_bands
119
-
120
- # look through the assets and collect eo:bands
121
- out = {}
122
- for asset in item.assets.values():
123
- for eo_band in asset.extra_fields.get("eo:bands", []):
124
- out[eo_band["name"]] = eo_band
125
- if out:
126
- return [v for v in out.values()]
127
-
128
- logger.debug("cannot find eo:bands definition")
129
- return []
130
-
131
- def get_collections(
132
- self,
133
- time: Optional[Union[TimeRange, List[TimeRange]]] = None,
134
- bounds: Optional[BoundsLike] = None,
135
- area: Optional[BaseGeometry] = None,
136
- ):
137
- if area is None and bounds is not None:
138
- area = Bounds.from_inp(bounds).geometry
139
- for collection in self.client.get_children():
140
- if time:
141
- for time_range in time if isinstance(time, list) else [time]:
142
- if _collection_extent_intersects(
143
- collection,
144
- area=area,
145
- time_range=time_range,
146
- ):
147
- yield collection
148
- else:
149
- if _collection_extent_intersects(collection, area=area):
150
- yield collection
151
-
152
-
153
- def _get_first_item(collections):
154
- for collection in collections:
155
- for item in collection.get_all_items():
156
- return item
157
111
  else:
158
- for child in collection.get_children():
159
- return _get_first_item(child)
160
- else:
161
- raise ValueError("collections contain no items")
112
+ for item in _all_intersecting_items(
113
+ self.client,
114
+ area=area,
115
+ ):
116
+ item.make_asset_hrefs_absolute()
117
+ yield item
162
118
 
163
119
 
164
120
  def _all_intersecting_items(
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  from functools import cached_property
3
3
  import logging
4
- from typing import Any, Callable, Dict, Generator, List, Optional, Set, Union
4
+ from typing import Any, Dict, Generator, List, Optional, Set, Union
5
5
 
6
6
  from mapchete.io.vector import fiona_open
7
7
  from mapchete.path import MPath, MPathLike
@@ -15,8 +15,8 @@ from shapely.geometry.base import BaseGeometry
15
15
  from mapchete_eo.exceptions import ItemGeometryError
16
16
  from mapchete_eo.product import blacklist_products
17
17
  from mapchete_eo.search.base import (
18
- CatalogSearcher,
19
- StaticCatalogWriterMixin,
18
+ CollectionSearcher,
19
+ StaticCollectionWriterMixin,
20
20
  filter_items,
21
21
  )
22
22
  from mapchete_eo.search.config import UTMSearchConfig
@@ -28,7 +28,7 @@ from mapchete_eo.types import TimeRange
28
28
  logger = logging.getLogger(__name__)
29
29
 
30
30
 
31
- class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
31
+ class UTMSearchCatalog(StaticCollectionWriterMixin, CollectionSearcher):
32
32
  endpoint: str
33
33
  id: str
34
34
  day_subdir_schema: str
@@ -42,36 +42,41 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
42
42
  )
43
43
  config_cls = UTMSearchConfig
44
44
 
45
- def __init__(
46
- self,
47
- endpoint: Optional[MPathLike] = None,
48
- collections: List[str] = [],
49
- stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None,
50
- ):
51
- self.endpoint = endpoint or self.endpoint
52
- if len(collections) == 0: # pragma: no cover
53
- raise ValueError("no collections provided")
54
- self.collections = collections
55
- self.stac_item_modifiers = stac_item_modifiers
56
-
57
45
  @cached_property
58
46
  def eo_bands(self) -> List[str]: # pragma: no cover
59
- return self._eo_bands()
47
+ for (
48
+ collection_properties
49
+ ) in UTMSearchConfig().sinergise_aws_collections.values():
50
+ if collection_properties["id"] == self.collection.split("/")[-1]:
51
+ collection = Collection.from_dict(
52
+ collection_properties["path"].read_json()
53
+ )
54
+ if collection:
55
+ summary = collection.summaries.to_dict()
56
+ if "eo:bands" in summary:
57
+ return summary["eo:bands"]
58
+ else:
59
+ raise ValueError(f"cannot find collection {collection}")
60
+ else:
61
+ logger.debug(
62
+ "cannot find eo:bands definition from collection %s",
63
+ self.collection,
64
+ )
65
+ return []
60
66
 
61
67
  def search(
62
68
  self,
63
69
  time: Optional[Union[TimeRange, List[TimeRange]]] = None,
64
70
  bounds: Optional[BoundsLike] = None,
65
71
  area: Optional[BaseGeometry] = None,
72
+ query: Optional[str] = None,
66
73
  search_kwargs: Optional[Dict[str, Any]] = None,
67
74
  ) -> Generator[Item, None, None]:
68
- config = self.config_cls(**search_kwargs or {})
69
- if bounds:
70
- bounds = Bounds.from_inp(bounds)
71
-
72
75
  for item in filter_items(
73
- self._raw_search(time=time, bounds=bounds, area=area),
74
- max_cloud_cover=config.max_cloud_cover,
76
+ self._raw_search(
77
+ time=time, bounds=Bounds.from_inp(bounds) if bounds else None, area=area
78
+ ),
79
+ query=query,
75
80
  ):
76
81
  yield item
77
82
 
@@ -92,7 +97,12 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
92
97
  elif bounds is not None:
93
98
  bounds = Bounds.from_inp(bounds)
94
99
  area = shape(bounds)
95
- for time_range in time if isinstance(time, list) else [time]:
100
+
101
+ # Cleaner time list in case None present as time (undefined)
102
+ time_list: list[TimeRange] = (
103
+ [t for t in time if t is not None] if isinstance(time, list) else [time]
104
+ )
105
+ for time_range in time_list:
96
106
  start_time = (
97
107
  time_range.start
98
108
  if isinstance(time_range.start, datetime.date)
@@ -151,28 +161,6 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
151
161
  elif area.intersects(shape(item.geometry)):
152
162
  yield item
153
163
 
154
- def _eo_bands(self) -> list:
155
- for collection_name in self.collections:
156
- for (
157
- collection_properties
158
- ) in UTMSearchConfig().sinergise_aws_collections.values():
159
- if collection_properties["id"] == collection_name:
160
- collection = Collection.from_dict(
161
- collection_properties["path"].read_json()
162
- )
163
- if collection:
164
- summary = collection.summaries.to_dict()
165
- if "eo:bands" in summary:
166
- return summary["eo:bands"]
167
- else:
168
- raise ValueError(f"cannot find collection {collection}")
169
- else:
170
- logger.debug(
171
- "cannot find eo:bands definition from collections %s",
172
- self.collections,
173
- )
174
- return []
175
-
176
164
  def get_collections(self):
177
165
  """
178
166
  yeild transformed collection from:
@@ -182,9 +170,8 @@ class UTMSearchCatalog(StaticCatalogWriterMixin, CatalogSearcher):
182
170
  """
183
171
  for collection_properties in self.config.sinergise_aws_collections.values():
184
172
  collection = Collection.from_dict(collection_properties["path"].read_json())
185
- for collection_name in self.collections:
186
- if collection_name == collection.id:
187
- yield collection
173
+ if self.collection.split("/")[-1] == collection.id:
174
+ yield collection
188
175
 
189
176
 
190
177
  def items_from_static_index(
mapchete_eo/settings.py CHANGED
@@ -16,6 +16,7 @@ class Settings(BaseSettings):
16
16
  default_cache_location: MPathLike = MPath("s3://eox-mhub-cache/")
17
17
  default_catalog_crs: CRS = CRS.from_epsg(4326)
18
18
  blacklist: Optional[MPathLike] = None
19
+ lazy_load_stac_items: bool = True
19
20
 
20
21
  # read from environment
21
22
  model_config = SettingsConfigDict(env_prefix="MAPCHETE_EO_")
mapchete_eo/sort.py CHANGED
@@ -5,10 +5,8 @@ This module holds all code required to sort products or slices.
5
5
  from typing import Callable, List, Optional
6
6
 
7
7
  from pydantic import BaseModel
8
- from pystac import Item
9
8
 
10
- from mapchete_eo.io.items import get_item_property
11
- from mapchete_eo.protocols import DateTimeProtocol
9
+ from mapchete_eo.protocols import DateTimeProtocol, GetPropertyProtocol
12
10
  from mapchete_eo.time import timedelta, to_datetime
13
11
  from mapchete_eo.types import DateTimeLike
14
12
 
@@ -51,11 +49,11 @@ class TargetDateSort(SortMethodConfig):
51
49
 
52
50
 
53
51
  def sort_objects_by_cloud_cover(
54
- objects: List[Item], reverse: bool = False
55
- ) -> List[Item]:
52
+ objects: List[GetPropertyProtocol], reverse: bool = False
53
+ ) -> List[GetPropertyProtocol]:
56
54
  if len(objects) == 0: # pragma: no cover
57
55
  return objects
58
- objects.sort(key=lambda x: get_item_property(x, "eo:cloud_cover"), reverse=reverse)
56
+ objects.sort(key=lambda x: x.get_property("eo:cloud_cover"), reverse=reverse)
59
57
  return objects
60
58
 
61
59
 
mapchete_eo/source.py ADDED
@@ -0,0 +1,107 @@
1
+ from functools import cached_property
2
+ from typing import Any, Dict, List, Literal, Optional, Generator, Union, Callable
3
+
4
+ from mapchete.bounds import Bounds
5
+ from mapchete.path import MPath
6
+ from mapchete.types import BoundsLike, CRSLike, MPathLike
7
+ from pydantic import BaseModel, ConfigDict, model_validator
8
+ from pystac import Item
9
+ from shapely.geometry.base import BaseGeometry
10
+ from shapely.errors import GEOSException
11
+
12
+ from mapchete_eo.exceptions import ItemGeometryError
13
+ from mapchete_eo.search.base import CollectionSearcher
14
+ from mapchete_eo.search import STACSearchCollection, STACStaticCollection
15
+ from mapchete_eo.settings import mapchete_eo_settings
16
+ from mapchete_eo.types import TimeRange
17
+
18
+
19
+ class Source(BaseModel):
20
+ """All information required to consume EO products."""
21
+
22
+ collection: str
23
+ catalog_crs: Optional[CRSLike] = mapchete_eo_settings.default_catalog_crs
24
+ query: Optional[str] = None
25
+ area: Optional[Union[MPathLike, dict, type[BaseGeometry]]] = None
26
+ bounds: Optional[BoundsLike] = None
27
+
28
+ model_config = ConfigDict(arbitrary_types_allowed=True)
29
+
30
+ @property
31
+ def item_modifier_funcs(self) -> List[Callable]:
32
+ return []
33
+
34
+ @cached_property
35
+ def catalog_type(self) -> Literal["search", "static"]:
36
+ # TODO: stupid test but probably sufficient
37
+ return "static" if self.collection.endswith(".json") else "search"
38
+
39
+ def _spatial_subset(
40
+ self,
41
+ bounds: Optional[BoundsLike] = None,
42
+ area: Optional[BaseGeometry] = None,
43
+ ) -> Dict[str, Any]:
44
+ """Combine bounds and area with bounds defined in Source if any."""
45
+ if self.bounds is None:
46
+ return {"bounds": bounds, "area": area}
47
+ self_bounds = Bounds.from_inp(self.bounds)
48
+ out = dict()
49
+ if bounds is not None:
50
+ bounds = Bounds.from_inp(bounds)
51
+ if bounds.intersects(self_bounds):
52
+ out["bounds"] = Bounds.from_inp(
53
+ bounds.geometry.intersection(self_bounds.geometry)
54
+ )
55
+ if area is not None:
56
+ out["area"] = area.intersection(self_bounds.geometry)
57
+ return out
58
+
59
+ def search(
60
+ self,
61
+ time: Optional[Union[TimeRange, List[TimeRange]]] = None,
62
+ bounds: Optional[BoundsLike] = None,
63
+ area: Optional[BaseGeometry] = None,
64
+ base_dir: Optional[MPathLike] = None,
65
+ ) -> Generator[Item, None, None]:
66
+ for item in self.get_catalog(base_dir=base_dir).search(
67
+ time=time,
68
+ query=self.query,
69
+ search_kwargs=dict(query=self.query) if self.query else None,
70
+ **self._spatial_subset(
71
+ bounds=bounds,
72
+ area=area,
73
+ ),
74
+ ):
75
+ yield self.apply_item_modifier_funcs(item)
76
+
77
+ def apply_item_modifier_funcs(self, item: Item) -> Item:
78
+ try:
79
+ for modifier in self.item_modifier_funcs:
80
+ item = modifier(item)
81
+ except GEOSException as exc:
82
+ raise ItemGeometryError(
83
+ f"item {item.get_self_href()} geometry could not be resolved: {str(exc)}"
84
+ )
85
+ return item
86
+
87
+ def get_catalog(self, base_dir: Optional[MPathLike] = None) -> CollectionSearcher:
88
+ match self.catalog_type:
89
+ case "search":
90
+ return STACSearchCollection(self.collection)
91
+ case "static":
92
+ return STACStaticCollection(
93
+ collection=MPath(self.collection).absolute_path(base_dir=base_dir)
94
+ )
95
+
96
+ def eo_bands(self, base_dir: Optional[MPathLike] = None) -> List[str]:
97
+ return self.get_catalog(base_dir=base_dir).eo_bands
98
+
99
+ @model_validator(mode="before")
100
+ def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
101
+ if "max_cloud_cover" in values:
102
+ raise DeprecationWarning(
103
+ "'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
104
+ )
105
+ elif "area" in values: # pragma: no cover
106
+ raise NotImplementedError("please use 'bounds' as spatial subset for now")
107
+ return values
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapchete-eo
3
- Version: 2025.10.1
3
+ Version: 2025.11.0
4
4
  Summary: mapchete EO data reader
5
5
  Project-URL: Homepage, https://gitlab.eox.at/maps/mapchete_eo
6
6
  Author-email: Joachim Ungar <joachim.ungar@eox.at>, Petr Sevcik <petr.sevcik@eox.at>
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Scientific/Engineering :: GIS
17
17
  Requires-Dist: click
18
+ Requires-Dist: cql2
18
19
  Requires-Dist: croniter
19
20
  Requires-Dist: lxml
20
21
  Requires-Dist: mapchete[complete]>=2025.10.0
@@ -0,0 +1,89 @@
1
+ mapchete_eo/__init__.py,sha256=2E0oLwEHFZUcghiMB3GjI_PKEVs-4pasu50WwMW-H2A,26
2
+ mapchete_eo/base.py,sha256=_6wbR9IZBBBdRBbZY4FEQ0uu603C-tMPYK4MGQ5xSYY,21953
3
+ mapchete_eo/blacklist.txt,sha256=6KhBY0jNjXgRpKRvKJoOTswvNUoP56IrIcNeCYnd2qo,17471
4
+ mapchete_eo/eostac.py,sha256=M-EL8y8pPw8H9hb85FPxSl-1FL_kIYOzxsTIhkrbBZY,570
5
+ mapchete_eo/exceptions.py,sha256=ul7_9o6_SfJXQPsDOQqRBhh09xv2t4AwHl6YJ7yWN5s,2150
6
+ mapchete_eo/product.py,sha256=0GJDk4pPvdCLxuJkZCMQnM7LIjvOBRUhIzchcQCqMjI,9644
7
+ mapchete_eo/protocols.py,sha256=KUbteFTOCSYEiSUJZj2MeckmrJHfJFrXCvZE70tByP8,1617
8
+ mapchete_eo/settings.py,sha256=lbwYoqiQXBRexxL7Y524Ry0dk4vBKpjN1u09UOP1_CM,734
9
+ mapchete_eo/sort.py,sha256=DfYAGEM6vzHJOFhaKxcKr-GwH5xM5ar4_mlp0uiVUK0,1787
10
+ mapchete_eo/source.py,sha256=Tfo5IIYdRWpS06K-M7mzesl4WIUEt4onGmc1uKJ0F14,4158
11
+ mapchete_eo/time.py,sha256=mCIrn6C-B7CDoFIIM4u8pG15nVV8KWQixF2fuhdDTdE,1799
12
+ mapchete_eo/types.py,sha256=yIHKZHlGCSerJzDBS2AH7yYLun4rY3rZZ73SCKiN26U,1814
13
+ mapchete_eo/array/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ mapchete_eo/array/buffer.py,sha256=GeeM7RVQ-Z_SKlkaeztWUrxEsa_yck-nXwXRIM3w0_I,483
15
+ mapchete_eo/array/color.py,sha256=ArJ3-0qjJJGAlRQodLios6JwD0uHLSRRb9BwfB31v-U,810
16
+ mapchete_eo/array/convert.py,sha256=oimklMfyS2XxQLd73zQ2cr9aE-39z1UIt6ry_2ZQI10,5390
17
+ mapchete_eo/cli/__init__.py,sha256=SRRilPKUtwsUCi1GdMNPxjIrCM-XyYM2hK1T9vvKUI8,946
18
+ mapchete_eo/cli/bounds.py,sha256=QcOXOXMcnd8ecwiCFVWukTLUFIkpj9SbB3ot-GA4heA,551
19
+ mapchete_eo/cli/options_arguments.py,sha256=6VqiwswEKpr5bS0w1ugRSf2z7rMKA-wiEFZdcmri94c,6916
20
+ mapchete_eo/cli/s2_brdf.py,sha256=siUGau8HzXNyQZkDUx1zAeaLcCfonJG5kWHSCvwJOhA,2729
21
+ mapchete_eo/cli/s2_cat_results.py,sha256=tR5Qm6kiCAcDO5V9AkYhoUbHyEx7OItSf6IqtIjhRLg,4648
22
+ mapchete_eo/cli/s2_find_broken_products.py,sha256=4nhtMLkys75enPV5lETw4iOn3CxxNnc-sMyhk_kaKx0,2946
23
+ mapchete_eo/cli/s2_jp2_static_catalog.py,sha256=_j_rfFnYHtE8k79TS9KFuX_IxOx_e-r7u6beoLYyxPY,5911
24
+ mapchete_eo/cli/s2_mask.py,sha256=1K5BiyTKyClGBdh6-Vh_ORTcgJOr6R5JfXtpEpDEQvk,2243
25
+ mapchete_eo/cli/s2_mgrs.py,sha256=EJxbrg4I8IttWIcLUEmmqkjsTUbVLufgrkDVrRbbkYE,1276
26
+ mapchete_eo/cli/s2_rgb.py,sha256=nzK5m4s0m2VOXgtit0uUOOhNJ14wquikWmbEQON5WJw,3747
27
+ mapchete_eo/cli/s2_verify.py,sha256=atfJA5luHZjDSqiM2udaufmnQ2B4uW2Tur_OgtYLa2g,4080
28
+ mapchete_eo/cli/static_catalog.py,sha256=O7oCJB6mxWwnEMowPBo6UMtWOu52mSNUBB9tpSw91a4,3005
29
+ mapchete_eo/image_operations/__init__.py,sha256=c36Vs53mT5UQR9Iwd2Ln_vnT4K_Ni8g-K7J9Fl6UsJo,432
30
+ mapchete_eo/image_operations/blend_functions.py,sha256=YaKLOEapMbsUR-_mkBatXwPcmim8GKpIAVua_GOdaHs,24131
31
+ mapchete_eo/image_operations/color_correction.py,sha256=vMXxcdyH3qOJDSKcXGU3FgXWIcgQxMy-QVe2fGQTbdk,4447
32
+ mapchete_eo/image_operations/compositing.py,sha256=K0bGCZqJuS39IBsjrRiJz5zowItI6RsiA-EBUVlbboo,8449
33
+ mapchete_eo/image_operations/dtype_scale.py,sha256=_CCdTBkMLDkZdjV_PsVEkqLWT6jWuTJPeMzCcRwBOns,1243
34
+ mapchete_eo/image_operations/fillnodata.py,sha256=Xdd-kacfo7VKqX7HRP0hqnnvV5OwNqnHLRTW64QhG24,4856
35
+ mapchete_eo/image_operations/filters.py,sha256=sa_Igv0zMIANHLEALVi8mxohZMoSrdQc6XVjXDYwzy8,7201
36
+ mapchete_eo/image_operations/linear_normalization.py,sha256=-eQX3WLCUYWUv-um3s1u33rgjAwCz84Ht8gcPsMWtJE,2468
37
+ mapchete_eo/image_operations/sigmoidal.py,sha256=IKU8F89HhQJWGUVmIrnkuhqzn_ztlGrTf8xXZaVQWzU,3575
38
+ mapchete_eo/io/__init__.py,sha256=1-1g4cESZZREvyumEUABZhDwgVuSxtxdqbNlLuVKlow,950
39
+ mapchete_eo/io/assets.py,sha256=E7phBOu8JaqyRhuEwl7wnmWHvG1yY1sw38QaMCuYkB4,16998
40
+ mapchete_eo/io/items.py,sha256=5H2xWAyLptDuuHh0rjY1MXReJomV0XBZgMr_rpdpYXU,6244
41
+ mapchete_eo/io/levelled_cubes.py,sha256=ZF7BLn9MHnJCCDjAoR9D7MNbHdusjJ9EACdM7rKNlyM,9018
42
+ mapchete_eo/io/path.py,sha256=fCjvowd9rGJbGio5Z-jDLxa49M2BVurYM9hJy53Im48,4831
43
+ mapchete_eo/io/products.py,sha256=2gELjHoEe4QH-Yjk82tA2nZUnBgfPbaGvk5GMHUWs4w,14642
44
+ mapchete_eo/io/profiles.py,sha256=l9YiXbKYs674xz6NLy5EAq3fBEvHTVSf_gopXD-CuSY,935
45
+ mapchete_eo/platforms/sentinel2/__init__.py,sha256=dviB4f5k45t_iOScTtnPc1kaWcohWKRq-8RaQzdggmU,401
46
+ mapchete_eo/platforms/sentinel2/_mapper_registry.py,sha256=3kuyW2g_xJoFUzZrAmhKn9ASHl4gBKuOlHsSZnpt21w,2746
47
+ mapchete_eo/platforms/sentinel2/bandpass_adjustment.py,sha256=DA0cQtjr8UH7r_kizAD-EmBsZvEtVVThf0hD0SCY7b4,3202
48
+ mapchete_eo/platforms/sentinel2/config.py,sha256=ifmzxDEuOFfoGAvbGyWyUWwF3Dw0NxEUEA3bWNByNR0,8482
49
+ mapchete_eo/platforms/sentinel2/driver.py,sha256=hNc9jmLQMmnA2ciVBPSTmVW6H8clL6sydkxcy6BjO40,1166
50
+ mapchete_eo/platforms/sentinel2/masks.py,sha256=6ig8sQhXkm1u6Bwbe7n8ewW8gTdbVJp-iaDCk780Qxg,11220
51
+ mapchete_eo/platforms/sentinel2/preprocessing_tasks.py,sha256=DJhbvYhNDGZ2Er5polK2LXWcg-MASZMFparNOw56yDk,1645
52
+ mapchete_eo/platforms/sentinel2/processing_baseline.py,sha256=uTE7zgzgi_W3NENLIAnJbWjyUD5_znpGa3cV-VoaEws,5179
53
+ mapchete_eo/platforms/sentinel2/product.py,sha256=UUbngHI2qPri5GkMPdmaw-ZS0y6aIQUtgZ5oZXUcIJc,28095
54
+ mapchete_eo/platforms/sentinel2/source.py,sha256=qSziCNTpInGy2LWIMiC3M4EP9G9_VtbCRNXB1XAO1P4,4269
55
+ mapchete_eo/platforms/sentinel2/types.py,sha256=EV7TleKZnCNuDlEMJ0UtJjDsvOUGbvh_pZToeOULQ18,2122
56
+ mapchete_eo/platforms/sentinel2/brdf/__init__.py,sha256=W7zAJZ5F5Xla7j4ua2opRANUbcD3jZbhTwhW5_cpOUI,242
57
+ mapchete_eo/platforms/sentinel2/brdf/config.py,sha256=v3WCEu4r-tEPQgWBEkYNAPLgCQobvYtQ2YiDeAdImMk,1133
58
+ mapchete_eo/platforms/sentinel2/brdf/correction.py,sha256=vi-BwAA6Cbayzk6WOuOESLhRkGLhiUAbVYfJUSfYhXA,8651
59
+ mapchete_eo/platforms/sentinel2/brdf/hls.py,sha256=pzrRTNfJhuqRLidLQvKJpSnaz816x7jUesXt_aRgFjc,8818
60
+ mapchete_eo/platforms/sentinel2/brdf/models.py,sha256=BBwGX1OLMRLncK1jD2reidaF82IKR4sjgNGHdTgBXaw,1446
61
+ mapchete_eo/platforms/sentinel2/brdf/protocols.py,sha256=fxLDFeaL44eIPC2UwcbEQ-AXPREWgBv6Iytc74AQ5eA,752
62
+ mapchete_eo/platforms/sentinel2/brdf/ross_thick.py,sha256=OvuuQxqWN8qrKpz8kDhmCd8R1uMLa8h5LISJSJ_NvYk,4685
63
+ mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py,sha256=RxhQ8vBU0r5vsQ3FnyfDdYS0EGDGKCwAXND0Gn1m5mQ,2148
64
+ mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py,sha256=Vio87daAQ1eBQfDfA97c9byVnAUEPCidjTBg6jnZhXk,167
65
+ mapchete_eo/platforms/sentinel2/metadata_parser/base.py,sha256=AvPfsQEAyXOrwOQ_MQgWLUFFJL0VhTzzWHpuSe14IS0,1520
66
+ mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py,sha256=Gt9jUZwO3u_p6iH3hkOGNS5Tad5yn-Y1F7uADtalBSo,5344
67
+ mapchete_eo/platforms/sentinel2/metadata_parser/models.py,sha256=FOdEeIKk_Sa_O3iGRqoR-5YLQuwjPG6pNbdFRgQVYm8,2325
68
+ mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py,sha256=i6O9t0Bbb5BCiktKmXgDBKWdzdtVoGJ-Nt-xib-w3qw,23786
69
+ mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py,sha256=W-PFNuC0zXfF07uTUH_-r2Macv-_5qlz_YsP04Jxh8Y,1821
70
+ mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py,sha256=lkftO9NCJqwV663K3YqUFkNQxQWrCuwp_enbfTkjdbQ,3746
71
+ mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py,sha256=b_lvs6-rKQjb-8vBjOLV0Zi4a2ZXZxMqinyeRljA5lY,5957
72
+ mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py,sha256=8UbF5Riox1FZIYGuDMBiGgxNfAQkOmu07ZMc2Eqiel0,8647
73
+ mapchete_eo/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
+ mapchete_eo/processes/config.py,sha256=WorMzHJzJaZPh-aEyEEwcXN08ALxn6zAhVMrjkDUWDU,1745
75
+ mapchete_eo/processes/dtype_scale.py,sha256=7hJlIe2DsE6Kmk1c3PLwLr8rnn_4_9S6Cz7bmRuzP9M,4176
76
+ mapchete_eo/processes/eo_to_xarray.py,sha256=Gcp4qju2C9S8KeUnVY5f3nAsrdckPhGRguzsgRtWBFs,514
77
+ mapchete_eo/processes/merge_rasters.py,sha256=jSXUI8pEWXbQWrM9iMtrH4ACH2VkCaBUH9CQg4z6zLA,8486
78
+ mapchete_eo/search/__init__.py,sha256=bHvaqoHrtoNyO8_3kyJfvhh1yCz0hYvU2fzXhjOXNhU,528
79
+ mapchete_eo/search/base.py,sha256=Xdhq7js96erhV5qgAeKviUakF4qJmzUkbvprrTCP6Zc,9107
80
+ mapchete_eo/search/config.py,sha256=-3TzCBzjTIkCVpuoJMDDShHh4rm_9_QFKYruedEwbjY,2241
81
+ mapchete_eo/search/s2_mgrs.py,sha256=x12m-tJZNc41lFHNR2h7eSyEZ-ceBO4Gdz785btcVN8,10859
82
+ mapchete_eo/search/stac_search.py,sha256=p38veJiUZtEzXq7KJJ0YzqeEG1L87Q3CLdPIME1RQ3A,10504
83
+ mapchete_eo/search/stac_static.py,sha256=nPvJPayImPHkjVr4XEDp7ucOOhbDpUTlGyMJeaRFmAI,7260
84
+ mapchete_eo/search/utm_search.py,sha256=6T9HtbgT8mrWjzX_7_fkggmQBCWDBhf5i7BJ9dk6BZw,9230
85
+ mapchete_eo-2025.11.0.dist-info/METADATA,sha256=TkqXdAgVkEPhNiAbMS4VSXUwuhKGnITHET5MyvgGwDQ,3257
86
+ mapchete_eo-2025.11.0.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
87
+ mapchete_eo-2025.11.0.dist-info/entry_points.txt,sha256=edU29CA3j8hwBafEd6jO2Zcou1sGkyjNro-y4_LnAnw,320
88
+ mapchete_eo-2025.11.0.dist-info/licenses/LICENSE,sha256=TC5JwvBnFrUgsSQSCDFPc3cqlbth2N0q8MWrhY1EVd0,1089
89
+ mapchete_eo-2025.11.0.dist-info/RECORD,,
@@ -2,7 +2,7 @@
2
2
  eo = mapchete_eo.cli:eo
3
3
 
4
4
  [mapchete.formats.drivers]
5
- eostac_dev = mapchete_eo.eostac
5
+ eostac = mapchete_eo.eostac
6
6
  sentinel2 = mapchete_eo.platforms.sentinel2
7
7
 
8
8
  [mapchete.processes]
File without changes
@@ -1,65 +0,0 @@
1
- from abc import ABC
2
- import logging
3
- from typing import Any, Callable, Dict, Generator, List, Optional, Union
4
-
5
- from mapchete.io.vector import IndexedFeatures
6
- from mapchete.types import Bounds
7
- from pystac import Item
8
- from shapely.errors import GEOSException
9
- from shapely.geometry.base import BaseGeometry
10
-
11
- from mapchete_eo.exceptions import ItemGeometryError
12
- from mapchete_eo.search.base import CatalogSearcher
13
- from mapchete_eo.types import TimeRange
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class Archive(ABC):
19
- """
20
- An archive combines a Catalog and a Storage.
21
- """
22
-
23
- time: Union[TimeRange, List[TimeRange]]
24
- area: BaseGeometry
25
- catalog: CatalogSearcher
26
- search_kwargs: Dict[str, Any]
27
- _items: Optional[IndexedFeatures] = None
28
- item_modifier_funcs: Optional[List[Callable[[Item], Item]]] = None
29
-
30
- def __init__(
31
- self,
32
- time: Union[TimeRange, List[TimeRange]],
33
- bounds: Optional[Bounds] = None,
34
- area: Optional[BaseGeometry] = None,
35
- search_kwargs: Optional[Dict[str, Any]] = None,
36
- catalog: Optional[CatalogSearcher] = None,
37
- ):
38
- if bounds is None and area is None:
39
- raise ValueError("either bounds or area have to be provided")
40
- elif area is None:
41
- area = Bounds.from_inp(bounds).geometry
42
- self.time = time
43
- self.area = area
44
- self.search_kwargs = search_kwargs or {}
45
- if catalog:
46
- self.catalog = catalog
47
-
48
- def get_catalog_config(self):
49
- return self.catalog.config_cls(**self.search_kwargs)
50
-
51
- def apply_item_modifier_funcs(self, item: Item) -> Item:
52
- try:
53
- for modifier in self.item_modifier_funcs or []:
54
- item = modifier(item)
55
- except GEOSException as exc:
56
- raise ItemGeometryError(
57
- f"item {item.get_self_href()} geometry could not be resolved: {str(exc)}"
58
- )
59
- return item
60
-
61
- def items(self) -> Generator[Item, None, None]:
62
- for item in self.catalog.search(
63
- time=self.time, area=self.area, search_kwargs=self.search_kwargs
64
- ):
65
- yield self.apply_item_modifier_funcs(item)