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.
@@ -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 shapely import unary_union
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
- geoemetries = []
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
- geoemetries.append(shape(product["geometry"]))
33
- return geoemetries
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=100"
52
- f"&dataset=ESA-DATASET&startDate={start_date}&completionDate={end_date}"
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
- response = requests.get(url)
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 s1_area_per_orbitstate(
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
- """Evaluates for both the ascending and descending state orbits the area of interesection
116
- between the given spatio-temporal context and the products available in the backend's
117
- catalogue.
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 in
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, GeoJSON):
202
+ if isinstance(spatial_extent, geojson.FeatureCollection):
135
203
  # Transform geojson into shapely geometry and compute bounds
136
- bounds = shape(spatial_extent).bounds
137
- epsg = 4362
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", bounds, temporal_extent, orbitDirection="ASCENDING"
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 select_S1_orbitstate(
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 that covers the most area of the given spatio-temporal context
209
- for the Sentinel-1 collection.
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 = s1_area_per_orbitstate(backend, spatial_extent, temporal_extent)
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
- return "ASCENDING"
343
+ orbit_choice = "ASCENDING"
344
+ reason = "Only orbit fully covering the requested area."
234
345
  elif descending_overlap and not ascending_overlap:
235
- return "DESCENDING"
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
- return "ASCENDING"
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
- return "DESCENDING"
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
- raise UncoveredS1Exception(
247
- "No product available to fully cover the given spatio-temporal context."
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.3
1
+ Metadata-Version: 2.4
2
2
  Name: openeo_gfmap
3
- Version: 0.1.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=kMXIkb7OqEBDdZGhTZp0gFDtq9Ip_IVwszCoE6HOqWY,14491
8
- openeo_gfmap/fetching/__init__.py,sha256=vuG5qdBEY6TytLjH2NWjyg_2ivHBEX9zvXOwD-MrxzQ,521
9
- openeo_gfmap/fetching/commons.py,sha256=6LBQry1V18jSivhd7pO5XHLzeb0msC85frHThvVnH0E,7636
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=QekJd2-BaEn_ObEaKEwpYTP7jkH5Enax7c7CgdpcRnY,5461
12
- openeo_gfmap/fetching/meteo.py,sha256=QVuyjPjzUzJyiYL3sxwSGA8bTgaSGI3ITkKkcW9WdzQ,3386
13
- openeo_gfmap/fetching/s1.py,sha256=bewQOzP5ClDhVzaKINAY3-IKPiBW7TCuVtUs_BRgSvA,6452
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=-Pf0PHfcO8Y0ok9lsx4Vp2qee19RhrkzHVLVVKTRkVE,12478
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=PIsSBDNURwrunVTjqsxfKEFxi0xlBflQH8sC0j29Zoc,20056
19
- openeo_gfmap/manager/job_splitters.py,sha256=zV0Tr3aEa4WORJgY8Z-rRHqsosj-1pWpPsyBSUnuNwo,5123
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=mYERSzjNJa92GriSCGmDI4lQLxFxRPeMUU3Yebs6NI0,2238
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=Oelktfzq3sE1leMI6_EC_Dt8yMvPA_8I2AeJSKqGxII,1485
31
- openeo_gfmap/utils/__init__.py,sha256=5UUoVq6PaNXar_1J82u-_6KInfuY7uNU4_u2E_Cj_HA,626
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=-QjoxFZ9RIigPjXOEE-LQcLW88aoTxfI3oC0SYR3n68,8073
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.1.0.dist-info/METADATA,sha256=BAThgJ7FwpCz2QLFWYkFiPkbg9UPrUDRGgAXjkq1iPc,4278
38
- openeo_gfmap-0.1.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
39
- openeo_gfmap-0.1.0.dist-info/licenses/LICENSE,sha256=aUuGpjieWiscTNtyLcSaeVsJ4pb6J9c4wUq1bR0e4t4,11349
40
- openeo_gfmap-0.1.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.2
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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)