mapchete-eo 2025.10.0__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.
- mapchete_eo/__init__.py +1 -1
- mapchete_eo/array/convert.py +7 -1
- mapchete_eo/base.py +123 -55
- mapchete_eo/cli/options_arguments.py +11 -27
- mapchete_eo/cli/s2_brdf.py +1 -1
- mapchete_eo/cli/s2_cat_results.py +4 -20
- mapchete_eo/cli/s2_find_broken_products.py +4 -20
- mapchete_eo/cli/s2_jp2_static_catalog.py +2 -2
- mapchete_eo/cli/static_catalog.py +4 -45
- mapchete_eo/eostac.py +1 -1
- mapchete_eo/io/assets.py +7 -7
- mapchete_eo/io/items.py +37 -22
- mapchete_eo/io/levelled_cubes.py +66 -35
- mapchete_eo/io/path.py +19 -8
- mapchete_eo/io/products.py +37 -27
- mapchete_eo/platforms/sentinel2/__init__.py +1 -1
- mapchete_eo/platforms/sentinel2/_mapper_registry.py +89 -0
- mapchete_eo/platforms/sentinel2/brdf/correction.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/hls.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/models.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/protocols.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +1 -1
- mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +1 -1
- mapchete_eo/platforms/sentinel2/config.py +73 -13
- mapchete_eo/platforms/sentinel2/driver.py +0 -39
- mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +6 -0
- mapchete_eo/platforms/sentinel2/{path_mappers → metadata_parser}/base.py +1 -1
- mapchete_eo/platforms/sentinel2/{path_mappers/metadata_xml.py → metadata_parser/default_path_mapper.py} +2 -2
- mapchete_eo/platforms/sentinel2/metadata_parser/models.py +78 -0
- mapchete_eo/platforms/sentinel2/{metadata_parser.py → metadata_parser/s2metadata.py} +51 -146
- mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +57 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +108 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +171 -0
- mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +217 -0
- mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +22 -1
- mapchete_eo/platforms/sentinel2/processing_baseline.py +3 -0
- mapchete_eo/platforms/sentinel2/product.py +88 -23
- mapchete_eo/platforms/sentinel2/source.py +114 -0
- mapchete_eo/platforms/sentinel2/types.py +5 -0
- mapchete_eo/processes/merge_rasters.py +7 -3
- mapchete_eo/product.py +14 -9
- mapchete_eo/protocols.py +5 -0
- mapchete_eo/search/__init__.py +3 -3
- mapchete_eo/search/base.py +126 -100
- mapchete_eo/search/config.py +25 -4
- mapchete_eo/search/s2_mgrs.py +8 -9
- mapchete_eo/search/stac_search.py +111 -75
- mapchete_eo/search/stac_static.py +63 -94
- mapchete_eo/search/utm_search.py +39 -48
- mapchete_eo/settings.py +1 -0
- mapchete_eo/sort.py +16 -2
- mapchete_eo/source.py +107 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/METADATA +2 -1
- mapchete_eo-2025.11.0.dist-info/RECORD +89 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/entry_points.txt +1 -1
- mapchete_eo/archives/__init__.py +0 -0
- mapchete_eo/archives/base.py +0 -65
- mapchete_eo/geometry.py +0 -271
- mapchete_eo/known_catalogs.py +0 -42
- mapchete_eo/platforms/sentinel2/archives.py +0 -190
- mapchete_eo/platforms/sentinel2/path_mappers/__init__.py +0 -29
- mapchete_eo/platforms/sentinel2/path_mappers/earthsearch.py +0 -34
- mapchete_eo/platforms/sentinel2/path_mappers/sinergise.py +0 -105
- mapchete_eo-2025.10.0.dist-info/RECORD +0 -88
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/WHEEL +0 -0
- {mapchete_eo-2025.10.0.dist-info → mapchete_eo-2025.11.0.dist-info}/licenses/LICENSE +0 -0
mapchete_eo/product.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any, List, Literal, Optional, Set
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import numpy.ma as ma
|
|
8
|
-
import
|
|
8
|
+
from pystac import Item
|
|
9
9
|
import xarray as xr
|
|
10
10
|
from mapchete import Timer
|
|
11
11
|
from mapchete.io.raster import ReferencedRaster
|
|
@@ -26,15 +26,19 @@ logger = logging.getLogger(__name__)
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class EOProduct(EOProductProtocol):
|
|
29
|
-
"""Wrapper class around a
|
|
29
|
+
"""Wrapper class around a Item which provides read functions."""
|
|
30
30
|
|
|
31
|
+
id: str
|
|
31
32
|
default_dtype: DTypeLike = np.uint16
|
|
33
|
+
_item: Optional[Item] = None
|
|
32
34
|
|
|
33
|
-
def __init__(self, item:
|
|
35
|
+
def __init__(self, item: Item):
|
|
34
36
|
self.item_dict = item.to_dict()
|
|
35
37
|
self.__geo_interface__ = self.item.geometry
|
|
36
38
|
self.bounds = Bounds.from_inp(shape(self))
|
|
37
39
|
self.crs = mapchete_eo_settings.default_catalog_crs
|
|
40
|
+
self._item = None
|
|
41
|
+
self.id = item.id
|
|
38
42
|
|
|
39
43
|
def __repr__(self):
|
|
40
44
|
return f"<EOProduct product_id={self.item.id}>"
|
|
@@ -43,11 +47,13 @@ class EOProduct(EOProductProtocol):
|
|
|
43
47
|
pass
|
|
44
48
|
|
|
45
49
|
@property
|
|
46
|
-
def item(self) ->
|
|
47
|
-
|
|
50
|
+
def item(self) -> Item:
|
|
51
|
+
if not self._item:
|
|
52
|
+
self._item = Item.from_dict(self.item_dict)
|
|
53
|
+
return self._item
|
|
48
54
|
|
|
49
55
|
@classmethod
|
|
50
|
-
def from_stac_item(self, item:
|
|
56
|
+
def from_stac_item(self, item: Item, **kwargs) -> EOProduct:
|
|
51
57
|
return EOProduct(item)
|
|
52
58
|
|
|
53
59
|
def get_mask(self) -> ReferencedRaster: ...
|
|
@@ -113,7 +119,6 @@ class EOProduct(EOProductProtocol):
|
|
|
113
119
|
nodatavals: NodataVals = None,
|
|
114
120
|
raise_empty: bool = True,
|
|
115
121
|
apply_offset: bool = True,
|
|
116
|
-
apply_scale: bool = False,
|
|
117
122
|
**kwargs,
|
|
118
123
|
) -> ma.MaskedArray:
|
|
119
124
|
assets = assets or []
|
|
@@ -172,7 +177,7 @@ class EOProduct(EOProductProtocol):
|
|
|
172
177
|
|
|
173
178
|
|
|
174
179
|
def eo_bands_to_band_locations(
|
|
175
|
-
item:
|
|
180
|
+
item: Item,
|
|
176
181
|
eo_bands: List[str],
|
|
177
182
|
role: Literal["data", "reflectance", "visual"] = "data",
|
|
178
183
|
) -> List[BandLocation]:
|
|
@@ -183,7 +188,7 @@ def eo_bands_to_band_locations(
|
|
|
183
188
|
|
|
184
189
|
|
|
185
190
|
def find_eo_band(
|
|
186
|
-
item:
|
|
191
|
+
item: Item,
|
|
187
192
|
eo_band_name: str,
|
|
188
193
|
role: Literal["data", "reflectance", "visual"] = "data",
|
|
189
194
|
) -> BandLocation:
|
mapchete_eo/protocols.py
CHANGED
|
@@ -15,6 +15,7 @@ from mapchete.io.raster import ReferencedRaster
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class EOProductProtocol(Protocol):
|
|
18
|
+
id: str
|
|
18
19
|
bounds: Bounds
|
|
19
20
|
crs: CRS
|
|
20
21
|
__geo_interface__: Optional[Dict[str, Any]]
|
|
@@ -54,3 +55,7 @@ class EOProductProtocol(Protocol):
|
|
|
54
55
|
|
|
55
56
|
class DateTimeProtocol(Protocol):
|
|
56
57
|
datetime: DateTimeLike
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GetPropertyProtocol(Protocol):
|
|
61
|
+
def get_property(self, property: str) -> Any: ...
|
mapchete_eo/search/__init__.py
CHANGED
|
@@ -7,8 +7,8 @@ of product metadata.
|
|
|
7
7
|
It helps the InputData class to find the input products and their metadata.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from mapchete_eo.search.stac_search import
|
|
11
|
-
from mapchete_eo.search.stac_static import
|
|
10
|
+
from mapchete_eo.search.stac_search import STACSearchCollection
|
|
11
|
+
from mapchete_eo.search.stac_static import STACStaticCollection
|
|
12
12
|
from mapchete_eo.search.utm_search import UTMSearchCatalog
|
|
13
13
|
|
|
14
|
-
__all__ = ["
|
|
14
|
+
__all__ = ["STACSearchCollection", "STACStaticCollection", "UTMSearchCatalog"]
|
mapchete_eo/search/base.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
from functools import cached_property
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from typing import Any, Callable, Dict, Generator, List, Optional, Type, Union
|
|
5
6
|
|
|
7
|
+
from cql2 import Expr
|
|
6
8
|
from pydantic import BaseModel
|
|
7
|
-
from pystac import Item, Catalog, CatalogType, Extent
|
|
8
9
|
from mapchete.path import MPath, MPathLike
|
|
9
10
|
from mapchete.types import Bounds
|
|
11
|
+
from pystac import Catalog, Item, CatalogType, Extent
|
|
10
12
|
from pystac.collection import Collection
|
|
11
13
|
from pystac.stac_io import DefaultStacIO
|
|
12
|
-
from pystac_client import
|
|
14
|
+
from pystac_client import CollectionClient
|
|
13
15
|
from pystac_client.stac_api_io import StacApiIO
|
|
14
16
|
from rasterio.profiles import Profile
|
|
15
17
|
from shapely.geometry.base import BaseGeometry
|
|
@@ -43,17 +45,42 @@ class FSSpecStacIO(StacApiIO):
|
|
|
43
45
|
return dst.write(json.dumps(json_dict, indent=2))
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
class
|
|
48
|
+
class CollectionSearcher(ABC):
|
|
47
49
|
"""
|
|
48
50
|
This class serves as a bridge between an Archive and a catalog implementation.
|
|
49
51
|
"""
|
|
50
52
|
|
|
51
|
-
eo_bands: List[str]
|
|
52
|
-
id: str
|
|
53
|
-
description: str
|
|
54
|
-
stac_extensions: List[str]
|
|
55
|
-
collections: List[str]
|
|
56
53
|
config_cls: Type[BaseModel]
|
|
54
|
+
collection: str
|
|
55
|
+
stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
collection: str,
|
|
60
|
+
stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None,
|
|
61
|
+
):
|
|
62
|
+
self.collection = collection
|
|
63
|
+
self.stac_item_modifiers = stac_item_modifiers
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
@cached_property
|
|
67
|
+
def client(self) -> CollectionClient: ...
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
@cached_property
|
|
71
|
+
def eo_bands(self) -> List[str]: ...
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
@cached_property
|
|
75
|
+
def id(self) -> str: ...
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
@cached_property
|
|
79
|
+
def description(self) -> str: ...
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
@cached_property
|
|
83
|
+
def stac_extensions(self) -> List[str]: ...
|
|
57
84
|
|
|
58
85
|
@abstractmethod
|
|
59
86
|
def search(
|
|
@@ -61,19 +88,16 @@ class CatalogSearcher(ABC):
|
|
|
61
88
|
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
|
|
62
89
|
bounds: Optional[Bounds] = None,
|
|
63
90
|
area: Optional[BaseGeometry] = None,
|
|
91
|
+
query: Optional[str] = None,
|
|
64
92
|
search_kwargs: Optional[Dict[str, Any]] = None,
|
|
65
93
|
) -> Generator[Item, None, None]: ...
|
|
66
94
|
|
|
67
95
|
|
|
68
|
-
class
|
|
69
|
-
client: Client
|
|
70
|
-
id: str
|
|
71
|
-
description: str
|
|
72
|
-
stac_extensions: List[str]
|
|
73
|
-
|
|
74
|
-
@abstractmethod
|
|
75
|
-
def get_collections(self) -> List[Collection]: # pragma: no cover
|
|
76
|
-
...
|
|
96
|
+
class StaticCollectionWriterMixin(CollectionSearcher):
|
|
97
|
+
# client: Client
|
|
98
|
+
# id: str
|
|
99
|
+
# description: str
|
|
100
|
+
# stac_extensions: List[str]
|
|
77
101
|
|
|
78
102
|
def write_static_catalog(
|
|
79
103
|
self,
|
|
@@ -100,94 +124,93 @@ class StaticCatalogWriterMixin(CatalogSearcher):
|
|
|
100
124
|
catalog_json = output_path / "catalog.json"
|
|
101
125
|
if catalog_json.exists():
|
|
102
126
|
logger.debug("open existing catalog %s", str(catalog_json))
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
|
|
127
|
+
catalog = Catalog.from_file(catalog_json)
|
|
128
|
+
# client = Client.from_file(catalog_json)
|
|
129
|
+
# existing_collection = client.get_collection(self.id)
|
|
106
130
|
else:
|
|
107
|
-
existing_collections = []
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
# existing_collections = []
|
|
132
|
+
catalog = Catalog(
|
|
133
|
+
name or f"{self.id}",
|
|
134
|
+
description or f"Static subset of {self.description}",
|
|
135
|
+
stac_extensions=self.stac_extensions,
|
|
136
|
+
href=str(catalog_json),
|
|
137
|
+
catalog_type=CatalogType.SELF_CONTAINED,
|
|
138
|
+
)
|
|
115
139
|
src_items = list(
|
|
116
140
|
self.search(
|
|
117
141
|
time=time, bounds=bounds, area=area, search_kwargs=search_kwargs
|
|
118
142
|
)
|
|
119
143
|
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
item
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
item
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
break
|
|
144
|
+
# collect all items and download assets if required
|
|
145
|
+
items: List[Item] = []
|
|
146
|
+
item_ids = set()
|
|
147
|
+
for n, item in enumerate(src_items, 1):
|
|
148
|
+
logger.debug("found item %s", item)
|
|
149
|
+
item = item.clone()
|
|
150
|
+
if assets:
|
|
151
|
+
logger.debug("get assets %s", assets)
|
|
152
|
+
item = get_assets(
|
|
153
|
+
item,
|
|
154
|
+
assets,
|
|
155
|
+
output_path / self.id / item.id,
|
|
156
|
+
resolution=assets_dst_resolution,
|
|
157
|
+
convert_profile=assets_convert_profile,
|
|
158
|
+
overwrite=overwrite,
|
|
159
|
+
ignore_if_exists=True,
|
|
160
|
+
)
|
|
161
|
+
if copy_metadata:
|
|
162
|
+
item = get_metadata_assets(
|
|
163
|
+
item,
|
|
164
|
+
output_path / self.id / item.id,
|
|
165
|
+
metadata_parser_classes=metadata_parser_classes,
|
|
166
|
+
resolution=assets_dst_resolution,
|
|
167
|
+
convert_profile=assets_convert_profile,
|
|
168
|
+
overwrite=overwrite,
|
|
169
|
+
)
|
|
170
|
+
# this has to be set to None, otherwise pystac will mess up the asset paths
|
|
171
|
+
# after normalizing
|
|
172
|
+
item.set_self_href(None)
|
|
173
|
+
|
|
174
|
+
items.append(item)
|
|
175
|
+
item_ids.add(item.id)
|
|
176
|
+
|
|
177
|
+
if progress_callback:
|
|
178
|
+
progress_callback(n=n, total=len(src_items))
|
|
179
|
+
|
|
180
|
+
# for existing_collection in existing_collections:
|
|
181
|
+
# if existing_collection.id == collection.id:
|
|
182
|
+
# logger.debug("try to find unregistered items in collection")
|
|
183
|
+
# collection_root_path = MPath.from_inp(
|
|
184
|
+
# existing_collection.get_self_href()
|
|
185
|
+
# ).parent
|
|
186
|
+
# for subpath in collection_root_path.ls():
|
|
187
|
+
# if subpath.is_directory():
|
|
188
|
+
# try:
|
|
189
|
+
# item = Item.from_file(
|
|
190
|
+
# subpath / subpath.with_suffix(".json").name
|
|
191
|
+
# )
|
|
192
|
+
# if item.id not in item_ids:
|
|
193
|
+
# logger.debug(
|
|
194
|
+
# "add existing item with id %s", item.id
|
|
195
|
+
# )
|
|
196
|
+
# items.append(item)
|
|
197
|
+
# item_ids.add(item.id)
|
|
198
|
+
# except FileNotFoundError:
|
|
199
|
+
# pass
|
|
200
|
+
# break
|
|
178
201
|
# create collection and copy metadata
|
|
179
202
|
logger.debug("create new collection")
|
|
180
203
|
out_collection = Collection(
|
|
181
|
-
id=
|
|
204
|
+
id=self.id,
|
|
182
205
|
extent=Extent.from_items(items),
|
|
183
|
-
description=
|
|
184
|
-
title=
|
|
185
|
-
stac_extensions=
|
|
186
|
-
license=
|
|
187
|
-
keywords=
|
|
188
|
-
providers=
|
|
189
|
-
summaries=
|
|
190
|
-
extra_fields=
|
|
206
|
+
description=self.description,
|
|
207
|
+
title=self.client.title,
|
|
208
|
+
stac_extensions=self.stac_extensions,
|
|
209
|
+
license=self.client.license,
|
|
210
|
+
keywords=self.client.keywords,
|
|
211
|
+
providers=self.client.providers,
|
|
212
|
+
summaries=self.client.summaries,
|
|
213
|
+
extra_fields=self.client.extra_fields,
|
|
191
214
|
catalog_type=CatalogType.SELF_CONTAINED,
|
|
192
215
|
)
|
|
193
216
|
|
|
@@ -209,14 +232,17 @@ class StaticCatalogWriterMixin(CatalogSearcher):
|
|
|
209
232
|
|
|
210
233
|
def filter_items(
|
|
211
234
|
items: Generator[Item, None, None],
|
|
212
|
-
|
|
213
|
-
max_cloud_cover: float = 100.0,
|
|
235
|
+
query: Optional[str] = None,
|
|
214
236
|
) -> Generator[Item, None, None]:
|
|
215
237
|
"""
|
|
216
238
|
Only for cloudcover now, this can and should be adapted for filter field and value
|
|
217
239
|
the field and value for the item filter would be defined in search.config.py corresponding configs
|
|
218
240
|
and passed down to the individual search approaches via said config and this Function.
|
|
219
241
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
242
|
+
if query:
|
|
243
|
+
expr = Expr(query)
|
|
244
|
+
for item in items:
|
|
245
|
+
if expr.matches(item.properties):
|
|
246
|
+
yield item
|
|
247
|
+
else:
|
|
248
|
+
yield from items
|
mapchete_eo/search/config.py
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
2
|
|
|
3
3
|
from mapchete.path import MPath, MPathLike
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, model_validator
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class StacSearchConfig(BaseModel):
|
|
8
8
|
max_cloud_cover: float = 100.0
|
|
9
|
+
query: Optional[str] = None
|
|
9
10
|
catalog_chunk_threshold: int = 10_000
|
|
10
11
|
catalog_chunk_zoom: int = 5
|
|
11
12
|
catalog_pagesize: int = 100
|
|
12
13
|
footprint_buffer: float = 0
|
|
13
14
|
|
|
15
|
+
@model_validator(mode="before")
|
|
16
|
+
def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
17
|
+
if "max_cloud_cover" in values: # pragma: no cover
|
|
18
|
+
raise DeprecationWarning(
|
|
19
|
+
"'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
|
|
20
|
+
)
|
|
21
|
+
return values
|
|
22
|
+
|
|
14
23
|
|
|
15
24
|
class StacStaticConfig(BaseModel):
|
|
16
|
-
|
|
25
|
+
@model_validator(mode="before")
|
|
26
|
+
def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
27
|
+
if "max_cloud_cover" in values: # pragma: no cover
|
|
28
|
+
raise DeprecationWarning(
|
|
29
|
+
"'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
|
|
30
|
+
)
|
|
31
|
+
return values
|
|
17
32
|
|
|
18
33
|
|
|
19
34
|
class UTMSearchConfig(BaseModel):
|
|
20
|
-
|
|
35
|
+
@model_validator(mode="before")
|
|
36
|
+
def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
if "max_cloud_cover" in values: # pragma: no cover
|
|
38
|
+
raise DeprecationWarning(
|
|
39
|
+
"'max_cloud_cover' will be deprecated soon. Please use 'eo:cloud_cover<=...' in the source 'query' field.",
|
|
40
|
+
)
|
|
41
|
+
return values
|
|
21
42
|
|
|
22
43
|
sinergise_aws_collections: dict = dict(
|
|
23
44
|
S2_L2A=dict(
|
mapchete_eo/search/s2_mgrs.py
CHANGED
|
@@ -6,18 +6,17 @@ from functools import cached_property
|
|
|
6
6
|
from itertools import product
|
|
7
7
|
from typing import List, Literal, Optional, Tuple, Union
|
|
8
8
|
|
|
9
|
-
from mapchete.geometry import
|
|
9
|
+
from mapchete.geometry import (
|
|
10
|
+
reproject_geometry,
|
|
11
|
+
repair_antimeridian_geometry,
|
|
12
|
+
transform_to_latlon,
|
|
13
|
+
)
|
|
10
14
|
from mapchete.types import Bounds
|
|
11
15
|
from rasterio.crs import CRS
|
|
12
16
|
from shapely import prepare
|
|
13
17
|
from shapely.geometry import box, mapping, shape
|
|
14
18
|
from shapely.geometry.base import BaseGeometry
|
|
15
19
|
|
|
16
|
-
from mapchete_eo.geometry import (
|
|
17
|
-
bounds_to_geom,
|
|
18
|
-
repair_antimeridian_geometry,
|
|
19
|
-
transform_to_latlon,
|
|
20
|
-
)
|
|
21
20
|
|
|
22
21
|
LATLON_LEFT = -180
|
|
23
22
|
LATLON_RIGHT = 180
|
|
@@ -255,7 +254,7 @@ class S2Tile:
|
|
|
255
254
|
grid_square = tile_id[3:]
|
|
256
255
|
try:
|
|
257
256
|
int(utm_zone)
|
|
258
|
-
except Exception:
|
|
257
|
+
except Exception: # pragma: no cover
|
|
259
258
|
raise ValueError(f"invalid UTM zone given: {utm_zone}")
|
|
260
259
|
|
|
261
260
|
return MGRSCell(utm_zone, latitude_band).tile(grid_square)
|
|
@@ -268,7 +267,7 @@ class S2Tile:
|
|
|
268
267
|
def s2_tiles_from_bounds(
|
|
269
268
|
left: float, bottom: float, right: float, top: float
|
|
270
269
|
) -> List[S2Tile]:
|
|
271
|
-
bounds = Bounds(left, bottom, right, top)
|
|
270
|
+
bounds = Bounds(left, bottom, right, top, crs="EPSG:4326")
|
|
272
271
|
|
|
273
272
|
# determine zones in eastern-western direction
|
|
274
273
|
min_zone_idx = math.floor((left + LATLON_WIDTH_OFFSET) / UTM_ZONE_WIDTH)
|
|
@@ -291,7 +290,7 @@ def s2_tiles_from_bounds(
|
|
|
291
290
|
min_latitude_band_idx -= 1
|
|
292
291
|
max_latitude_band_idx += 1
|
|
293
292
|
|
|
294
|
-
aoi =
|
|
293
|
+
aoi = bounds.latlon_geometry()
|
|
295
294
|
prepare(aoi)
|
|
296
295
|
|
|
297
296
|
def tiles_generator():
|