eodash_catalog 0.0.10__py3-none-any.whl → 0.0.12__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.

Potentially problematic release.


This version of eodash_catalog might be problematic. Click here for more details.

@@ -1,51 +1,62 @@
1
+ import importlib
1
2
  import json
2
3
  import os
4
+ import sys
5
+ import uuid
6
+ from collections.abc import Callable
3
7
  from datetime import datetime, timedelta
4
8
  from itertools import groupby
5
9
  from operator import itemgetter
6
10
 
7
11
  import requests
8
12
  from dateutil import parser
9
- from pystac import (
10
- Item,
11
- Link,
12
- SpatialExtent,
13
- Summaries,
14
- )
13
+ from pystac import Catalog, Collection, Item, Link, SpatialExtent, Summaries
15
14
  from pystac_client import Client
16
15
 
17
16
  from eodash_catalog.sh_endpoint import get_SH_token
18
17
  from eodash_catalog.stac_handling import (
19
18
  add_collection_information,
20
19
  add_example_info,
21
- get_or_create_collection_and_times,
20
+ get_collection_times_from_config,
21
+ get_or_create_collection,
22
22
  )
23
23
  from eodash_catalog.thumbnails import generate_thumbnail
24
24
  from eodash_catalog.utils import (
25
+ Options,
25
26
  create_geojson_point,
26
27
  generate_veda_cog_link,
27
28
  retrieveExtentFromWMSWMTS,
28
29
  )
29
30
 
30
31
 
31
- def process_STAC_Datacube_Endpoint(config, endpoint, data, catalog):
32
- collection, _ = get_or_create_collection_and_times(
33
- catalog, data["Name"], data, config, endpoint
32
+ def process_STAC_Datacube_Endpoint(
33
+ catalog_config: dict, endpoint_config: dict, collection_config: dict, catalog: Catalog
34
+ ) -> Collection:
35
+ collection = get_or_create_collection(
36
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
34
37
  )
35
- add_visualization_info(collection, data, endpoint)
38
+ add_visualization_info(collection, collection_config, endpoint_config)
36
39
 
37
- stac_endpoint_url = endpoint["EndPoint"]
38
- if endpoint.get("Name") == "xcube":
39
- stac_endpoint_url = stac_endpoint_url + endpoint.get("StacEndpoint", "")
40
+ stac_endpoint_url = endpoint_config["EndPoint"]
41
+ if endpoint_config.get("Name") == "xcube":
42
+ stac_endpoint_url = stac_endpoint_url + endpoint_config.get("StacEndpoint", "")
40
43
  # assuming /search not implemented
41
44
  api = Client.open(stac_endpoint_url)
42
- coll = api.get_collection(endpoint.get("CollectionId", "datacubes"))
43
- item = coll.get_item(endpoint.get("DatacubeId"))
45
+ collection_id = endpoint_config.get("CollectionId", "datacubes")
46
+ coll = api.get_collection(collection_id)
47
+ if not coll:
48
+ raise ValueError(f"Collection {collection_id} not found in endpoint {endpoint_config}")
49
+ item_id = endpoint_config.get("DatacubeId", "")
50
+ item = coll.get_item(item_id)
51
+ if not item:
52
+ raise ValueError(f"Item {item_id} not found in collection {coll}")
44
53
  # slice a datacube along temporal axis to individual items, selectively adding properties
45
54
  dimensions = item.properties.get("cube:dimensions", {})
46
- variables = item.properties.get("cube:variables")
47
- if endpoint.get("Variable") not in variables:
48
- raise Exception(f'Variable {endpoint.get("Variable")} not found in datacube {variables}')
55
+ variables = item.properties.get("cube:variables", {})
56
+ if endpoint_config.get("Variable") not in variables:
57
+ raise Exception(
58
+ f'Variable {endpoint_config.get("Variable")} not found in datacube {variables}'
59
+ )
49
60
  time_dimension = "time"
50
61
  for k, v in dimensions.items():
51
62
  if v.get("type") == "temporal":
@@ -70,27 +81,34 @@ def process_STAC_Datacube_Endpoint(config, endpoint, data, catalog):
70
81
  else:
71
82
  link.extra_fields["start_datetime"] = item.properties["start_datetime"]
72
83
  link.extra_fields["end_datetime"] = item.properties["end_datetime"]
73
- unit = variables.get(endpoint.get("Variable")).get("unit")
74
- if unit and "yAxis" not in data:
75
- data["yAxis"] = unit
84
+ unit = variables.get(endpoint_config.get("Variable")).get("unit")
85
+ if unit and "yAxis" not in collection_config:
86
+ collection_config["yAxis"] = unit
76
87
  collection.update_extent_from_items()
77
88
 
78
- add_collection_information(config, collection, data)
89
+ add_collection_information(catalog_config, collection, collection_config)
79
90
 
80
91
  return collection
81
92
 
82
93
 
83
- def handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers=None):
84
- if "Locations" in data:
85
- root_collection, _ = get_or_create_collection_and_times(
86
- catalog, data["Name"], data, config, endpoint
94
+ def handle_STAC_based_endpoint(
95
+ catalog_config: dict,
96
+ endpoint_config: dict,
97
+ collection_config: dict,
98
+ catalog: Catalog,
99
+ options: Options,
100
+ headers=None,
101
+ ) -> Collection:
102
+ if "Locations" in collection_config:
103
+ root_collection = get_or_create_collection(
104
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
87
105
  )
88
- for location in data["Locations"]:
106
+ for location in collection_config["Locations"]:
89
107
  if "FilterDates" in location:
90
108
  collection = process_STACAPI_Endpoint(
91
- config=config,
92
- endpoint=endpoint,
93
- data=data,
109
+ catalog_config=catalog_config,
110
+ endpoint_config=endpoint_config,
111
+ collection_config=collection_config,
94
112
  catalog=catalog,
95
113
  options=options,
96
114
  headers=headers,
@@ -100,9 +118,9 @@ def handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers
100
118
  )
101
119
  else:
102
120
  collection = process_STACAPI_Endpoint(
103
- config=config,
104
- endpoint=endpoint,
105
- data=data,
121
+ catalog_config=catalog_config,
122
+ endpoint_config=endpoint_config,
123
+ collection_config=collection_config,
106
124
  catalog=catalog,
107
125
  options=options,
108
126
  headers=headers,
@@ -112,8 +130,8 @@ def handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers
112
130
  # Update identifier to use location as well as title
113
131
  # TODO: should we use the name as id? it provides much more
114
132
  # information in the clients
115
- collection.id = location["Identifier"]
116
- collection.title = (location["Name"],)
133
+ collection.id = location.get("Identifier", uuid.uuid4())
134
+ collection.title = location.get("Name")
117
135
  # See if description should be overwritten
118
136
  if "Description" in location:
119
137
  collection.description = location["Description"]
@@ -121,12 +139,12 @@ def handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers
121
139
  collection.description = location["Name"]
122
140
  # TODO: should we remove all assets from sub collections?
123
141
  link = root_collection.add_child(collection)
124
- latlng = f"{location["Point"][1]},{location["Point"][0]}"
142
+ latlng = f'{location["Point"][1]},{location["Point"][0]}'
125
143
  # Add extra properties we need
126
144
  link.extra_fields["id"] = location["Identifier"]
127
145
  link.extra_fields["latlng"] = latlng
128
146
  link.extra_fields["name"] = location["Name"]
129
- add_example_info(collection, data, endpoint, config)
147
+ add_example_info(collection, collection_config, endpoint_config, catalog_config)
130
148
  if "OverwriteBBox" in location:
131
149
  collection.extent.spatial = SpatialExtent(
132
150
  [
@@ -136,55 +154,55 @@ def handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers
136
154
  root_collection.update_extent_from_items()
137
155
  # Add bbox extents from children
138
156
  for c_child in root_collection.get_children():
139
- root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
157
+ if isinstance(c_child, Collection):
158
+ root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
140
159
  else:
141
- if "Bbox" in endpoint:
160
+ if "Bbox" in endpoint_config:
142
161
  root_collection = process_STACAPI_Endpoint(
143
- config=config,
144
- endpoint=endpoint,
145
- data=data,
162
+ catalog_config=catalog_config,
163
+ endpoint_config=endpoint_config,
164
+ collection_config=collection_config,
146
165
  catalog=catalog,
147
166
  options=options,
148
167
  headers=headers,
149
- bbox=",".join(map(str, endpoint["Bbox"])),
168
+ bbox=",".join(map(str, endpoint_config["Bbox"])),
150
169
  )
151
170
  else:
152
171
  root_collection = process_STACAPI_Endpoint(
153
- config=config,
154
- endpoint=endpoint,
155
- data=data,
172
+ catalog_config=catalog_config,
173
+ endpoint_config=endpoint_config,
174
+ collection_config=collection_config,
156
175
  catalog=catalog,
157
176
  options=options,
158
177
  headers=headers,
159
178
  )
160
179
 
161
- add_example_info(root_collection, data, endpoint, config)
180
+ add_example_info(root_collection, collection_config, endpoint_config, catalog_config)
162
181
  return root_collection
163
182
 
164
183
 
165
184
  def process_STACAPI_Endpoint(
166
- config,
167
- endpoint,
168
- data,
169
- catalog,
170
- options,
171
- headers=None,
185
+ catalog_config: dict,
186
+ endpoint_config: dict,
187
+ collection_config: dict,
188
+ catalog: Catalog,
189
+ options: Options,
190
+ headers: dict[str, str] | None = None,
172
191
  bbox=None,
173
- root_collection=None,
174
- filter_dates=None,
175
- ):
192
+ root_collection: Collection | None = None,
193
+ filter_dates: list[str] | None = None,
194
+ ) -> Collection:
176
195
  if headers is None:
177
196
  headers = {}
178
- collection, _ = get_or_create_collection_and_times(
179
- catalog, endpoint["CollectionId"], data, config, endpoint
197
+ collection = get_or_create_collection(
198
+ catalog, endpoint_config["CollectionId"], collection_config, catalog_config, endpoint_config
180
199
  )
181
- # add_visualization_info(collection, data, endpoint)
182
200
 
183
- api = Client.open(endpoint["EndPoint"], headers=headers)
201
+ api = Client.open(endpoint_config["EndPoint"], headers=headers)
184
202
  if bbox is None:
185
- bbox = "-180,-90,180,90"
203
+ bbox = [-180, -90, 180, 90]
186
204
  results = api.search(
187
- collections=[endpoint["CollectionId"]],
205
+ collections=[endpoint_config["CollectionId"]],
188
206
  bbox=bbox,
189
207
  datetime=["1900-01-01T00:00:00Z", "3000-01-01T00:00:00Z"],
190
208
  )
@@ -203,37 +221,41 @@ def process_STACAPI_Endpoint(
203
221
  link = collection.add_item(item)
204
222
  if options.tn:
205
223
  if "cog_default" in item.assets:
206
- generate_thumbnail(item, data, endpoint, item.assets["cog_default"].href)
224
+ generate_thumbnail(
225
+ item, collection_config, endpoint_config, item.assets["cog_default"].href
226
+ )
207
227
  else:
208
- generate_thumbnail(item, data, endpoint)
228
+ generate_thumbnail(item, collection_config, endpoint_config)
209
229
  # Check if we can create visualization link
210
- if "Assets" in endpoint:
211
- add_visualization_info(item, data, endpoint, item.id)
230
+ if "Assets" in endpoint_config:
231
+ add_visualization_info(item, collection_config, endpoint_config, item.id)
212
232
  link.extra_fields["item"] = item.id
213
233
  elif "cog_default" in item.assets:
214
- add_visualization_info(item, data, endpoint, item.assets["cog_default"].href)
234
+ add_visualization_info(
235
+ item, collection_config, endpoint_config, item.assets["cog_default"].href
236
+ )
215
237
  link.extra_fields["cog_href"] = item.assets["cog_default"].href
216
238
  elif item_datetime:
217
239
  time_string = item_datetime.isoformat()[:-6] + "Z"
218
- add_visualization_info(item, data, endpoint, time=time_string)
240
+ add_visualization_info(item, collection_config, endpoint_config, time=time_string)
219
241
  elif "start_datetime" in item.properties and "end_datetime" in item.properties:
220
242
  add_visualization_info(
221
243
  item,
222
- data,
223
- endpoint,
244
+ collection_config,
245
+ endpoint_config,
224
246
  time="{}/{}".format(
225
247
  item.properties["start_datetime"], item.properties["end_datetime"]
226
248
  ),
227
249
  )
228
250
  # If a root collection exists we point back to it from the item
229
- if root_collection is not None:
251
+ if root_collection:
230
252
  item.set_collection(root_collection)
231
253
 
232
254
  # bubble up information we want to the link
233
255
  # it is possible for datetime to be null, if it is start and end datetime have to exist
234
256
  if item_datetime:
235
257
  iso_time = item_datetime.isoformat()[:-6] + "Z"
236
- if endpoint["Name"] == "Sentinel Hub":
258
+ if endpoint_config["Name"] == "Sentinel Hub":
237
259
  # for SH WMS we only save the date (no time)
238
260
  link.extra_fields["datetime"] = iso_date
239
261
  else:
@@ -245,61 +267,74 @@ def process_STACAPI_Endpoint(
245
267
  collection.update_extent_from_items()
246
268
 
247
269
  # replace SH identifier with catalog identifier
248
- collection.id = data["Name"]
249
- add_collection_information(config, collection, data)
270
+ collection.id = collection_config["Name"]
271
+ add_collection_information(catalog_config, collection, collection_config)
250
272
 
251
273
  # Check if we need to overwrite the bbox after update from items
252
- if "OverwriteBBox" in endpoint:
274
+ if "OverwriteBBox" in endpoint_config:
253
275
  collection.extent.spatial = SpatialExtent(
254
276
  [
255
- endpoint["OverwriteBBox"],
277
+ endpoint_config["OverwriteBBox"],
256
278
  ]
257
279
  )
258
280
 
259
281
  return collection
260
282
 
261
283
 
262
- def handle_VEDA_endpoint(config, endpoint, data, catalog, options):
263
- collection = handle_STAC_based_endpoint(config, endpoint, data, catalog, options)
284
+ def handle_VEDA_endpoint(
285
+ catalog_config: dict,
286
+ endpoint_config: dict,
287
+ collection_config: dict,
288
+ catalog: Catalog,
289
+ options: Options,
290
+ ) -> Collection:
291
+ collection = handle_STAC_based_endpoint(
292
+ catalog_config, endpoint_config, collection_config, catalog, options
293
+ )
264
294
  return collection
265
295
 
266
296
 
267
- def handle_collection_only(config, endpoint, data, catalog):
268
- collection, times = get_or_create_collection_and_times(
269
- catalog, data["Name"], data, config, endpoint
297
+ def handle_collection_only(
298
+ catalog_config: dict, endpoint_config: dict, collection_config: dict, catalog: Catalog
299
+ ) -> Collection:
300
+ collection = get_or_create_collection(
301
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
270
302
  )
271
- if len(times) > 0 and not endpoint.get("Disable_Items"):
303
+ times = get_collection_times_from_config(endpoint_config)
304
+ if len(times) > 0 and not endpoint_config.get("Disable_Items"):
272
305
  for t in times:
273
306
  item = Item(
274
307
  id=t,
275
- bbox=endpoint.get("OverwriteBBox"),
308
+ bbox=endpoint_config.get("OverwriteBBox"),
276
309
  properties={},
277
310
  geometry=None,
278
311
  datetime=parser.isoparse(t),
279
312
  )
280
313
  link = collection.add_item(item)
281
314
  link.extra_fields["datetime"] = t
282
- add_collection_information(config, collection, data)
315
+ add_collection_information(catalog_config, collection, collection_config)
283
316
  return collection
284
317
 
285
318
 
286
- def handle_SH_WMS_endpoint(config, endpoint, data, catalog):
319
+ def handle_SH_WMS_endpoint(
320
+ catalog_config: dict, endpoint_config: dict, collection_config: dict, catalog: Catalog
321
+ ) -> Collection:
287
322
  # create collection and subcollections (based on locations)
288
- if "Locations" in data:
289
- root_collection, _ = get_or_create_collection_and_times(
290
- catalog, data["Name"], data, config, endpoint
323
+ if "Locations" in collection_config:
324
+ root_collection = get_or_create_collection(
325
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
291
326
  )
292
- for location in data["Locations"]:
327
+ for location in collection_config["Locations"]:
293
328
  # create and populate location collections based on times
294
329
  # TODO: Should we add some new description per location?
295
330
  location_config = {
296
331
  "Title": location["Name"],
297
332
  "Description": "",
298
333
  }
299
- collection, _ = get_or_create_collection_and_times(
300
- catalog, location["Identifier"], location_config, config, endpoint
334
+ collection = get_or_create_collection(
335
+ catalog, location["Identifier"], location_config, catalog_config, endpoint_config
301
336
  )
302
- collection.extra_fields["endpointtype"] = endpoint["Name"]
337
+ collection.extra_fields["endpointtype"] = endpoint_config["Name"]
303
338
  for time in location["Times"]:
304
339
  item = Item(
305
340
  id=time,
@@ -311,7 +346,7 @@ def handle_SH_WMS_endpoint(config, endpoint, data, catalog):
311
346
  "https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
312
347
  ],
313
348
  )
314
- add_visualization_info(item, data, endpoint, time=time)
349
+ add_visualization_info(item, collection_config, endpoint_config, time=time)
315
350
  item_link = collection.add_item(item)
316
351
  item_link.extra_fields["datetime"] = time
317
352
 
@@ -323,39 +358,44 @@ def handle_SH_WMS_endpoint(config, endpoint, data, catalog):
323
358
  link.extra_fields["country"] = location["Country"]
324
359
  link.extra_fields["city"] = location["Name"]
325
360
  collection.update_extent_from_items()
326
- add_visualization_info(collection, data, endpoint)
361
+ add_visualization_info(collection, collection_config, endpoint_config)
327
362
 
328
363
  root_collection.update_extent_from_items()
329
364
  # Add bbox extents from children
330
365
  for c_child in root_collection.get_children():
331
- root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
366
+ if isinstance(c_child, Collection):
367
+ root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
332
368
  return root_collection
333
369
 
334
370
 
335
- def handle_xcube_endpoint(config, endpoint, data: dict, catalog):
336
- root_collection = process_STAC_Datacube_Endpoint(
337
- config=config,
338
- endpoint=endpoint,
339
- data=data,
371
+ def handle_xcube_endpoint(
372
+ catalog_config: dict, endpoint_config: dict, collection_config: dict, catalog: Catalog
373
+ ) -> Collection:
374
+ collection = process_STAC_Datacube_Endpoint(
375
+ catalog_config=catalog_config,
376
+ endpoint_config=endpoint_config,
377
+ collection_config=collection_config,
340
378
  catalog=catalog,
341
379
  )
342
380
 
343
- add_example_info(root_collection, data, endpoint, config)
344
- return root_collection
381
+ add_example_info(collection, collection_config, endpoint_config, catalog_config)
382
+ return collection
345
383
 
346
384
 
347
- def handle_GeoDB_endpoint(config, endpoint, data: dict, catalog):
348
- collection, _ = get_or_create_collection_and_times(
349
- catalog, endpoint["CollectionId"], data, config, endpoint
385
+ def handle_GeoDB_endpoint(
386
+ catalog_config: dict, endpoint_config: dict, collection_config: dict, catalog: Catalog
387
+ ) -> Collection:
388
+ collection = get_or_create_collection(
389
+ catalog, endpoint_config["CollectionId"], collection_config, catalog_config, endpoint_config
350
390
  )
351
391
  select = "?select=aoi,aoi_id,country,city,time"
352
392
  url = (
353
- endpoint["EndPoint"]
354
- + endpoint["Database"]
355
- + "_{}".format(endpoint["CollectionId"])
393
+ endpoint_config["EndPoint"]
394
+ + endpoint_config["Database"]
395
+ + "_{}".format(endpoint_config["CollectionId"])
356
396
  + select
357
397
  )
358
- if additional_query_parameters := endpoint.get("AdditionalQueryString"):
398
+ if additional_query_parameters := endpoint_config.get("AdditionalQueryString"):
359
399
  url += f"&{additional_query_parameters}"
360
400
  response = json.loads(requests.get(url).text)
361
401
 
@@ -370,7 +410,7 @@ def handle_GeoDB_endpoint(config, endpoint, data: dict, catalog):
370
410
  unique_values = next(iter({v["aoi_id"]: v for v in values}.values()))
371
411
  country = unique_values["country"]
372
412
  city = unique_values["city"]
373
- IdKey = endpoint.get("IdKey", "city")
413
+ IdKey = endpoint_config.get("IdKey", "city")
374
414
  IdValue = unique_values[IdKey]
375
415
  if country not in countries:
376
416
  countries.append(country)
@@ -408,20 +448,20 @@ def handle_GeoDB_endpoint(config, endpoint, data: dict, catalog):
408
448
  link.extra_fields["country"] = country
409
449
  link.extra_fields["city"] = city
410
450
 
411
- if "yAxis" not in data:
451
+ if "yAxis" not in collection_config:
412
452
  # fetch yAxis and store it to data, preventing need to save it per dataset in yml
413
453
  select = "?select=y_axis&limit=1"
414
454
  url = (
415
- endpoint["EndPoint"]
416
- + endpoint["Database"]
417
- + "_{}".format(endpoint["CollectionId"])
455
+ endpoint_config["EndPoint"]
456
+ + endpoint_config["Database"]
457
+ + "_{}".format(endpoint_config["CollectionId"])
418
458
  + select
419
459
  )
420
460
  response = json.loads(requests.get(url).text)
421
461
  yAxis = response[0]["y_axis"]
422
- data["yAxis"] = yAxis
423
- add_collection_information(config, collection, data)
424
- add_example_info(collection, data, endpoint, config)
462
+ collection_config["yAxis"] = yAxis
463
+ add_collection_information(catalog_config, collection, collection_config)
464
+ add_example_info(collection, collection_config, endpoint_config, catalog_config)
425
465
 
426
466
  collection.update_extent_from_items()
427
467
  collection.summaries = Summaries(
@@ -433,34 +473,51 @@ def handle_GeoDB_endpoint(config, endpoint, data: dict, catalog):
433
473
  return collection
434
474
 
435
475
 
436
- def handle_SH_endpoint(config, endpoint, data, catalog, options):
476
+ def handle_SH_endpoint(
477
+ catalog_config: dict,
478
+ endpoint_config: dict,
479
+ collection_config: dict,
480
+ catalog: Catalog,
481
+ options: Options,
482
+ ) -> Collection:
437
483
  token = get_SH_token()
438
484
  headers = {"Authorization": f"Bearer {token}"}
439
- endpoint["EndPoint"] = "https://services.sentinel-hub.com/api/v1/catalog/1.0.0/"
485
+ endpoint_config["EndPoint"] = "https://services.sentinel-hub.com/api/v1/catalog/1.0.0/"
440
486
  # Overwrite collection id with type, such as ZARR or BYOC
441
- if "Type" in endpoint:
442
- endpoint["CollectionId"] = endpoint["Type"] + "-" + endpoint["CollectionId"]
443
- collection = handle_STAC_based_endpoint(config, endpoint, data, catalog, options, headers)
487
+ if "Type" in endpoint_config:
488
+ endpoint_config["CollectionId"] = (
489
+ endpoint_config["Type"] + "-" + endpoint_config["CollectionId"]
490
+ )
491
+ collection = handle_STAC_based_endpoint(
492
+ catalog_config, endpoint_config, collection_config, catalog, options, headers
493
+ )
444
494
  return collection
445
495
 
446
496
 
447
- def handle_WMS_endpoint(config, endpoint, data, catalog, wmts=False):
448
- collection, times = get_or_create_collection_and_times(
449
- catalog, data["Name"], data, config, endpoint
497
+ def handle_WMS_endpoint(
498
+ catalog_config: dict,
499
+ endpoint_config: dict,
500
+ collection_config: dict,
501
+ catalog: Catalog,
502
+ wmts: bool = False,
503
+ ) -> Collection:
504
+ collection = get_or_create_collection(
505
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
450
506
  )
507
+ times = get_collection_times_from_config(endpoint_config)
451
508
  spatial_extent = collection.extent.spatial.to_dict().get("bbox", [-180, -90, 180, 90])[0]
452
- if endpoint.get("Type") != "OverwriteTimes" or not endpoint.get("OverwriteBBox"):
509
+ if endpoint_config.get("Type") != "OverwriteTimes" or not endpoint_config.get("OverwriteBBox"):
453
510
  # some endpoints allow "narrowed-down" capabilities per-layer, which we utilize to not
454
511
  # have to process full service capabilities XML
455
- capabilities_url = endpoint["EndPoint"]
512
+ capabilities_url = endpoint_config["EndPoint"]
456
513
  spatial_extent, times = retrieveExtentFromWMSWMTS(
457
514
  capabilities_url,
458
- endpoint["LayerId"],
459
- version=endpoint.get("Version", "1.1.1"),
515
+ endpoint_config["LayerId"],
516
+ version=endpoint_config.get("Version", "1.1.1"),
460
517
  wmts=wmts,
461
518
  )
462
519
  # Create an item per time to allow visualization in stac clients
463
- if len(times) > 0 and not endpoint.get("Disable_Items"):
520
+ if len(times) > 0 and not endpoint_config.get("Disable_Items"):
464
521
  for t in times:
465
522
  item = Item(
466
523
  id=t,
@@ -472,98 +529,102 @@ def handle_WMS_endpoint(config, endpoint, data, catalog, wmts=False):
472
529
  "https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
473
530
  ],
474
531
  )
475
- add_visualization_info(item, data, endpoint, time=t)
532
+ add_visualization_info(item, collection_config, endpoint_config, time=t)
476
533
  link = collection.add_item(item)
477
534
  link.extra_fields["datetime"] = t
478
535
  collection.update_extent_from_items()
479
536
 
480
537
  # Check if we should overwrite bbox
481
- if "OverwriteBBox" in endpoint:
538
+ if "OverwriteBBox" in endpoint_config:
482
539
  collection.extent.spatial = SpatialExtent(
483
540
  [
484
- endpoint["OverwriteBBox"],
541
+ endpoint_config["OverwriteBBox"],
485
542
  ]
486
543
  )
487
- add_collection_information(config, collection, data)
544
+ add_collection_information(catalog_config, collection, collection_config)
488
545
  return collection
489
546
 
490
547
 
491
- def handle_GeoDB_Tiles_endpoint(config, endpoint, data, catalog):
492
- raise NotImplementedError
493
-
494
-
495
- def generate_veda_tiles_link(endpoint, item):
496
- collection = "collection={}".format(endpoint["CollectionId"])
548
+ def generate_veda_tiles_link(endpoint_config: dict, item: str | None) -> str:
549
+ collection = "collection={}".format(endpoint_config["CollectionId"])
497
550
  assets = ""
498
- for asset in endpoint["Assets"]:
551
+ for asset in endpoint_config["Assets"]:
499
552
  assets += f"&assets={asset}"
500
553
  color_formula = ""
501
- if "ColorFormula" in endpoint:
502
- color_formula = "&color_formula={}".format(endpoint["ColorFormula"])
554
+ if "ColorFormula" in endpoint_config:
555
+ color_formula = "&color_formula={}".format(endpoint_config["ColorFormula"])
503
556
  no_data = ""
504
- if "NoData" in endpoint:
505
- no_data = "&no_data={}".format(endpoint["NoData"])
557
+ if "NoData" in endpoint_config:
558
+ no_data = "&no_data={}".format(endpoint_config["NoData"])
506
559
  item = f"&item={item}" if item else ""
507
560
  target_url = f"https://staging-raster.delta-backend.com/stac/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{collection}{item}{assets}{color_formula}{no_data}"
508
561
  return target_url
509
562
 
510
563
 
511
- def add_visualization_info(stac_object, data, endpoint, file_url=None, time=None):
564
+ def add_visualization_info(
565
+ stac_object: Collection | Item,
566
+ collection_config: dict,
567
+ endpoint_config: dict,
568
+ file_url: str | None = None,
569
+ time: str | None = None,
570
+ ) -> None:
512
571
  # add extension reference
513
- if endpoint["Name"] == "Sentinel Hub" or endpoint["Name"] == "Sentinel Hub WMS":
572
+ if endpoint_config["Name"] == "Sentinel Hub" or endpoint_config["Name"] == "Sentinel Hub WMS":
514
573
  instanceId = os.getenv("SH_INSTANCE_ID")
515
- if "InstanceId" in endpoint:
516
- instanceId = endpoint["InstanceId"]
517
- extra_fields = {
518
- "wms:layers": [endpoint["LayerId"]],
574
+ if "InstanceId" in endpoint_config:
575
+ instanceId = endpoint_config["InstanceId"]
576
+ extra_fields: dict[str, list[str] | dict[str, str]] = {
577
+ "wms:layers": [endpoint_config["LayerId"]],
519
578
  "role": ["data"],
520
579
  }
521
580
  if time is not None:
522
- if endpoint["Name"] == "Sentinel Hub WMS":
581
+ if endpoint_config["Name"] == "Sentinel Hub WMS":
523
582
  # SH WMS for public collections needs time interval, we use full day here
524
583
  datetime_object = datetime.strptime(time, "%Y-%m-%d")
525
584
  start = datetime_object.isoformat()
526
585
  end = (datetime_object + timedelta(days=1) - timedelta(milliseconds=1)).isoformat()
527
586
  time_interval = f"{start}/{end}"
528
587
  extra_fields["wms:dimensions"] = {"TIME": time_interval}
529
- if endpoint["Name"] == "Sentinel Hub":
588
+ if endpoint_config["Name"] == "Sentinel Hub":
530
589
  extra_fields["wms:dimensions"] = {"TIME": time}
531
590
  stac_object.add_link(
532
591
  Link(
533
592
  rel="wms",
534
593
  target=f"https://services.sentinel-hub.com/ogc/wms/{instanceId}",
535
- media_type=(endpoint.get("MimeType", "image/png")),
536
- title=data["Name"],
594
+ media_type=(endpoint_config.get("MimeType", "image/png")),
595
+ title=collection_config["Name"],
537
596
  extra_fields=extra_fields,
538
597
  )
539
598
  )
540
- elif endpoint["Name"] == "WMS":
599
+ elif endpoint_config["Name"] == "WMS":
541
600
  extra_fields = {
542
- "wms:layers": [endpoint["LayerId"]],
601
+ "wms:layers": [endpoint_config["LayerId"]],
543
602
  "role": ["data"],
544
603
  }
545
604
  if time is not None:
546
605
  extra_fields["wms:dimensions"] = {
547
606
  "TIME": time,
548
607
  }
549
- if "Styles" in endpoint:
550
- extra_fields["wms:styles"] = endpoint["Styles"]
608
+ if "Styles" in endpoint_config:
609
+ extra_fields["wms:styles"] = endpoint_config["Styles"]
551
610
  media_type = "image/jpeg"
552
- if "MediaType" in endpoint:
553
- media_type = endpoint["MediaType"]
611
+ if "MediaType" in endpoint_config:
612
+ media_type = endpoint_config["MediaType"]
554
613
  stac_object.add_link(
555
614
  Link(
556
615
  rel="wms",
557
- target=endpoint["EndPoint"],
616
+ target=endpoint_config["EndPoint"],
558
617
  media_type=media_type,
559
- title=data["Name"],
618
+ title=collection_config["Name"],
560
619
  extra_fields=extra_fields,
561
620
  )
562
621
  )
563
- elif endpoint["Name"] == "JAXA_WMTS_PALSAR":
564
- target_url = "{}".format(endpoint.get("EndPoint"))
622
+ elif endpoint_config["Name"] == "JAXA_WMTS_PALSAR":
623
+ target_url = "{}".format(endpoint_config.get("EndPoint"))
565
624
  # custom time just for this special case as a default for collection wmts
566
- extra_fields = {"wmts:layer": endpoint.get("LayerId").replace("{time}", time or "2017")}
625
+ extra_fields = {
626
+ "wmts:layer": endpoint_config.get("LayerId", "").replace("{time}", time or "2017")
627
+ }
567
628
  stac_object.add_link(
568
629
  Link(
569
630
  rel="wmts",
@@ -573,23 +634,23 @@ def add_visualization_info(stac_object, data, endpoint, file_url=None, time=None
573
634
  extra_fields=extra_fields,
574
635
  )
575
636
  )
576
- elif endpoint["Name"] == "xcube":
577
- if endpoint["Type"] == "zarr":
637
+ elif endpoint_config["Name"] == "xcube":
638
+ if endpoint_config["Type"] == "zarr":
578
639
  # either preset ColormapName of left as a template
579
- cbar = endpoint.get("ColormapName", "{cbar}")
640
+ cbar = endpoint_config.get("ColormapName", "{cbar}")
580
641
  # either preset Rescale of left as a template
581
642
  vmin = "{vmin}"
582
643
  vmax = "{vmax}"
583
- if "Rescale" in endpoint:
584
- vmin = endpoint["Rescale"][0]
585
- vmax = endpoint["Rescale"][1]
586
- crs = endpoint.get("Crs", "EPSG:3857")
644
+ if "Rescale" in endpoint_config:
645
+ vmin = endpoint_config["Rescale"][0]
646
+ vmax = endpoint_config["Rescale"][1]
647
+ crs = endpoint_config.get("Crs", "EPSG:3857")
587
648
  target_url = (
588
649
  "{}/tiles/{}/{}/{{z}}/{{y}}/{{x}}" "?crs={}&time={{time}}&vmin={}&vmax={}&cbar={}"
589
650
  ).format(
590
- endpoint["EndPoint"],
591
- endpoint["DatacubeId"],
592
- endpoint["Variable"],
651
+ endpoint_config["EndPoint"],
652
+ endpoint_config["DatacubeId"],
653
+ endpoint_config["Variable"],
593
654
  crs,
594
655
  vmin,
595
656
  vmax,
@@ -603,16 +664,16 @@ def add_visualization_info(stac_object, data, endpoint, file_url=None, time=None
603
664
  title="xcube tiles",
604
665
  )
605
666
  )
606
- elif endpoint["Type"] == "WMTSCapabilities":
607
- target_url = "{}".format(endpoint.get("EndPoint"))
667
+ elif endpoint_config["Type"] == "WMTSCapabilities":
668
+ target_url = "{}".format(endpoint_config.get("EndPoint"))
608
669
  extra_fields = {
609
- "wmts:layer": endpoint.get("LayerId"),
670
+ "wmts:layer": endpoint_config.get("LayerId", ""),
610
671
  "role": ["data"],
611
672
  }
612
673
  dimensions = {}
613
674
  if time is not None:
614
675
  dimensions["time"] = time
615
- if dimensions_config := endpoint.get("Dimensions", {}):
676
+ if dimensions_config := endpoint_config.get("Dimensions", {}):
616
677
  for key, value in dimensions_config.items():
617
678
  dimensions[key] = value
618
679
  if dimensions != {}:
@@ -626,44 +687,80 @@ def add_visualization_info(stac_object, data, endpoint, file_url=None, time=None
626
687
  extra_fields=extra_fields,
627
688
  )
628
689
  )
629
- elif endpoint["Name"] == "VEDA":
630
- if endpoint["Type"] == "cog":
631
- target_url = generate_veda_cog_link(endpoint, file_url)
632
- elif endpoint["Type"] == "tiles":
633
- target_url = generate_veda_tiles_link(endpoint, file_url)
690
+ elif endpoint_config["Name"] == "VEDA":
691
+ if endpoint_config["Type"] == "cog":
692
+ target_url = generate_veda_cog_link(endpoint_config, file_url)
693
+ elif endpoint_config["Type"] == "tiles":
694
+ target_url = generate_veda_tiles_link(endpoint_config, file_url)
634
695
  if target_url:
635
696
  stac_object.add_link(
636
697
  Link(
637
698
  rel="xyz",
638
699
  target=target_url,
639
700
  media_type="image/png",
640
- title=data["Name"],
701
+ title=collection_config["Name"],
641
702
  )
642
703
  )
643
- elif endpoint["Name"] == "GeoDB Vector Tiles":
704
+ elif endpoint_config["Name"] == "GeoDB Vector Tiles":
644
705
  # `${geoserverUrl}${config.layerName}@EPSG%3A${projString}@pbf/{z}/{x}/{-y}.pbf`,
645
706
  # 'geodb_debd884d-92f9-4979-87b6-eadef1139394:GTIF_AT_Gemeinden_3857'
646
707
  target_url = "{}{}:{}_{}@EPSG:3857@pbf/{{z}}/{{x}}/{{-y}}.pbf".format(
647
- endpoint["EndPoint"],
648
- endpoint["Instance"],
649
- endpoint["Database"],
650
- endpoint["CollectionId"],
708
+ endpoint_config["EndPoint"],
709
+ endpoint_config["Instance"],
710
+ endpoint_config["Database"],
711
+ endpoint_config["CollectionId"],
651
712
  )
652
713
  stac_object.add_link(
653
714
  Link(
654
715
  rel="xyz",
655
716
  target=target_url,
656
717
  media_type="application/pbf",
657
- title=data["Name"],
718
+ title=collection_config["Name"],
658
719
  extra_fields={
659
- "description": data["Title"],
660
- "parameters": endpoint["Parameters"],
661
- "matchKey": endpoint["MatchKey"],
662
- "timeKey": endpoint["TimeKey"],
663
- "source": endpoint["Source"],
720
+ "description": collection_config["Title"],
721
+ "parameters": endpoint_config["Parameters"],
722
+ "matchKey": endpoint_config["MatchKey"],
723
+ "timeKey": endpoint_config["TimeKey"],
724
+ "source": endpoint_config["Source"],
664
725
  "role": ["data"],
665
726
  },
666
727
  )
667
728
  )
668
729
  else:
669
730
  print("Visualization endpoint not supported")
731
+
732
+
733
+ def handle_custom_endpoint(
734
+ catalog_config: dict,
735
+ endpoint_config: dict,
736
+ collection_config: dict,
737
+ catalog: Catalog,
738
+ ) -> Collection:
739
+ collection = get_or_create_collection(
740
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
741
+ )
742
+ # invoke 3rd party code and return
743
+ function_path = endpoint_config["Python_Function_Location"]
744
+ module_name, _, func_name = function_path.rpartition(".")
745
+ # add current working directory to sys path
746
+ sys.path.append(os.getcwd())
747
+ try:
748
+ # import configured function
749
+ imported_function: Callable[[Collection, dict, dict, dict], Collection] = getattr(
750
+ importlib.import_module(module_name), func_name
751
+ )
752
+ except ModuleNotFoundError as e:
753
+ print(
754
+ f"""function {func_name} from module {module_name} can not be imported.
755
+ Check if you are specifying relative path inside the
756
+ catalog repository or catalog generator repository."""
757
+ )
758
+ raise e
759
+ # execture the custom handler
760
+ collection = imported_function(
761
+ collection,
762
+ catalog_config,
763
+ endpoint_config,
764
+ collection_config,
765
+ )
766
+ return collection