mapchete-eo 2025.7.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 -0
- mapchete_eo/archives/__init__.py +0 -0
- mapchete_eo/archives/base.py +65 -0
- mapchete_eo/array/__init__.py +0 -0
- mapchete_eo/array/buffer.py +16 -0
- mapchete_eo/array/color.py +29 -0
- mapchete_eo/array/convert.py +157 -0
- mapchete_eo/base.py +528 -0
- mapchete_eo/blacklist.txt +175 -0
- mapchete_eo/cli/__init__.py +30 -0
- mapchete_eo/cli/bounds.py +22 -0
- mapchete_eo/cli/options_arguments.py +243 -0
- mapchete_eo/cli/s2_brdf.py +77 -0
- mapchete_eo/cli/s2_cat_results.py +146 -0
- mapchete_eo/cli/s2_find_broken_products.py +93 -0
- mapchete_eo/cli/s2_jp2_static_catalog.py +166 -0
- mapchete_eo/cli/s2_mask.py +71 -0
- mapchete_eo/cli/s2_mgrs.py +45 -0
- mapchete_eo/cli/s2_rgb.py +114 -0
- mapchete_eo/cli/s2_verify.py +129 -0
- mapchete_eo/cli/static_catalog.py +123 -0
- mapchete_eo/eostac.py +30 -0
- mapchete_eo/exceptions.py +87 -0
- mapchete_eo/geometry.py +271 -0
- mapchete_eo/image_operations/__init__.py +12 -0
- mapchete_eo/image_operations/color_correction.py +136 -0
- mapchete_eo/image_operations/compositing.py +247 -0
- mapchete_eo/image_operations/dtype_scale.py +43 -0
- mapchete_eo/image_operations/fillnodata.py +130 -0
- mapchete_eo/image_operations/filters.py +319 -0
- mapchete_eo/image_operations/linear_normalization.py +81 -0
- mapchete_eo/image_operations/sigmoidal.py +114 -0
- mapchete_eo/io/__init__.py +37 -0
- mapchete_eo/io/assets.py +492 -0
- mapchete_eo/io/items.py +147 -0
- mapchete_eo/io/levelled_cubes.py +228 -0
- mapchete_eo/io/path.py +144 -0
- mapchete_eo/io/products.py +413 -0
- mapchete_eo/io/profiles.py +45 -0
- mapchete_eo/known_catalogs.py +42 -0
- mapchete_eo/platforms/sentinel2/__init__.py +17 -0
- mapchete_eo/platforms/sentinel2/archives.py +190 -0
- mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +104 -0
- mapchete_eo/platforms/sentinel2/brdf/__init__.py +8 -0
- mapchete_eo/platforms/sentinel2/brdf/config.py +32 -0
- mapchete_eo/platforms/sentinel2/brdf/correction.py +260 -0
- mapchete_eo/platforms/sentinel2/brdf/hls.py +251 -0
- mapchete_eo/platforms/sentinel2/brdf/models.py +44 -0
- mapchete_eo/platforms/sentinel2/brdf/protocols.py +27 -0
- mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +136 -0
- mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +76 -0
- mapchete_eo/platforms/sentinel2/config.py +181 -0
- mapchete_eo/platforms/sentinel2/driver.py +78 -0
- mapchete_eo/platforms/sentinel2/masks.py +325 -0
- mapchete_eo/platforms/sentinel2/metadata_parser.py +734 -0
- mapchete_eo/platforms/sentinel2/path_mappers/__init__.py +29 -0
- mapchete_eo/platforms/sentinel2/path_mappers/base.py +56 -0
- mapchete_eo/platforms/sentinel2/path_mappers/earthsearch.py +34 -0
- mapchete_eo/platforms/sentinel2/path_mappers/metadata_xml.py +135 -0
- mapchete_eo/platforms/sentinel2/path_mappers/sinergise.py +105 -0
- mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +26 -0
- mapchete_eo/platforms/sentinel2/processing_baseline.py +160 -0
- mapchete_eo/platforms/sentinel2/product.py +669 -0
- mapchete_eo/platforms/sentinel2/types.py +109 -0
- mapchete_eo/processes/__init__.py +0 -0
- mapchete_eo/processes/config.py +51 -0
- mapchete_eo/processes/dtype_scale.py +112 -0
- mapchete_eo/processes/eo_to_xarray.py +19 -0
- mapchete_eo/processes/merge_rasters.py +235 -0
- mapchete_eo/product.py +278 -0
- mapchete_eo/protocols.py +56 -0
- mapchete_eo/search/__init__.py +14 -0
- mapchete_eo/search/base.py +222 -0
- mapchete_eo/search/config.py +42 -0
- mapchete_eo/search/s2_mgrs.py +314 -0
- mapchete_eo/search/stac_search.py +251 -0
- mapchete_eo/search/stac_static.py +236 -0
- mapchete_eo/search/utm_search.py +251 -0
- mapchete_eo/settings.py +24 -0
- mapchete_eo/sort.py +48 -0
- mapchete_eo/time.py +53 -0
- mapchete_eo/types.py +73 -0
- mapchete_eo-2025.7.0.dist-info/METADATA +38 -0
- mapchete_eo-2025.7.0.dist-info/RECORD +87 -0
- mapchete_eo-2025.7.0.dist-info/WHEEL +5 -0
- mapchete_eo-2025.7.0.dist-info/entry_points.txt +11 -0
- mapchete_eo-2025.7.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Protocol
|
|
3
|
+
|
|
4
|
+
from mapchete.io.raster import ReferencedRaster
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import DTypeLike
|
|
8
|
+
|
|
9
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
10
|
+
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BRDFModelProtocol(Protocol):
|
|
14
|
+
"""Defines base interface to all kind of models.
|
|
15
|
+
|
|
16
|
+
Can be sensor models, sun models or directional models!
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def calculate(self) -> ReferencedRaster: ...
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_s2metadata(
|
|
23
|
+
s2_metadata: S2Metadata,
|
|
24
|
+
band: L2ABand,
|
|
25
|
+
detector_id: Optional[int] = None,
|
|
26
|
+
processing_dtype: DTypeLike = np.float32,
|
|
27
|
+
) -> BRDFModelProtocol: ...
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.typing import DTypeLike
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from affine import Affine
|
|
9
|
+
from mapchete.io.raster import ReferencedRaster
|
|
10
|
+
from mapchete.types import CRSLike
|
|
11
|
+
|
|
12
|
+
from mapchete_eo.platforms.sentinel2.brdf.protocols import (
|
|
13
|
+
BRDFModelProtocol,
|
|
14
|
+
)
|
|
15
|
+
from mapchete_eo.platforms.sentinel2.brdf.config import L2ABandFParams, ModelParameters
|
|
16
|
+
from mapchete_eo.platforms.sentinel2.brdf.hls import _get_viewing_angles
|
|
17
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
18
|
+
from mapchete_eo.platforms.sentinel2.types import L2ABand
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RossThick(BRDFModelProtocol):
|
|
22
|
+
"""Directional model."""
|
|
23
|
+
|
|
24
|
+
sun_zenith: np.ndarray
|
|
25
|
+
sun_azimuth: np.ndarray
|
|
26
|
+
view_zenith: np.ndarray
|
|
27
|
+
view_azimuth: np.ndarray
|
|
28
|
+
f_band_params: ModelParameters
|
|
29
|
+
processing_dtype: DTypeLike = np.float32
|
|
30
|
+
|
|
31
|
+
transform: Affine
|
|
32
|
+
crs: CRSLike
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
s2_metadata: S2Metadata,
|
|
37
|
+
band: L2ABand,
|
|
38
|
+
detector_id: Optional[int] = None,
|
|
39
|
+
processing_dtype: DTypeLike = np.float32,
|
|
40
|
+
):
|
|
41
|
+
self.sun_zenith = s2_metadata.sun_angles.zenith.raster.data
|
|
42
|
+
self.sun_azimuth = s2_metadata.sun_angles.azimuth.raster.data
|
|
43
|
+
self.view_zenith, self.view_azimuth = _get_viewing_angles(
|
|
44
|
+
s2_metadata=s2_metadata, band=band, detector_id=detector_id
|
|
45
|
+
)
|
|
46
|
+
self.f_band_params = L2ABandFParams[band.name].value
|
|
47
|
+
self.processing_dtype = processing_dtype
|
|
48
|
+
|
|
49
|
+
self.sun_zenith_radian = np.deg2rad(self.sun_zenith)
|
|
50
|
+
self.sun_azimuth_radian = np.deg2rad(self.sun_azimuth)
|
|
51
|
+
self.view_zenith_radian = np.deg2rad(self.view_zenith)
|
|
52
|
+
self.view_azimuth_radian = np.deg2rad(self.view_azimuth)
|
|
53
|
+
|
|
54
|
+
self.relative_azimuth_angle_radian = np.abs(
|
|
55
|
+
self.view_azimuth_radian - self.sun_azimuth_radian
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.transform = s2_metadata.sun_angles.zenith.raster.transform
|
|
59
|
+
self.crs = s2_metadata.crs
|
|
60
|
+
|
|
61
|
+
def calculate(self) -> ReferencedRaster:
|
|
62
|
+
"""
|
|
63
|
+
Ross-Thick BRDF model function that computes the C factor.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
- f_iso, f_vol, f_geo: BRDF model parameters to fit.
|
|
67
|
+
- sza: Solar Zenith Angle (in degrees).
|
|
68
|
+
- vza: View Zenith Angle (in degrees).
|
|
69
|
+
- raa: Relative Azimuth Angle (in degrees).
|
|
70
|
+
- normalize: Normalize by nadir sensor with sun angles.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
- C factor according to the Ross-Thick BRDF model.
|
|
74
|
+
"""
|
|
75
|
+
sza = self.sun_zenith_radian
|
|
76
|
+
vza = self.view_zenith_radian
|
|
77
|
+
raa = self.relative_azimuth_angle_radian
|
|
78
|
+
|
|
79
|
+
f_iso = self.f_band_params.f_iso
|
|
80
|
+
f_vol = self.f_band_params.f_vol
|
|
81
|
+
f_geo = self.f_band_params.f_geo
|
|
82
|
+
|
|
83
|
+
# Scale vza for fitting
|
|
84
|
+
vza = vza / (np.pi / 2)
|
|
85
|
+
|
|
86
|
+
def compute_kernels(vza, sza, raa):
|
|
87
|
+
# Cosine of view and solar zenith angles
|
|
88
|
+
cos_vza = np.cos(vza)
|
|
89
|
+
cos_sza = np.cos(sza)
|
|
90
|
+
|
|
91
|
+
# Phase angle (theta)
|
|
92
|
+
cos_theta = cos_vza * cos_sza + np.sin(vza) * np.sin(sza) * np.cos(raa)
|
|
93
|
+
theta = np.arccos(np.clip(cos_theta, -1.0, 1.0))
|
|
94
|
+
|
|
95
|
+
# Ross-Thick Kernel (K_vol)
|
|
96
|
+
K_vol = ((np.pi - theta) * cos_theta + np.sin(theta)) / (
|
|
97
|
+
cos_vza + cos_sza
|
|
98
|
+
) - np.pi / 4
|
|
99
|
+
|
|
100
|
+
# Li-Sparse Kernel (K_geo)
|
|
101
|
+
tan_vza = np.tan(vza)
|
|
102
|
+
tan_sza = np.tan(sza)
|
|
103
|
+
D = np.sqrt(tan_vza**2 + tan_sza**2 - 2 * tan_vza * tan_sza * np.cos(raa))
|
|
104
|
+
cos_phase = np.clip((2 * D / (D + tan_vza + tan_sza)), -1.0, 1.0)
|
|
105
|
+
K_geo = (1 / np.pi) * (D - cos_phase * (tan_vza + tan_sza)) + np.arctan(D)
|
|
106
|
+
|
|
107
|
+
return K_vol, K_geo
|
|
108
|
+
|
|
109
|
+
# Calculate kernels for actual angles
|
|
110
|
+
K_vol, K_geo = compute_kernels(vza, sza, raa)
|
|
111
|
+
C_actual = f_iso + f_vol * K_vol + f_geo * K_geo
|
|
112
|
+
|
|
113
|
+
# Calculate kernels for nadir (0° view, 0° relative azimuth)
|
|
114
|
+
K_vol_nadir, K_geo_nadir = compute_kernels(0, sza, 0)
|
|
115
|
+
C_nadir = f_iso + f_vol * K_vol_nadir + f_geo * K_geo_nadir
|
|
116
|
+
|
|
117
|
+
# Normalize and return c-factors
|
|
118
|
+
return ReferencedRaster.from_array_like(
|
|
119
|
+
array_like=(C_nadir / C_actual),
|
|
120
|
+
transform=self.transform,
|
|
121
|
+
crs=self.crs,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def from_s2metadata(
|
|
126
|
+
s2_metadata: S2Metadata,
|
|
127
|
+
band: L2ABand,
|
|
128
|
+
detector_id: Optional[int] = None,
|
|
129
|
+
processing_dtype: DTypeLike = np.float32,
|
|
130
|
+
) -> RossThick:
|
|
131
|
+
return RossThick(
|
|
132
|
+
s2_metadata=s2_metadata,
|
|
133
|
+
band=band,
|
|
134
|
+
detector_id=detector_id,
|
|
135
|
+
processing_dtype=processing_dtype,
|
|
136
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from fiona.transform import transform
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from mapchete_eo.platforms.sentinel2.metadata_parser import S2Metadata
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_sun_zenith_angles(s2_metadata: S2Metadata) -> np.ndarray:
|
|
10
|
+
_, (bottom, top) = transform(
|
|
11
|
+
s2_metadata.crs,
|
|
12
|
+
"EPSG:4326",
|
|
13
|
+
[s2_metadata.bounds[0], s2_metadata.bounds[2]],
|
|
14
|
+
[s2_metadata.bounds[1], s2_metadata.bounds[3]],
|
|
15
|
+
)
|
|
16
|
+
return get_sun_angle_array(
|
|
17
|
+
min_lat=bottom,
|
|
18
|
+
max_lat=top,
|
|
19
|
+
shape=s2_metadata.sun_angles.zenith.raster.data.shape,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_sun_angle_array(
|
|
24
|
+
min_lat: float, max_lat: float, shape: Tuple[int, int]
|
|
25
|
+
) -> np.ndarray:
|
|
26
|
+
"""
|
|
27
|
+
Calculate array of sun angles between latitudes.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
=======
|
|
31
|
+
sun angle array in radians : np.ndarray
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def _sun_angle(lat):
|
|
35
|
+
"""
|
|
36
|
+
Calculate the constant sun zenith angle via 6th polynomial function see HLS.
|
|
37
|
+
|
|
38
|
+
See page 13 of:
|
|
39
|
+
https://hls.gsfc.nasa.gov/wp-content/uploads/2019/01/HLS.v1.4.UserGuide_draft_ver3.1.pdf
|
|
40
|
+
"""
|
|
41
|
+
# constants used for sun angle calculation
|
|
42
|
+
# See page 13 of:
|
|
43
|
+
# https://hls.gsfc.nasa.gov/wp-content/uploads/2019/01/HLS.v1.4.UserGuide_draft_ver3.1.pdf
|
|
44
|
+
k0 = 31
|
|
45
|
+
k1 = -0.127
|
|
46
|
+
k2 = 0.0119
|
|
47
|
+
k3 = 2.4e-05
|
|
48
|
+
k4 = -9.48e-07
|
|
49
|
+
k5 = -1.95e-09
|
|
50
|
+
k6 = 6.15e-11
|
|
51
|
+
|
|
52
|
+
# Constant sun zenith angle 6th polynomial function
|
|
53
|
+
return (
|
|
54
|
+
k0
|
|
55
|
+
+ k1 * lat
|
|
56
|
+
+ k2 * (lat**2)
|
|
57
|
+
+ k4 * (lat**4)
|
|
58
|
+
+ k3 * (lat**3)
|
|
59
|
+
+ k5 * (lat**5)
|
|
60
|
+
+ k6 * (lat**6)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# return get_constant_sun_angle(min_lat, max_lat)
|
|
64
|
+
height, width = shape
|
|
65
|
+
cell_size = (max_lat - min_lat) / (height + 1)
|
|
66
|
+
|
|
67
|
+
# move by half a pixel so pixel centers are represented
|
|
68
|
+
top = max_lat - cell_size / 2
|
|
69
|
+
|
|
70
|
+
# generate one column of angles
|
|
71
|
+
angles = [_sun_angle(top - i * cell_size) for i in range(width)]
|
|
72
|
+
|
|
73
|
+
# expand column to output shape width
|
|
74
|
+
return np.radians(
|
|
75
|
+
np.array([[i for _ in range(width)] for i in angles], dtype=np.float32)
|
|
76
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from mapchete.path import MPathLike
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ValidationError,
|
|
9
|
+
field_validator,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from mapchete_eo.base import BaseDriverConfig
|
|
13
|
+
from mapchete_eo.io.path import ProductPathGenerationMethod
|
|
14
|
+
from mapchete_eo.platforms.sentinel2.archives import ArchiveClsFromString, AWSL2ACOGv1
|
|
15
|
+
from mapchete_eo.platforms.sentinel2.brdf.config import BRDFModels
|
|
16
|
+
from mapchete_eo.platforms.sentinel2.types import (
|
|
17
|
+
CloudType,
|
|
18
|
+
ProductQIMaskResolution,
|
|
19
|
+
Resolution,
|
|
20
|
+
SceneClassification,
|
|
21
|
+
)
|
|
22
|
+
from mapchete_eo.search.config import StacSearchConfig
|
|
23
|
+
from mapchete_eo.types import TimeRange
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BRDFModelConfig(BaseModel):
|
|
27
|
+
model: BRDFModels = BRDFModels.HLS
|
|
28
|
+
bands: List[str] = ["blue", "green", "red", "nir"]
|
|
29
|
+
resolution: Resolution = Resolution["60m"]
|
|
30
|
+
footprints_cached_read: bool = False
|
|
31
|
+
log10_bands_scale: bool = False
|
|
32
|
+
per_detector_correction: bool = False
|
|
33
|
+
|
|
34
|
+
# This correction value is applied to `fv` (kvol) and `fr` (kgeo) in the final steps of the BRDF param
|
|
35
|
+
correction_weight: float = 1.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BRDFSCLClassConfig(BRDFModelConfig):
|
|
39
|
+
scl_classes: List[SceneClassification]
|
|
40
|
+
|
|
41
|
+
@field_validator("scl_classes", mode="before")
|
|
42
|
+
@classmethod
|
|
43
|
+
def to_scl_classes(cls, values: List[str]) -> List[SceneClassification]:
|
|
44
|
+
out = []
|
|
45
|
+
for value in values:
|
|
46
|
+
if isinstance(value, SceneClassification):
|
|
47
|
+
out.append(value)
|
|
48
|
+
elif isinstance(value, str):
|
|
49
|
+
out.append(SceneClassification[value])
|
|
50
|
+
else:
|
|
51
|
+
raise ValidationError("value must be mappable to SceneClassification")
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BRDFConfig(BRDFModelConfig):
|
|
56
|
+
"""
|
|
57
|
+
Main BRDF configuration with optional sub-configurations for certain SCL classes.
|
|
58
|
+
|
|
59
|
+
model: BRDF model
|
|
60
|
+
bands: list of band names
|
|
61
|
+
resolution: resolution BRDF is calculated on
|
|
62
|
+
footprints_cached_read: download and read footprints from cache or not
|
|
63
|
+
correction_weight: make correction stronger (>1) or weaker (<1)
|
|
64
|
+
scl_specific_configurations: list of parameters like above plus SCL classes it
|
|
65
|
+
should be applied to
|
|
66
|
+
|
|
67
|
+
e.g.
|
|
68
|
+
BRDFConfig(
|
|
69
|
+
model="HLS",
|
|
70
|
+
bands=["red", "green", "blue"],
|
|
71
|
+
resolution="60m",
|
|
72
|
+
footprints_cached_read=True,
|
|
73
|
+
correction_weight=0.9,
|
|
74
|
+
log10_bands_scale=True,
|
|
75
|
+
scl_specific_configurations=[
|
|
76
|
+
BRDFSCLClassConfig(
|
|
77
|
+
scl_classes=["water"],
|
|
78
|
+
model="HLS",
|
|
79
|
+
bands=["red", "green", "blue"],
|
|
80
|
+
resolution="60m",
|
|
81
|
+
footprints_cached_read=True,
|
|
82
|
+
correction_weight=1.3,
|
|
83
|
+
)
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
scl_specific_configurations: Optional[List[BRDFSCLClassConfig]] = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CacheConfig(BaseModel):
|
|
93
|
+
path: MPathLike
|
|
94
|
+
product_path_generation_method: ProductPathGenerationMethod = (
|
|
95
|
+
ProductPathGenerationMethod.hash
|
|
96
|
+
)
|
|
97
|
+
intersection_percent: float = 100.0
|
|
98
|
+
assets: List[str] = []
|
|
99
|
+
assets_resolution: Resolution = Resolution.original
|
|
100
|
+
keep: bool = False
|
|
101
|
+
max_cloud_cover: float = 100.0
|
|
102
|
+
max_disk_usage: float = 90.0
|
|
103
|
+
brdf: Optional[BRDFConfig] = None
|
|
104
|
+
zoom: int = 13
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Sentinel2DriverConfig(BaseDriverConfig):
|
|
108
|
+
format: str = "Sentinel-2"
|
|
109
|
+
time: Union[TimeRange, List[TimeRange]]
|
|
110
|
+
archive: ArchiveClsFromString = AWSL2ACOGv1
|
|
111
|
+
cat_baseurl: Optional[MPathLike] = None
|
|
112
|
+
search_index: Optional[MPathLike] = None
|
|
113
|
+
max_cloud_cover: float = 100.0
|
|
114
|
+
stac_config: StacSearchConfig = StacSearchConfig()
|
|
115
|
+
first_granule_only: bool = False
|
|
116
|
+
utm_zone: Optional[int] = None
|
|
117
|
+
with_scl: bool = False
|
|
118
|
+
brdf: Optional[BRDFConfig] = None
|
|
119
|
+
cache: Optional[CacheConfig] = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class MaskConfig(BaseModel):
|
|
123
|
+
# mask by footprint geometry
|
|
124
|
+
footprint: bool = True
|
|
125
|
+
# apply buffer (in meters!) to footprint
|
|
126
|
+
footprint_buffer_m: float = -500
|
|
127
|
+
# add pixel buffer to all masks
|
|
128
|
+
buffer: int = 0
|
|
129
|
+
# mask by L1C cloud types (either opaque, cirrus or all)
|
|
130
|
+
l1c_cloud_type: Optional[CloudType] = None
|
|
131
|
+
# mask using the snow/ice mask
|
|
132
|
+
snow_ice: bool = False
|
|
133
|
+
# mask using cloud probability classification
|
|
134
|
+
cloud_probability_threshold: int = 100
|
|
135
|
+
cloud_probability_resolution: ProductQIMaskResolution = ProductQIMaskResolution[
|
|
136
|
+
"60m"
|
|
137
|
+
]
|
|
138
|
+
# mask using cloud probability classification
|
|
139
|
+
snow_probability_threshold: int = 100
|
|
140
|
+
snow_probability_resolution: ProductQIMaskResolution = ProductQIMaskResolution[
|
|
141
|
+
"60m"
|
|
142
|
+
]
|
|
143
|
+
# mask using one or more of the SCL classes
|
|
144
|
+
scl_classes: Optional[List[SceneClassification]] = None
|
|
145
|
+
# download masks before reading
|
|
146
|
+
l1c_cloud_mask_cached_read: bool = False
|
|
147
|
+
snow_ice_mask_cached_read: bool = False
|
|
148
|
+
cloud_probability_cached_read: bool = False
|
|
149
|
+
snow_probability_cached_read: bool = False
|
|
150
|
+
scl_cached_read: bool = False
|
|
151
|
+
|
|
152
|
+
@field_validator("scl_classes", mode="before")
|
|
153
|
+
@classmethod
|
|
154
|
+
def to_scl_classes(cls, values: List[str]) -> List[SceneClassification]:
|
|
155
|
+
if values is None:
|
|
156
|
+
return
|
|
157
|
+
out = []
|
|
158
|
+
for value in values:
|
|
159
|
+
if isinstance(value, SceneClassification):
|
|
160
|
+
out.append(value)
|
|
161
|
+
elif isinstance(value, str):
|
|
162
|
+
out.append(SceneClassification[value])
|
|
163
|
+
else:
|
|
164
|
+
raise ValidationError("value must be mappable to SceneClassification")
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def parse(config: Union[dict, MaskConfig]) -> MaskConfig:
|
|
169
|
+
"""
|
|
170
|
+
Make sure all values are parsed correctly
|
|
171
|
+
"""
|
|
172
|
+
if isinstance(config, MaskConfig):
|
|
173
|
+
return config
|
|
174
|
+
|
|
175
|
+
elif isinstance(config, dict):
|
|
176
|
+
return MaskConfig(**config)
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
raise TypeError(
|
|
180
|
+
f"mask configuration should either be a dictionary or a MaskConfig object, not {config}"
|
|
181
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Optional, List, Tuple
|
|
2
|
+
|
|
3
|
+
from mapchete.geometry import reproject_geometry
|
|
4
|
+
from mapchete.path import MPath
|
|
5
|
+
from mapchete.types import NodataVal
|
|
6
|
+
from rasterio.enums import Resampling
|
|
7
|
+
|
|
8
|
+
from mapchete_eo import base
|
|
9
|
+
from mapchete_eo.archives.base import Archive
|
|
10
|
+
from mapchete_eo.platforms.sentinel2.config import Sentinel2DriverConfig
|
|
11
|
+
from mapchete_eo.platforms.sentinel2.preprocessing_tasks import parse_s2_product
|
|
12
|
+
from mapchete_eo.search.stac_static import STACStaticCatalog
|
|
13
|
+
from mapchete_eo.settings import mapchete_eo_settings
|
|
14
|
+
from mapchete_eo.types import MergeMethod
|
|
15
|
+
|
|
16
|
+
METADATA: dict = {
|
|
17
|
+
"driver_name": "Sentinel-2",
|
|
18
|
+
"data_type": None,
|
|
19
|
+
"mode": "r",
|
|
20
|
+
"file_extensions": [],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Sentinel2Cube(base.EODataCube):
|
|
25
|
+
# Sentinel-2 driver specific default values:
|
|
26
|
+
default_read_merge_method: MergeMethod = MergeMethod.average
|
|
27
|
+
default_read_merge_products_by: Optional[str] = "s2:datastrip_id"
|
|
28
|
+
default_read_nodataval: NodataVal = 0
|
|
29
|
+
default_read_resampling: Resampling = Resampling.bilinear
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Sentinel2CubeGroup = List[Tuple[str, Sentinel2Cube]]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InputData(base.InputData):
|
|
36
|
+
"""
|
|
37
|
+
Main driver class used by mapchete.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Sentinel-2 driver specific parameters:
|
|
41
|
+
default_preprocessing_task = staticmethod(parse_s2_product)
|
|
42
|
+
driver_config_model = Sentinel2DriverConfig
|
|
43
|
+
params: Sentinel2DriverConfig
|
|
44
|
+
input_tile_cls = Sentinel2Cube
|
|
45
|
+
|
|
46
|
+
def set_archive(self, base_dir: MPath):
|
|
47
|
+
if self.params.cat_baseurl:
|
|
48
|
+
self.archive = Archive(
|
|
49
|
+
catalog=STACStaticCatalog(
|
|
50
|
+
baseurl=MPath(self.params.cat_baseurl).absolute_path(
|
|
51
|
+
base_dir=base_dir
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
area=self.bbox(mapchete_eo_settings.default_catalog_crs),
|
|
55
|
+
time=self.time,
|
|
56
|
+
search_kwargs=dict(max_cloud_cover=self.params.max_cloud_cover),
|
|
57
|
+
)
|
|
58
|
+
elif self.params.archive:
|
|
59
|
+
catalog_area = reproject_geometry(
|
|
60
|
+
self.area,
|
|
61
|
+
src_crs=self.crs,
|
|
62
|
+
dst_crs=mapchete_eo_settings.default_catalog_crs,
|
|
63
|
+
)
|
|
64
|
+
self.archive = self.params.archive(
|
|
65
|
+
time=self.time,
|
|
66
|
+
bounds=catalog_area.bounds,
|
|
67
|
+
area=catalog_area,
|
|
68
|
+
search_kwargs=dict(
|
|
69
|
+
search_index=(
|
|
70
|
+
MPath(self.params.search_index).absolute_path(base_dir=base_dir)
|
|
71
|
+
if self.params.search_index
|
|
72
|
+
else None
|
|
73
|
+
),
|
|
74
|
+
max_cloud_cover=self.params.max_cloud_cover,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError("either 'archive' or 'cat_baseurl' or both is required.")
|