mapchete-eo 2025.10.1__py2.py3-none-any.whl → 2026.1.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/base.py +94 -54
- 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 +20 -16
- mapchete_eo/io/items.py +36 -23
- mapchete_eo/io/path.py +19 -8
- mapchete_eo/io/products.py +22 -24
- 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 -144
- 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 +83 -18
- mapchete_eo/platforms/sentinel2/source.py +114 -0
- mapchete_eo/platforms/sentinel2/types.py +5 -0
- mapchete_eo/product.py +14 -8
- mapchete_eo/protocols.py +5 -0
- mapchete_eo/search/__init__.py +3 -3
- mapchete_eo/search/base.py +127 -99
- mapchete_eo/search/config.py +75 -4
- mapchete_eo/search/s2_mgrs.py +8 -9
- mapchete_eo/search/stac_search.py +99 -97
- mapchete_eo/search/stac_static.py +46 -102
- mapchete_eo/search/utm_search.py +54 -62
- mapchete_eo/settings.py +1 -0
- mapchete_eo/sort.py +4 -6
- mapchete_eo/source.py +107 -0
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2026.1.0.dist-info}/METADATA +4 -3
- mapchete_eo-2026.1.0.dist-info/RECORD +89 -0
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2026.1.0.dist-info}/WHEEL +1 -1
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2026.1.0.dist-info}/entry_points.txt +1 -1
- {mapchete_eo-2025.10.1.dist-info → mapchete_eo-2026.1.0.dist-info}/licenses/LICENSE +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.1.dist-info/RECORD +0 -88
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.
|
|
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[
|
|
55
|
-
) -> List[
|
|
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:
|
|
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:
|
|
3
|
+
Version: 2026.1.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>
|
|
@@ -21,6 +21,7 @@ Requires-Dist: mapchete[complete]>=2025.10.0
|
|
|
21
21
|
Requires-Dist: opencv-python-headless
|
|
22
22
|
Requires-Dist: pillow
|
|
23
23
|
Requires-Dist: pydantic
|
|
24
|
+
Requires-Dist: pygeofilter
|
|
24
25
|
Requires-Dist: pystac-client>=0.7.5
|
|
25
26
|
Requires-Dist: pystac[urllib3]>=1.12.2
|
|
26
27
|
Requires-Dist: retry
|
|
@@ -32,9 +33,9 @@ Provides-Extra: docs
|
|
|
32
33
|
Requires-Dist: sphinx; extra == 'docs'
|
|
33
34
|
Requires-Dist: sphinx-rtd-theme; extra == 'docs'
|
|
34
35
|
Provides-Extra: test
|
|
36
|
+
Requires-Dist: pytest; extra == 'test'
|
|
35
37
|
Requires-Dist: pytest-coverage; extra == 'test'
|
|
36
|
-
Requires-Dist: pytest-lazy-
|
|
37
|
-
Requires-Dist: pytest<8; extra == 'test'
|
|
38
|
+
Requires-Dist: pytest-lazy-fixtures; extra == 'test'
|
|
38
39
|
Description-Content-Type: text/x-rst
|
|
39
40
|
|
|
40
41
|
.. image:: logo/mapchete_eo.svg
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
mapchete_eo/__init__.py,sha256=Q2R77nEGEKjxOBz3qMm2JGtkZn-nVFmReBAVH_YeZ4M,25
|
|
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=OVXLxrwCCUa6frP-xpm2CL30MR8eMABnwVD6EpN1cqU,17215
|
|
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=B8vlwgvDYRqxHre5aYWfPZ0tmUic-mb1VBnzl0llUSQ,9757
|
|
80
|
+
mapchete_eo/search/config.py,sha256=L3DBEuKNGh_WXi8_Ha-qj6HW9ksRioYeFyPYGGN09qA,3696
|
|
81
|
+
mapchete_eo/search/s2_mgrs.py,sha256=x12m-tJZNc41lFHNR2h7eSyEZ-ceBO4Gdz785btcVN8,10859
|
|
82
|
+
mapchete_eo/search/stac_search.py,sha256=gWMSmDaXZTfH_GV1YiQ8ThbnJYsxHB2j_y_SgMX41Bg,10079
|
|
83
|
+
mapchete_eo/search/stac_static.py,sha256=e53nKu8QRmanGvPmrvIDavHXm8v_4aT5V-wvU8wnkiw,6979
|
|
84
|
+
mapchete_eo/search/utm_search.py,sha256=ZbNJCf461H_709Bpqzuwiu4_iwXC4M-wkemxXKmGenk,9489
|
|
85
|
+
mapchete_eo-2026.1.0.dist-info/METADATA,sha256=0qagoc1eCiRIjxn78e_jJIieQkjV018I_0ylh-1leQQ,3262
|
|
86
|
+
mapchete_eo-2026.1.0.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
|
|
87
|
+
mapchete_eo-2026.1.0.dist-info/entry_points.txt,sha256=edU29CA3j8hwBafEd6jO2Zcou1sGkyjNro-y4_LnAnw,320
|
|
88
|
+
mapchete_eo-2026.1.0.dist-info/licenses/LICENSE,sha256=gmZNf2EjyzaNbUyhfsBJlS0yQeG7GEmd-29miNXtEwA,1089
|
|
89
|
+
mapchete_eo-2026.1.0.dist-info/RECORD,,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022 -
|
|
3
|
+
Copyright (c) 2022 - 2026 EOX IT Services
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
mapchete_eo/archives/__init__.py
DELETED
|
File without changes
|
mapchete_eo/archives/base.py
DELETED
|
@@ -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)
|
mapchete_eo/geometry.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import math
|
|
3
|
-
from functools import partial
|
|
4
|
-
from typing import Callable, Iterable, Tuple
|
|
5
|
-
|
|
6
|
-
from fiona.crs import CRS
|
|
7
|
-
from fiona.transform import transform as fiona_transform
|
|
8
|
-
from mapchete.geometry import reproject_geometry
|
|
9
|
-
from mapchete.types import Bounds, CRSLike
|
|
10
|
-
from shapely.geometry import (
|
|
11
|
-
GeometryCollection,
|
|
12
|
-
LinearRing,
|
|
13
|
-
LineString,
|
|
14
|
-
MultiLineString,
|
|
15
|
-
MultiPoint,
|
|
16
|
-
MultiPolygon,
|
|
17
|
-
Point,
|
|
18
|
-
Polygon,
|
|
19
|
-
box,
|
|
20
|
-
shape,
|
|
21
|
-
)
|
|
22
|
-
from shapely.geometry.base import BaseGeometry
|
|
23
|
-
from shapely.ops import unary_union
|
|
24
|
-
|
|
25
|
-
CoordArrays = Tuple[Iterable[float], Iterable[float]]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def transform_to_latlon(
|
|
32
|
-
geometry: BaseGeometry, src_crs: CRSLike, width_threshold: float = 180.0
|
|
33
|
-
) -> BaseGeometry:
|
|
34
|
-
"""Transforms a geometry to lat/lon coordinates.
|
|
35
|
-
|
|
36
|
-
If resulting geometry crosses the Antimeridian it will be fixed by moving coordinates
|
|
37
|
-
from the Western Hemisphere to outside of the lat/lon bounds on the East, making sure
|
|
38
|
-
the correct geometry shape is preserved.
|
|
39
|
-
|
|
40
|
-
As a next step, repair_antimeridian_geometry() can be applied, which then splits up
|
|
41
|
-
this geometry into a multipart geometry where all of its subgeometries are within the
|
|
42
|
-
lat/lon bounds again.
|
|
43
|
-
"""
|
|
44
|
-
latlon_crs = CRS.from_epsg(4326)
|
|
45
|
-
|
|
46
|
-
def transform_shift_coords(coords: CoordArrays) -> CoordArrays:
|
|
47
|
-
out_x_coords, out_y_coords = fiona_transform(src_crs, latlon_crs, *coords)
|
|
48
|
-
if max(out_x_coords) - min(out_x_coords) > width_threshold:
|
|
49
|
-
# we probably have an antimeridian crossing here!
|
|
50
|
-
out_x_coords, out_y_coords = coords_longitudinal_shift(
|
|
51
|
-
coords_transform(coords, src_crs, latlon_crs), only_negative_coords=True
|
|
52
|
-
)
|
|
53
|
-
return (out_x_coords, out_y_coords)
|
|
54
|
-
|
|
55
|
-
return custom_transform(geometry, transform_shift_coords)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def repair_antimeridian_geometry(
|
|
59
|
-
geometry: BaseGeometry, width_threshold: float = 180.0
|
|
60
|
-
) -> BaseGeometry:
|
|
61
|
-
"""
|
|
62
|
-
Repair geometry and apply fix if it crosses the Antimeridian.
|
|
63
|
-
|
|
64
|
-
A geometry crosses the Antimeridian if it is at least partly outside of the
|
|
65
|
-
lat/lon bounding box or if its width exceeds a certain threshold. This can happen
|
|
66
|
-
after reprojection if the geometry coordinates are transformed separately and land
|
|
67
|
-
left and right of the Antimeridian, thus resulting in a polygon spanning almost the
|
|
68
|
-
whole lat/lon bounding box width.
|
|
69
|
-
"""
|
|
70
|
-
# repair geometry if it is broken
|
|
71
|
-
geometry = geometry.buffer(0)
|
|
72
|
-
latlon_bbox = box(-180, -90, 180, 90)
|
|
73
|
-
|
|
74
|
-
# only attempt to fix if geometry is too wide or reaches over the lat/lon bounds
|
|
75
|
-
if (
|
|
76
|
-
Bounds.from_inp(geometry).width >= width_threshold
|
|
77
|
-
or not geometry.difference(latlon_bbox).is_empty
|
|
78
|
-
):
|
|
79
|
-
# (1) shift only coordinates on the western hemisphere by 360°, thus "fixing"
|
|
80
|
-
# the footprint, but letting it cross the antimeridian
|
|
81
|
-
shifted_geometry = longitudinal_shift(geometry, only_negative_coords=True)
|
|
82
|
-
|
|
83
|
-
# (2) split up geometry in one outside of latlon bounds and one inside
|
|
84
|
-
inside = shifted_geometry.intersection(latlon_bbox)
|
|
85
|
-
outside = shifted_geometry.difference(latlon_bbox)
|
|
86
|
-
|
|
87
|
-
# (3) shift back only the polygon outside of latlon bounds by -360, thus moving
|
|
88
|
-
# it back to the western hemisphere
|
|
89
|
-
outside_shifted = longitudinal_shift(
|
|
90
|
-
outside, offset=-360, only_negative_coords=False
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# (4) create a MultiPolygon out from these two polygons
|
|
94
|
-
geometry = unary_union([inside, outside_shifted])
|
|
95
|
-
|
|
96
|
-
return geometry
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def buffer_antimeridian_safe(
|
|
100
|
-
footprint: BaseGeometry, buffer_m: float = 0
|
|
101
|
-
) -> BaseGeometry:
|
|
102
|
-
"""Buffer geometry by meters and make it Antimeridian-safe.
|
|
103
|
-
|
|
104
|
-
Safe means that if it crosses the Antimeridian and is a MultiPolygon,
|
|
105
|
-
the buffer will only be applied to the edges facing away from the Antimeridian
|
|
106
|
-
thus leaving the polygon intact if shifted back.
|
|
107
|
-
"""
|
|
108
|
-
if footprint.is_empty:
|
|
109
|
-
return footprint
|
|
110
|
-
|
|
111
|
-
# repair geometry if it is broken
|
|
112
|
-
footprint = footprint.buffer(0)
|
|
113
|
-
|
|
114
|
-
if not buffer_m:
|
|
115
|
-
return footprint
|
|
116
|
-
|
|
117
|
-
if isinstance(footprint, MultiPolygon):
|
|
118
|
-
# we have a shifted footprint here!
|
|
119
|
-
# (1) unshift one part
|
|
120
|
-
subpolygons = []
|
|
121
|
-
for polygon in footprint.geoms:
|
|
122
|
-
lon = polygon.centroid.x
|
|
123
|
-
if lon < 0:
|
|
124
|
-
polygon = longitudinal_shift(polygon)
|
|
125
|
-
subpolygons.append(polygon)
|
|
126
|
-
# (2) merge to single polygon
|
|
127
|
-
merged = unary_union(subpolygons)
|
|
128
|
-
|
|
129
|
-
# (3) apply buffer
|
|
130
|
-
if isinstance(merged, MultiPolygon):
|
|
131
|
-
buffered = unary_union(
|
|
132
|
-
[
|
|
133
|
-
buffer_antimeridian_safe(polygon, buffer_m=buffer_m)
|
|
134
|
-
for polygon in merged.geoms
|
|
135
|
-
]
|
|
136
|
-
)
|
|
137
|
-
else:
|
|
138
|
-
buffered = buffer_antimeridian_safe(merged, buffer_m=buffer_m)
|
|
139
|
-
|
|
140
|
-
# (4) fix again
|
|
141
|
-
return repair_antimeridian_geometry(buffered)
|
|
142
|
-
|
|
143
|
-
# UTM zone CRS
|
|
144
|
-
utm_crs = latlon_to_utm_crs(footprint.centroid.y, footprint.centroid.x)
|
|
145
|
-
latlon_crs = CRS.from_string("EPSG:4326")
|
|
146
|
-
|
|
147
|
-
return transform_to_latlon(
|
|
148
|
-
reproject_geometry(
|
|
149
|
-
footprint, src_crs=latlon_crs, dst_crs=utm_crs, clip_to_crs_bounds=False
|
|
150
|
-
).buffer(buffer_m),
|
|
151
|
-
src_crs=utm_crs,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def longitudinal_shift(
|
|
156
|
-
geometry: BaseGeometry, offset: float = 360.0, only_negative_coords: bool = False
|
|
157
|
-
) -> BaseGeometry:
|
|
158
|
-
"""Return geometry with either all or Western hemisphere coordinates shifted by some offset."""
|
|
159
|
-
return custom_transform(
|
|
160
|
-
geometry,
|
|
161
|
-
partial(
|
|
162
|
-
coords_longitudinal_shift,
|
|
163
|
-
by=offset,
|
|
164
|
-
only_negative_coords=only_negative_coords,
|
|
165
|
-
),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def latlon_to_utm_crs(lat: float, lon: float) -> CRS:
|
|
170
|
-
min_zone = 1
|
|
171
|
-
max_zone = 60
|
|
172
|
-
utm_zone = (
|
|
173
|
-
f"{max([min([(math.floor((lon + 180) / 6) + 1), max_zone]), min_zone]):02}"
|
|
174
|
-
)
|
|
175
|
-
hemisphere_code = "7" if lat <= 0 else "6"
|
|
176
|
-
return CRS.from_string(f"EPSG:32{hemisphere_code}{utm_zone}")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def bounds_to_geom(bounds: Bounds) -> BaseGeometry:
|
|
180
|
-
# TODO: move into core package
|
|
181
|
-
if bounds.left < -180:
|
|
182
|
-
part1 = Bounds(-180, bounds.bottom, bounds.right, bounds.top)
|
|
183
|
-
part2 = Bounds(bounds.left + 360, bounds.bottom, 180, bounds.top)
|
|
184
|
-
return unary_union([shape(part1), shape(part2)])
|
|
185
|
-
elif bounds.right > 180:
|
|
186
|
-
part1 = Bounds(-180, bounds.bottom, bounds.right - 360, bounds.top)
|
|
187
|
-
part2 = Bounds(bounds.left, bounds.bottom, 180, bounds.top)
|
|
188
|
-
return unary_union([shape(part1), shape(part2)])
|
|
189
|
-
else:
|
|
190
|
-
return shape(bounds)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def custom_transform(geometry: BaseGeometry, func: Callable) -> BaseGeometry:
|
|
194
|
-
# todo: shapely.transform.transform maybe can make this code more simple
|
|
195
|
-
# https://shapely.readthedocs.io/en/stable/reference/shapely.transform.html#shapely.transform
|
|
196
|
-
def _point(point: Point) -> Point:
|
|
197
|
-
return Point(zip(*func(point.xy)))
|
|
198
|
-
|
|
199
|
-
def _multipoint(multipoint: MultiPoint) -> MultiPoint:
|
|
200
|
-
return MultiPoint([_point(point) for point in multipoint])
|
|
201
|
-
|
|
202
|
-
def _linestring(linestring: LineString) -> LineString:
|
|
203
|
-
return LineString(zip(*func(linestring.xy)))
|
|
204
|
-
|
|
205
|
-
def _multilinestring(multilinestring: MultiLineString) -> MultiLineString:
|
|
206
|
-
return MultiLineString(
|
|
207
|
-
[_linestring(linestring) for linestring in multilinestring.geoms]
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
def _linearring(linearring: LinearRing) -> LinearRing:
|
|
211
|
-
return LinearRing(((x, y) for x, y in zip(*func(linearring.xy))))
|
|
212
|
-
|
|
213
|
-
def _polygon(polygon: Polygon) -> Polygon:
|
|
214
|
-
return Polygon(
|
|
215
|
-
_linearring(polygon.exterior),
|
|
216
|
-
holes=list(map(_linearring, polygon.interiors)),
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
def _multipolygon(multipolygon: MultiPolygon) -> MultiPolygon:
|
|
220
|
-
return MultiPolygon([_polygon(polygon) for polygon in multipolygon.geoms])
|
|
221
|
-
|
|
222
|
-
def _geometrycollection(
|
|
223
|
-
geometrycollection: GeometryCollection,
|
|
224
|
-
) -> GeometryCollection:
|
|
225
|
-
return GeometryCollection(
|
|
226
|
-
[_any_geometry(subgeometry) for subgeometry in geometrycollection.geoms]
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
def _any_geometry(geometry: BaseGeometry) -> BaseGeometry:
|
|
230
|
-
transform_funcs = {
|
|
231
|
-
Point: _point,
|
|
232
|
-
MultiPoint: _multipoint,
|
|
233
|
-
LineString: _linestring,
|
|
234
|
-
MultiLineString: _multilinestring,
|
|
235
|
-
Polygon: _polygon,
|
|
236
|
-
MultiPolygon: _multipolygon,
|
|
237
|
-
GeometryCollection: _geometrycollection,
|
|
238
|
-
}
|
|
239
|
-
try:
|
|
240
|
-
return transform_funcs[type(geometry)](geometry)
|
|
241
|
-
except KeyError:
|
|
242
|
-
raise TypeError(f"unknown geometry {geometry} of type {type(geometry)}")
|
|
243
|
-
|
|
244
|
-
if geometry.is_empty:
|
|
245
|
-
return geometry
|
|
246
|
-
|
|
247
|
-
# make valid by buffering
|
|
248
|
-
return _any_geometry(geometry).buffer(0)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def coords_transform(
|
|
252
|
-
coords: CoordArrays, src_crs: CRSLike, dst_crs: CRSLike
|
|
253
|
-
) -> CoordArrays:
|
|
254
|
-
return fiona_transform(src_crs, dst_crs, *coords)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def coords_longitudinal_shift(
|
|
258
|
-
coords: CoordArrays,
|
|
259
|
-
by: float = 360,
|
|
260
|
-
only_negative_coords: bool = False,
|
|
261
|
-
) -> CoordArrays:
|
|
262
|
-
x_coords, y_coords = coords
|
|
263
|
-
x_coords = (
|
|
264
|
-
(
|
|
265
|
-
x_coord + by
|
|
266
|
-
if (only_negative_coords and x_coord < 0) or not only_negative_coords
|
|
267
|
-
else x_coord
|
|
268
|
-
)
|
|
269
|
-
for x_coord in x_coords
|
|
270
|
-
)
|
|
271
|
-
return x_coords, y_coords
|
mapchete_eo/known_catalogs.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Catalogs define access to a search interface which provide products
|
|
3
|
-
as pystac Items.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from typing import List
|
|
7
|
-
|
|
8
|
-
from mapchete_eo.search import STACSearchCatalog, UTMSearchCatalog
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class EarthSearchV1S2L2A(STACSearchCatalog):
|
|
12
|
-
"""Earth-Search catalog for Sentinel-2 Level 2A COGs."""
|
|
13
|
-
|
|
14
|
-
endpoint: str = "https://earth-search.aws.element84.com/v1/"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CDSESearch(STACSearchCatalog):
|
|
18
|
-
"""Copernicus Data Space Ecosystem (CDSE) STAC API."""
|
|
19
|
-
|
|
20
|
-
endpoint: str = "https://stac.dataspace.copernicus.eu/v1"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class PlanetaryComputerSearch(STACSearchCatalog):
|
|
24
|
-
"""Planetary Computer Search."""
|
|
25
|
-
|
|
26
|
-
endpoint: str = "https://planetarycomputer.microsoft.com/api/stac/v1/"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class AWSSearchCatalogS2L2A(UTMSearchCatalog):
|
|
30
|
-
"""
|
|
31
|
-
Not a search endpoint, just hanging STAC collection with items separately.
|
|
32
|
-
Need custom parser/browser to find scenes based on date and UTM MGRS Granule
|
|
33
|
-
|
|
34
|
-
https://sentinel-s2-l2a-stac.s3.amazonaws.com/sentinel-s2-l2a.json
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
id: str = "sentinel-s2-l2a"
|
|
38
|
-
endpoint: str = "s3://sentinel-s2-l2a-stac/"
|
|
39
|
-
day_subdir_schema: str = "{year}/{month:02d}/{day:02d}"
|
|
40
|
-
stac_json_endswith: str = "T{tile_id}.json"
|
|
41
|
-
description: str = "Sentinel-2 L2A JPEG2000 archive on AWS."
|
|
42
|
-
stac_extensions: List[str] = []
|