openeo-gfmap 0.1.0__py3-none-any.whl → 0.3.0__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.
- openeo_gfmap/features/feature_extractor.py +9 -0
- openeo_gfmap/fetching/__init__.py +16 -4
- openeo_gfmap/fetching/commons.py +1 -0
- openeo_gfmap/fetching/generic.py +81 -73
- openeo_gfmap/fetching/s1.py +1 -3
- openeo_gfmap/fetching/s2.py +1 -0
- openeo_gfmap/inference/model_inference.py +5 -2
- openeo_gfmap/manager/job_manager.py +271 -84
- openeo_gfmap/manager/job_splitters.py +169 -21
- openeo_gfmap/preprocessing/sar.py +12 -33
- openeo_gfmap/stac/constants.py +1 -1
- openeo_gfmap/utils/__init__.py +16 -0
- openeo_gfmap/utils/catalogue.py +172 -35
- openeo_gfmap/utils/split_stac.py +125 -0
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/METADATA +5 -4
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/RECORD +18 -18
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/WHEEL +1 -1
- openeo_gfmap/fetching/meteo.py +0 -126
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/licenses/LICENSE +0 -0
openeo_gfmap/utils/catalogue.py
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
"""Functionalities to interract with product catalogues."""
|
2
2
|
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
import geojson
|
6
|
+
import pandas as pd
|
3
7
|
import requests
|
4
|
-
from geojson import GeoJSON
|
5
8
|
from pyproj.crs import CRS
|
6
9
|
from rasterio.warp import transform_bounds
|
7
|
-
from
|
8
|
-
from shapely.geometry import box, shape
|
10
|
+
from requests import adapters
|
11
|
+
from shapely.geometry import Point, box, shape
|
12
|
+
from shapely.ops import unary_union
|
9
13
|
|
10
14
|
from openeo_gfmap import (
|
11
15
|
Backend,
|
@@ -14,6 +18,21 @@ from openeo_gfmap import (
|
|
14
18
|
SpatialContext,
|
15
19
|
TemporalContext,
|
16
20
|
)
|
21
|
+
from openeo_gfmap.utils import _log
|
22
|
+
|
23
|
+
request_sessions: Optional[requests.Session] = None
|
24
|
+
|
25
|
+
|
26
|
+
def _request_session() -> requests.Session:
|
27
|
+
global request_sessions
|
28
|
+
|
29
|
+
if request_sessions is None:
|
30
|
+
request_sessions = requests.Session()
|
31
|
+
retries = adapters.Retry(
|
32
|
+
total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504]
|
33
|
+
)
|
34
|
+
request_sessions.mount("https://", adapters.HTTPAdapter(max_retries=retries))
|
35
|
+
return request_sessions
|
17
36
|
|
18
37
|
|
19
38
|
class UncoveredS1Exception(Exception):
|
@@ -24,13 +43,21 @@ class UncoveredS1Exception(Exception):
|
|
24
43
|
|
25
44
|
|
26
45
|
def _parse_cdse_products(response: dict):
|
27
|
-
"""Parses the geometry of products from the CDSE catalogue."""
|
28
|
-
|
46
|
+
"""Parses the geometry and timestamps of products from the CDSE catalogue."""
|
47
|
+
geometries = []
|
48
|
+
timestamps = []
|
29
49
|
products = response["features"]
|
30
50
|
|
31
51
|
for product in products:
|
32
|
-
|
33
|
-
|
52
|
+
if "geometry" in product and "startDate" in product["properties"]:
|
53
|
+
geometries.append(shape(product["geometry"]))
|
54
|
+
timestamps.append(pd.to_datetime(product["properties"]["startDate"]))
|
55
|
+
else:
|
56
|
+
_log.warning(
|
57
|
+
"Cannot parse product %s does not have a geometry or timestamp.",
|
58
|
+
product["properties"]["id"],
|
59
|
+
)
|
60
|
+
return geometries, timestamps
|
34
61
|
|
35
62
|
|
36
63
|
def _query_cdse_catalogue(
|
@@ -39,6 +66,14 @@ def _query_cdse_catalogue(
|
|
39
66
|
temporal_extent: TemporalContext,
|
40
67
|
**additional_parameters: dict,
|
41
68
|
) -> dict:
|
69
|
+
"""
|
70
|
+
Queries the CDSE catalogue for a given collection, spatio-temporal context and additional
|
71
|
+
parameters.
|
72
|
+
|
73
|
+
Params
|
74
|
+
------
|
75
|
+
|
76
|
+
"""
|
42
77
|
minx, miny, maxx, maxy = bounds
|
43
78
|
|
44
79
|
# The date format should be YYYY-MM-DD
|
@@ -48,13 +83,14 @@ def _query_cdse_catalogue(
|
|
48
83
|
url = (
|
49
84
|
f"https://catalogue.dataspace.copernicus.eu/resto/api/collections/"
|
50
85
|
f"{collection}/search.json?box={minx},{miny},{maxx},{maxy}"
|
51
|
-
f"&sortParam=startDate&maxRecords=
|
52
|
-
f"&
|
86
|
+
f"&sortParam=startDate&maxRecords=1000&dataset=ESA-DATASET"
|
87
|
+
f"&startDate={start_date}&completionDate={end_date}"
|
53
88
|
)
|
54
89
|
for key, value in additional_parameters.items():
|
55
90
|
url += f"&{key}={value}"
|
56
91
|
|
57
|
-
|
92
|
+
session = _request_session()
|
93
|
+
response = session.get(url, timeout=60)
|
58
94
|
|
59
95
|
if response.status_code != 200:
|
60
96
|
raise Exception(
|
@@ -107,19 +143,51 @@ def _check_cdse_catalogue(
|
|
107
143
|
return len(grd_tiles) > 0
|
108
144
|
|
109
145
|
|
110
|
-
def
|
146
|
+
def _compute_max_gap_days(
|
147
|
+
temporal_extent: TemporalContext, timestamps: list[pd.DatetimeIndex]
|
148
|
+
) -> int:
|
149
|
+
"""Computes the maximum temporal gap in days from the timestamps parsed from the catalogue.
|
150
|
+
Requires the start and end date to be included in the timestamps to compute the gap before
|
151
|
+
and after the first and last observation.
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
temporal_extent : TemporalContext
|
156
|
+
The temporal extent to be checked. Same as used to query the catalogue.
|
157
|
+
timestamps : list[pd.DatetimeIndex]
|
158
|
+
The list of timestamps parsed from the catalogue and to compute the gap from.
|
159
|
+
|
160
|
+
Returns
|
161
|
+
-------
|
162
|
+
days : int
|
163
|
+
The maximum temporal gap in days.
|
164
|
+
"""
|
165
|
+
# Computes max temporal gap. Include requested start and end date so we dont miss
|
166
|
+
# any start or end gap before first/last observation
|
167
|
+
timestamps = pd.DatetimeIndex(
|
168
|
+
sorted(
|
169
|
+
[pd.to_datetime(temporal_extent.start_date, utc=True)]
|
170
|
+
+ timestamps
|
171
|
+
+ [pd.to_datetime(temporal_extent.end_date, utc=True)]
|
172
|
+
)
|
173
|
+
)
|
174
|
+
return timestamps.to_series().diff().max().days
|
175
|
+
|
176
|
+
|
177
|
+
def s1_area_per_orbitstate_vvvh(
|
111
178
|
backend: BackendContext,
|
112
179
|
spatial_extent: SpatialContext,
|
113
180
|
temporal_extent: TemporalContext,
|
114
181
|
) -> dict:
|
115
|
-
"""
|
116
|
-
|
117
|
-
|
182
|
+
"""
|
183
|
+
Evaluates for both the ascending and descending state orbits the area of interesection and
|
184
|
+
maximum temporal gap for the available products with a VV&VH polarisation.
|
118
185
|
|
119
186
|
Parameters
|
120
187
|
----------
|
121
188
|
backend : BackendContext
|
122
|
-
The backend to be within, as each backend might use different catalogues.
|
189
|
+
The backend to be within, as each backend might use different catalogues. Only the CDSE,
|
190
|
+
CDSE_STAGING and FED backends are supported.
|
123
191
|
spatial_extent : SpatialContext
|
124
192
|
The spatial extent to be checked, it will check within its bounding box.
|
125
193
|
temporal_extent : TemporalContext
|
@@ -128,13 +196,23 @@ def s1_area_per_orbitstate(
|
|
128
196
|
Returns
|
129
197
|
------
|
130
198
|
dict
|
131
|
-
Keys containing the orbit state and values containing the total area of intersection
|
132
|
-
km^2
|
199
|
+
Keys containing the orbit state and values containing the total area of intersection and
|
200
|
+
in km^2 and maximum temporal gap in days.
|
133
201
|
"""
|
134
|
-
if isinstance(spatial_extent,
|
202
|
+
if isinstance(spatial_extent, geojson.FeatureCollection):
|
135
203
|
# Transform geojson into shapely geometry and compute bounds
|
136
|
-
|
137
|
-
|
204
|
+
shapely_geometries = [
|
205
|
+
shape(feature["geometry"]) for feature in spatial_extent["features"]
|
206
|
+
]
|
207
|
+
if len(shapely_geometries) == 1 and isinstance(shapely_geometries[0], Point):
|
208
|
+
point = shapely_geometries[0]
|
209
|
+
buffer_size = 0.0001
|
210
|
+
buffered_geometry = point.buffer(buffer_size)
|
211
|
+
bounds = buffered_geometry.bounds
|
212
|
+
else:
|
213
|
+
geometry = unary_union(shapely_geometries)
|
214
|
+
bounds = geometry.bounds
|
215
|
+
epsg = 4326
|
138
216
|
elif isinstance(spatial_extent, BoundingBoxExtent):
|
139
217
|
bounds = [
|
140
218
|
spatial_extent.west,
|
@@ -153,17 +231,22 @@ def s1_area_per_orbitstate(
|
|
153
231
|
|
154
232
|
# Queries the products in the catalogues
|
155
233
|
if backend.backend in [Backend.CDSE, Backend.CDSE_STAGING, Backend.FED]:
|
156
|
-
ascending_products = _parse_cdse_products(
|
234
|
+
ascending_products, ascending_timestamps = _parse_cdse_products(
|
157
235
|
_query_cdse_catalogue(
|
158
|
-
"Sentinel1",
|
236
|
+
"Sentinel1",
|
237
|
+
bounds,
|
238
|
+
temporal_extent,
|
239
|
+
orbitDirection="ASCENDING",
|
240
|
+
polarisation="VV%26VH",
|
159
241
|
)
|
160
242
|
)
|
161
|
-
descending_products = _parse_cdse_products(
|
243
|
+
descending_products, descending_timestamps = _parse_cdse_products(
|
162
244
|
_query_cdse_catalogue(
|
163
245
|
"Sentinel1",
|
164
246
|
bounds,
|
165
247
|
temporal_extent,
|
166
248
|
orbitDirection="DESCENDING",
|
249
|
+
polarisation="VV%26VH",
|
167
250
|
)
|
168
251
|
)
|
169
252
|
else:
|
@@ -185,6 +268,9 @@ def s1_area_per_orbitstate(
|
|
185
268
|
return {
|
186
269
|
"ASCENDING": {
|
187
270
|
"full_overlap": ascending_covers,
|
271
|
+
"max_temporal_gap": _compute_max_gap_days(
|
272
|
+
temporal_extent, ascending_timestamps
|
273
|
+
),
|
188
274
|
"area": sum(
|
189
275
|
product.intersection(spatial_extent).area
|
190
276
|
for product in ascending_products
|
@@ -192,6 +278,9 @@ def s1_area_per_orbitstate(
|
|
192
278
|
},
|
193
279
|
"DESCENDING": {
|
194
280
|
"full_overlap": descending_covers,
|
281
|
+
"max_temporal_gap": _compute_max_gap_days(
|
282
|
+
temporal_extent, descending_timestamps
|
283
|
+
),
|
195
284
|
"area": sum(
|
196
285
|
product.intersection(spatial_extent).area
|
197
286
|
for product in descending_products
|
@@ -200,22 +289,31 @@ def s1_area_per_orbitstate(
|
|
200
289
|
}
|
201
290
|
|
202
291
|
|
203
|
-
def
|
292
|
+
def select_s1_orbitstate_vvvh(
|
204
293
|
backend: BackendContext,
|
205
294
|
spatial_extent: SpatialContext,
|
206
295
|
temporal_extent: TemporalContext,
|
296
|
+
max_temporal_gap: int = 60,
|
207
297
|
) -> str:
|
208
|
-
"""Selects the orbit state
|
209
|
-
|
298
|
+
"""Selects the orbit state based on some predefined rules that
|
299
|
+
are checked in sequential order:
|
300
|
+
1. prefer an orbit with full coverage over the requested bounds
|
301
|
+
2. prefer an orbit with a maximum temporal gap under a
|
302
|
+
predefined threshold
|
303
|
+
3. prefer the orbit that covers the most area of intersection
|
304
|
+
for the available products
|
210
305
|
|
211
306
|
Parameters
|
212
307
|
----------
|
213
308
|
backend : BackendContext
|
214
|
-
The backend to be within, as each backend might use different catalogues.
|
309
|
+
The backend to be within, as each backend might use different catalogues. Only the CDSE,
|
310
|
+
CDSE_STAGING and FED backends are supported.
|
215
311
|
spatial_extent : SpatialContext
|
216
312
|
The spatial extent to be checked, it will check within its bounding box.
|
217
313
|
temporal_extent : TemporalContext
|
218
314
|
The temporal period to be checked.
|
315
|
+
max_temporal_gap: int, optional, default: 30
|
316
|
+
The maximum temporal gap in days to be considered for the orbit state.
|
219
317
|
|
220
318
|
Returns
|
221
319
|
------
|
@@ -224,25 +322,64 @@ def select_S1_orbitstate(
|
|
224
322
|
"""
|
225
323
|
|
226
324
|
# Queries the products in the catalogues
|
227
|
-
areas =
|
325
|
+
areas = s1_area_per_orbitstate_vvvh(backend, spatial_extent, temporal_extent)
|
228
326
|
|
229
327
|
ascending_overlap = areas["ASCENDING"]["full_overlap"]
|
230
328
|
descending_overlap = areas["DESCENDING"]["full_overlap"]
|
329
|
+
ascending_gap_too_large = areas["ASCENDING"]["max_temporal_gap"] > max_temporal_gap
|
330
|
+
descending_gap_too_large = (
|
331
|
+
areas["DESCENDING"]["max_temporal_gap"] > max_temporal_gap
|
332
|
+
)
|
333
|
+
|
334
|
+
orbit_choice = None
|
335
|
+
|
336
|
+
if not ascending_overlap and not descending_overlap:
|
337
|
+
raise UncoveredS1Exception(
|
338
|
+
"No product available to fully cover the requested area in both orbit states."
|
339
|
+
)
|
231
340
|
|
341
|
+
# Rule 1: Prefer an orbit with full coverage over the requested bounds
|
232
342
|
if ascending_overlap and not descending_overlap:
|
233
|
-
|
343
|
+
orbit_choice = "ASCENDING"
|
344
|
+
reason = "Only orbit fully covering the requested area."
|
234
345
|
elif descending_overlap and not ascending_overlap:
|
235
|
-
|
346
|
+
orbit_choice = "DESCENDING"
|
347
|
+
reason = "Only orbit fully covering the requested area."
|
348
|
+
|
349
|
+
# Rule 2: Prefer an orbit with a maximum temporal gap under a predefined threshold
|
350
|
+
elif ascending_gap_too_large and not descending_gap_too_large:
|
351
|
+
orbit_choice = "DESCENDING"
|
352
|
+
reason = (
|
353
|
+
"Only orbit with temporal gap under the threshold. "
|
354
|
+
f"{areas['DESCENDING']['max_temporal_gap']} days < {max_temporal_gap} days"
|
355
|
+
)
|
356
|
+
elif descending_gap_too_large and not ascending_gap_too_large:
|
357
|
+
orbit_choice = "ASCENDING"
|
358
|
+
reason = (
|
359
|
+
"Only orbit with temporal gap under the threshold. "
|
360
|
+
f"{areas['ASCENDING']['max_temporal_gap']} days < {max_temporal_gap} days"
|
361
|
+
)
|
362
|
+
# Rule 3: Prefer the orbit that covers the most area of intersection
|
363
|
+
# for the available products
|
236
364
|
elif ascending_overlap and descending_overlap:
|
237
365
|
ascending_cover_area = areas["ASCENDING"]["area"]
|
238
366
|
descending_cover_area = areas["DESCENDING"]["area"]
|
239
367
|
|
240
368
|
# Selects the orbit state that covers the most area
|
241
369
|
if ascending_cover_area > descending_cover_area:
|
242
|
-
|
370
|
+
orbit_choice = "ASCENDING"
|
371
|
+
reason = (
|
372
|
+
"Orbit has more cumulative intersected area. "
|
373
|
+
f"{ascending_cover_area} > {descending_cover_area}"
|
374
|
+
)
|
243
375
|
else:
|
244
|
-
|
376
|
+
reason = (
|
377
|
+
"Orbit has more cumulative intersected area. "
|
378
|
+
f"{descending_cover_area} > {ascending_cover_area}"
|
379
|
+
)
|
380
|
+
orbit_choice = "DESCENDING"
|
245
381
|
|
246
|
-
|
247
|
-
"
|
248
|
-
|
382
|
+
if orbit_choice is not None:
|
383
|
+
_log.info(f"Selected orbit state: {orbit_choice}. Reason: {reason}")
|
384
|
+
return orbit_choice
|
385
|
+
raise UncoveredS1Exception("Failed to select suitable Sentinel-1 orbit.")
|
@@ -0,0 +1,125 @@
|
|
1
|
+
"""Utility function to split a STAC collection into multiple STAC collections based on CRS.
|
2
|
+
Requires the "proj:epsg" property to be present in all the STAC items.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Iterator, Union
|
8
|
+
|
9
|
+
import pystac
|
10
|
+
|
11
|
+
|
12
|
+
def _extract_epsg_from_stac_item(stac_item: pystac.Item) -> int:
|
13
|
+
"""
|
14
|
+
Extract the EPSG code from a STAC item.
|
15
|
+
|
16
|
+
Parameters:
|
17
|
+
stac_item (pystac.Item): The STAC item.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
int: The EPSG code.
|
21
|
+
|
22
|
+
Raises:
|
23
|
+
KeyError: If the "proj:epsg" property is missing from the STAC item.
|
24
|
+
"""
|
25
|
+
|
26
|
+
try:
|
27
|
+
epsg_code = stac_item.properties["proj:epsg"]
|
28
|
+
return epsg_code
|
29
|
+
except KeyError:
|
30
|
+
raise KeyError("The 'proj:epsg' property is missing from the STAC item.")
|
31
|
+
|
32
|
+
|
33
|
+
def _get_items_by_epsg(
|
34
|
+
collection: pystac.Collection,
|
35
|
+
) -> Iterator[tuple[int, pystac.Item]]:
|
36
|
+
"""
|
37
|
+
Generator function that yields items grouped by their EPSG code.
|
38
|
+
|
39
|
+
Parameters:
|
40
|
+
collection (pystac.Collection): The STAC collection.
|
41
|
+
|
42
|
+
Yields:
|
43
|
+
tuple[int, pystac.Item]: EPSG code and corresponding STAC item.
|
44
|
+
"""
|
45
|
+
for item in collection.get_all_items():
|
46
|
+
epsg = _extract_epsg_from_stac_item(item)
|
47
|
+
yield epsg, item
|
48
|
+
|
49
|
+
|
50
|
+
def _create_collection_skeleton(
|
51
|
+
collection: pystac.Collection, epsg: int
|
52
|
+
) -> pystac.Collection:
|
53
|
+
"""
|
54
|
+
Create a skeleton for a new STAC collection with a given EPSG code.
|
55
|
+
|
56
|
+
Parameters:
|
57
|
+
collection (pystac.Collection): The original STAC collection.
|
58
|
+
epsg (int): The EPSG code.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
pystac.Collection: The skeleton of the new STAC collection.
|
62
|
+
"""
|
63
|
+
new_collection = pystac.Collection(
|
64
|
+
id=f"{collection.id}_{epsg}",
|
65
|
+
description=f"{collection.description} Containing only items with EPSG code {epsg}",
|
66
|
+
extent=collection.extent.clone(),
|
67
|
+
summaries=collection.summaries,
|
68
|
+
license=collection.license,
|
69
|
+
stac_extensions=collection.stac_extensions,
|
70
|
+
)
|
71
|
+
if "item_assets" in collection.extra_fields:
|
72
|
+
item_assets_extension = pystac.extensions.item_assets.ItemAssetsExtension.ext(
|
73
|
+
collection
|
74
|
+
)
|
75
|
+
|
76
|
+
new_item_assets_extension = (
|
77
|
+
pystac.extensions.item_assets.ItemAssetsExtension.ext(
|
78
|
+
new_collection, add_if_missing=True
|
79
|
+
)
|
80
|
+
)
|
81
|
+
|
82
|
+
new_item_assets_extension.item_assets = item_assets_extension.item_assets
|
83
|
+
return new_collection
|
84
|
+
|
85
|
+
|
86
|
+
def split_collection_by_epsg(
|
87
|
+
collection: Union[str, Path, pystac.Collection], output_dir: Union[str, Path]
|
88
|
+
):
|
89
|
+
"""
|
90
|
+
Split a STAC collection into multiple STAC collections based on EPSG code.
|
91
|
+
|
92
|
+
Parameters
|
93
|
+
----------
|
94
|
+
collection: Union[str, Path, pystac.Collection]
|
95
|
+
A collection of STAC items or a path to a STAC collection.
|
96
|
+
output_dir: Union[str, Path]
|
97
|
+
The directory where the split STAC collections will be saved.
|
98
|
+
"""
|
99
|
+
|
100
|
+
if not isinstance(collection, pystac.Collection):
|
101
|
+
collection = Path(collection)
|
102
|
+
output_dir = Path(output_dir)
|
103
|
+
os.makedirs(output_dir, exist_ok=True)
|
104
|
+
|
105
|
+
try:
|
106
|
+
collection = pystac.read_file(collection)
|
107
|
+
except pystac.STACError:
|
108
|
+
print("Please provide a path to a valid STAC collection.")
|
109
|
+
return
|
110
|
+
|
111
|
+
collections_by_epsg = {}
|
112
|
+
|
113
|
+
for epsg, item in _get_items_by_epsg(collection):
|
114
|
+
if epsg not in collections_by_epsg:
|
115
|
+
collections_by_epsg[epsg] = _create_collection_skeleton(collection, epsg)
|
116
|
+
|
117
|
+
# Add item to the corresponding collection
|
118
|
+
collections_by_epsg[epsg].add_item(item)
|
119
|
+
|
120
|
+
# Write each collection to disk
|
121
|
+
for epsg, new_collection in collections_by_epsg.items():
|
122
|
+
new_collection.update_extent_from_items() # Update extent based on added items
|
123
|
+
collection_path = output_dir / f"collection-{epsg}"
|
124
|
+
new_collection.normalize_hrefs(str(collection_path))
|
125
|
+
new_collection.save()
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: openeo_gfmap
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: OpenEO General Framework for Mapping
|
5
5
|
Project-URL: Homepage, https://github.com/Open-EO/openeo-gfmap
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Open-EO/openeo-gfmap/issues
|
@@ -13,14 +13,15 @@ Requires-Dist: cftime
|
|
13
13
|
Requires-Dist: fastparquet
|
14
14
|
Requires-Dist: geojson>=3.0.0
|
15
15
|
Requires-Dist: geopandas
|
16
|
-
Requires-Dist: h3
|
16
|
+
Requires-Dist: h3==4.1.0
|
17
17
|
Requires-Dist: h5netcdf>=1.2.0
|
18
18
|
Requires-Dist: netcdf4
|
19
19
|
Requires-Dist: numpy<2.0.0
|
20
20
|
Requires-Dist: onnxruntime
|
21
|
-
Requires-Dist: openeo
|
21
|
+
Requires-Dist: openeo<=0.35
|
22
22
|
Requires-Dist: pyarrow
|
23
23
|
Requires-Dist: rasterio
|
24
|
+
Requires-Dist: s2sphere==0.2.*
|
24
25
|
Requires-Dist: scipy
|
25
26
|
Provides-Extra: dev
|
26
27
|
Requires-Dist: matplotlib>=3.3.0; extra == 'dev'
|
@@ -4,37 +4,37 @@ openeo_gfmap/metadata.py,sha256=WlJ3zy78ff1E65HDbw7kOuYamqyvfV0BCNrv_ZvXX4Y,762
|
|
4
4
|
openeo_gfmap/spatial.py,sha256=y1Gk9yM1j2eod127Pthn7SKxY37EFbzvPX0znqHgnMw,1353
|
5
5
|
openeo_gfmap/temporal.py,sha256=qhKCgSoOc0CrscPTru-d7acaxsCVhftyYrb_8UVU1S4,583
|
6
6
|
openeo_gfmap/features/__init__.py,sha256=UtQUPnglF9uURn9FqtegnazuE4_CgQP6a2Cdx1TOuZ0,419
|
7
|
-
openeo_gfmap/features/feature_extractor.py,sha256=
|
8
|
-
openeo_gfmap/fetching/__init__.py,sha256=
|
9
|
-
openeo_gfmap/fetching/commons.py,sha256=
|
7
|
+
openeo_gfmap/features/feature_extractor.py,sha256=ApTCCbD1-S4VOOSEZh9i-Gqxso87xwe_z7CN35fP15A,14719
|
8
|
+
openeo_gfmap/fetching/__init__.py,sha256=KFDwqKTwXUDhFqPqeaIg5uCL2xp7lXmNzcbAph-EU1c,938
|
9
|
+
openeo_gfmap/fetching/commons.py,sha256=7kr34Lq3ZxKWQXJrtAHFJYkc93y6LHGfTe1jn7Adr64,7656
|
10
10
|
openeo_gfmap/fetching/fetching.py,sha256=dHeOMzN6OPgD8YFfZtcCzEOwQqo47IeBgIdS2mrx3MY,3674
|
11
|
-
openeo_gfmap/fetching/generic.py,sha256=
|
12
|
-
openeo_gfmap/fetching/
|
13
|
-
openeo_gfmap/fetching/
|
14
|
-
openeo_gfmap/fetching/s2.py,sha256=Ulmjn57pCA2GyKV-OHnbTaLenMUK3_QpY06fDw3STaU,7699
|
11
|
+
openeo_gfmap/fetching/generic.py,sha256=ojSO52cnLsWpC6FAnLRoXxfQmTiC839DzFH8MAok2B8,5851
|
12
|
+
openeo_gfmap/fetching/s1.py,sha256=Ek9Ek-GExyKdb-9Ijja6I-izOmVvPY2C9w9gyyGGjII,6360
|
13
|
+
openeo_gfmap/fetching/s2.py,sha256=ytjrZiZIwXxrdiky2V0bAKLBU9Dpaa5b2XsHvI6jl1M,7718
|
15
14
|
openeo_gfmap/inference/__init__.py,sha256=M6NnKGYCpHNYmRL9OkHi5GmfCtWoJ0wCNR6VXRuDgjE,165
|
16
|
-
openeo_gfmap/inference/model_inference.py,sha256
|
15
|
+
openeo_gfmap/inference/model_inference.py,sha256=0qPUgrjI1hy5ZnyGwuuvvw5oxnMGdgvvu9Go6-e9LZQ,12550
|
17
16
|
openeo_gfmap/manager/__init__.py,sha256=2bckkPiDQBgoBWD9spk1BKXy2UGkWKe50A3HmIwmqrA,795
|
18
|
-
openeo_gfmap/manager/job_manager.py,sha256
|
19
|
-
openeo_gfmap/manager/job_splitters.py,sha256=
|
17
|
+
openeo_gfmap/manager/job_manager.py,sha256=-MZJBfF_wV94FejoYbFPNviEQx3jLmJXb6XLeHg7egE,27221
|
18
|
+
openeo_gfmap/manager/job_splitters.py,sha256=FU-1Wuwr1Xu8vEwuEZACPRrgBhgeLm2DLx6E6QSNkdg,10826
|
20
19
|
openeo_gfmap/preprocessing/__init__.py,sha256=-kJAy_WY4o8oqziRozcUuXtuGIM0IOvTCF6agTUgRWA,619
|
21
20
|
openeo_gfmap/preprocessing/cloudmasking.py,sha256=d280H5fByjNbCVZHjPn_dUatNI-ejphu4A75sUVoRqo,10029
|
22
21
|
openeo_gfmap/preprocessing/compositing.py,sha256=Jp9Ku5JpU7TJ4DYGc6YuqMeP1Ip7zns7NguC17BtFyA,2526
|
23
22
|
openeo_gfmap/preprocessing/interpolation.py,sha256=VVD483NoC0KYUSh28XaBNZzNaybQjdyCN8rXyXy2W9E,327
|
24
|
-
openeo_gfmap/preprocessing/sar.py,sha256=
|
23
|
+
openeo_gfmap/preprocessing/sar.py,sha256=XtOIlBrX3o2DlrGHBksHYWzOdzhkZMQT2rp5LxDbdMQ,1391
|
25
24
|
openeo_gfmap/preprocessing/scaling.py,sha256=oUNhykVC41Je3E_men_-PikAKNwYhYbwN9J1_Ru8Zi4,2121
|
26
25
|
openeo_gfmap/preprocessing/udf_cldmask.py,sha256=WqqFLBK5rIQPkb_dlgUWWSzicsPtVSthaIef40FHKJA,1162
|
27
26
|
openeo_gfmap/preprocessing/udf_rank.py,sha256=n2gSIY2ZHVVr9wJx1Bs2HtmvScAkz2NqhjxUM-iIKM0,1438
|
28
27
|
openeo_gfmap/preprocessing/udf_score.py,sha256=L1d5do1lIRJFLWqbuSbXPdwR-hPoSZqVE8ffVtG5kI0,3330
|
29
28
|
openeo_gfmap/stac/__init__.py,sha256=kVMJ9hrN4MjcRCOgRDCj5TfAWRXe0GHu2gJQjG-dS4Y,59
|
30
|
-
openeo_gfmap/stac/constants.py,sha256=
|
31
|
-
openeo_gfmap/utils/__init__.py,sha256=
|
29
|
+
openeo_gfmap/stac/constants.py,sha256=O1bcijRBj6YRqR_aAcYO5JzJg7mdzhzUSm4vKnxMbtQ,1485
|
30
|
+
openeo_gfmap/utils/__init__.py,sha256=UDwkWUwsnV6ZLXeaJKOCos-MDG2ZaIFyg8s0IiRVtng,997
|
32
31
|
openeo_gfmap/utils/build_df.py,sha256=OPmD_Onkl9ybYIiLxmU_GmanP8xD71F1ZybJc7xQmns,1515
|
33
|
-
openeo_gfmap/utils/catalogue.py,sha256
|
32
|
+
openeo_gfmap/utils/catalogue.py,sha256=sgiXbRfywY77HSdQnnJ9eKoadc4TKQVa-uuaRadgAOw,13642
|
34
33
|
openeo_gfmap/utils/intervals.py,sha256=V6l3ofww50fN_pvWC4NuGQ2ZsyGdhAlTZTiRcC0foVE,2395
|
35
34
|
openeo_gfmap/utils/netcdf.py,sha256=KkAAxnq-ZCMjDMd82638noYwxqNpMsnpiU04Q-qX26A,698
|
35
|
+
openeo_gfmap/utils/split_stac.py,sha256=asjT0jx6ic8GJFqqAisaWxOvQ_suSRv4sxyFOyHFvpI,3895
|
36
36
|
openeo_gfmap/utils/tile_processing.py,sha256=QZ9bi5tPmyTVyyNvFZgd26s5dSnMl1grTKq2veK1C90,2068
|
37
|
-
openeo_gfmap-0.
|
38
|
-
openeo_gfmap-0.
|
39
|
-
openeo_gfmap-0.
|
40
|
-
openeo_gfmap-0.
|
37
|
+
openeo_gfmap-0.3.0.dist-info/METADATA,sha256=9khjPclTO13jDj2FFvOiaznGb60XCWYN8D-TdK1-mjE,4322
|
38
|
+
openeo_gfmap-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
39
|
+
openeo_gfmap-0.3.0.dist-info/licenses/LICENSE,sha256=aUuGpjieWiscTNtyLcSaeVsJ4pb6J9c4wUq1bR0e4t4,11349
|
40
|
+
openeo_gfmap-0.3.0.dist-info/RECORD,,
|
openeo_gfmap/fetching/meteo.py
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
"""Meteo data fetchers."""
|
2
|
-
|
3
|
-
from functools import partial
|
4
|
-
|
5
|
-
import openeo
|
6
|
-
from geojson import GeoJSON
|
7
|
-
|
8
|
-
from openeo_gfmap import (
|
9
|
-
Backend,
|
10
|
-
BackendContext,
|
11
|
-
FetchType,
|
12
|
-
SpatialContext,
|
13
|
-
TemporalContext,
|
14
|
-
)
|
15
|
-
from openeo_gfmap.fetching import CollectionFetcher
|
16
|
-
from openeo_gfmap.fetching.commons import convert_band_names
|
17
|
-
from openeo_gfmap.fetching.generic import (
|
18
|
-
_get_generic_fetcher,
|
19
|
-
_get_generic_processor,
|
20
|
-
_load_collection,
|
21
|
-
)
|
22
|
-
|
23
|
-
WEATHER_MAPPING_TERRASCOPE = {
|
24
|
-
"dewpoint-temperature": "AGERA5-DEWTEMP",
|
25
|
-
"precipitation-flux": "AGERA5-PRECIP",
|
26
|
-
"solar-radiation-flux": "AGERA5-SOLRAD",
|
27
|
-
"temperature-max": "AGERA5-TMAX",
|
28
|
-
"temperature-mean": "AGERA5-TMEAN",
|
29
|
-
"temperature-min": "AGERA5-TMIN",
|
30
|
-
"vapour-pressure": "AGERA5-VAPOUR",
|
31
|
-
"wind-speed": "AGERA5-WIND",
|
32
|
-
}
|
33
|
-
|
34
|
-
WEATHER_MAPPING_STAC = {
|
35
|
-
"dewpoint_temperature_mean": "AGERA5-DEWTEMP",
|
36
|
-
"total_precipitation": "AGERA5-PRECIP",
|
37
|
-
"solar_radiataion_flux": "AGERA5-SOLRAD",
|
38
|
-
"2m_temperature_max": "AGERA5-TMAX",
|
39
|
-
"2m_temperature_mean": "AGERA5-TMEAN",
|
40
|
-
"2m_temperature_min": "AGERA5-TMIN",
|
41
|
-
"vapour_pressure": "AGERA5-VAPOUR",
|
42
|
-
"wind_speed": "AGERA5-WIND",
|
43
|
-
}
|
44
|
-
|
45
|
-
|
46
|
-
def stac_fetcher(
|
47
|
-
connection: openeo.Connection,
|
48
|
-
spatial_extent: SpatialContext,
|
49
|
-
temporal_extent: TemporalContext,
|
50
|
-
bands: list,
|
51
|
-
fetch_type: FetchType,
|
52
|
-
**params,
|
53
|
-
) -> openeo.DataCube:
|
54
|
-
bands = convert_band_names(bands, WEATHER_MAPPING_STAC)
|
55
|
-
|
56
|
-
cube = _load_collection(
|
57
|
-
connection,
|
58
|
-
bands,
|
59
|
-
"https://stac.openeo.vito.be/collections/agera5_daily",
|
60
|
-
spatial_extent,
|
61
|
-
temporal_extent,
|
62
|
-
fetch_type,
|
63
|
-
is_stac=True,
|
64
|
-
**params,
|
65
|
-
)
|
66
|
-
|
67
|
-
if isinstance(spatial_extent, GeoJSON) and fetch_type == FetchType.POLYGON:
|
68
|
-
cube = cube.filter_spatial(spatial_extent)
|
69
|
-
|
70
|
-
return cube
|
71
|
-
|
72
|
-
|
73
|
-
METEO_BACKEND_MAP = {
|
74
|
-
Backend.TERRASCOPE: {
|
75
|
-
"fetch": partial(
|
76
|
-
_get_generic_fetcher,
|
77
|
-
collection_name="AGERA5",
|
78
|
-
band_mapping=WEATHER_MAPPING_TERRASCOPE,
|
79
|
-
),
|
80
|
-
"preprocessor": partial(
|
81
|
-
_get_generic_processor,
|
82
|
-
collection_name="AGERA5",
|
83
|
-
band_mapping=WEATHER_MAPPING_TERRASCOPE,
|
84
|
-
),
|
85
|
-
},
|
86
|
-
Backend.CDSE: {
|
87
|
-
"fetch": stac_fetcher,
|
88
|
-
"preprocessor": partial(
|
89
|
-
_get_generic_processor,
|
90
|
-
collection_name="AGERA5",
|
91
|
-
band_mapping=WEATHER_MAPPING_STAC,
|
92
|
-
),
|
93
|
-
},
|
94
|
-
Backend.CDSE_STAGING: {
|
95
|
-
"fetch": stac_fetcher,
|
96
|
-
"preprocessor": partial(
|
97
|
-
_get_generic_processor,
|
98
|
-
collection_name="AGERA5",
|
99
|
-
band_mapping=WEATHER_MAPPING_STAC,
|
100
|
-
),
|
101
|
-
},
|
102
|
-
Backend.FED: {
|
103
|
-
"fetch": stac_fetcher,
|
104
|
-
"preprocessor": partial(
|
105
|
-
_get_generic_processor,
|
106
|
-
collection_name="AGERA5",
|
107
|
-
band_mapping=WEATHER_MAPPING_STAC,
|
108
|
-
),
|
109
|
-
},
|
110
|
-
}
|
111
|
-
|
112
|
-
|
113
|
-
def build_meteo_extractor(
|
114
|
-
backend_context: BackendContext,
|
115
|
-
bands: list,
|
116
|
-
fetch_type: FetchType,
|
117
|
-
**params,
|
118
|
-
) -> CollectionFetcher:
|
119
|
-
backend_functions = METEO_BACKEND_MAP.get(backend_context.backend)
|
120
|
-
|
121
|
-
fetcher, preprocessor = (
|
122
|
-
partial(backend_functions["fetch"], fetch_type=fetch_type),
|
123
|
-
backend_functions["preprocessor"](fetch_type=fetch_type),
|
124
|
-
)
|
125
|
-
|
126
|
-
return CollectionFetcher(backend_context, bands, fetcher, preprocessor, **params)
|
File without changes
|