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