eodash_catalog 0.3.5__py3-none-any.whl → 0.3.20__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,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Daniel Santillan <daniel.santillan@eox.at>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.3.5"
4
+ __version__ = "0.3.20"
@@ -1,3 +1,4 @@
1
+ import copy
1
2
  import importlib
2
3
  import io
3
4
  import json
@@ -8,6 +9,7 @@ from collections.abc import Callable
8
9
  from datetime import datetime, timedelta
9
10
  from itertools import groupby
10
11
  from operator import itemgetter
12
+ from urllib.parse import urlparse
11
13
 
12
14
  import pyarrow.parquet as pq
13
15
  import requests
@@ -19,6 +21,8 @@ from structlog import get_logger
19
21
 
20
22
  from eodash_catalog.sh_endpoint import get_SH_token
21
23
  from eodash_catalog.stac_handling import (
24
+ add_authentication,
25
+ add_base_overlay_info,
22
26
  add_collection_information,
23
27
  add_example_info,
24
28
  add_process_info_child_collection,
@@ -172,7 +176,7 @@ def handle_STAC_based_endpoint(
172
176
  catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
173
177
  )
174
178
  for location in collection_config["Locations"]:
175
- identifier = location.get("Identifier", uuid.uuid4())
179
+ identifier = location.get("Identifier", str(uuid.uuid4()))
176
180
  collection = process_STACAPI_Endpoint(
177
181
  catalog_config=catalog_config,
178
182
  endpoint_config=endpoint_config,
@@ -212,11 +216,13 @@ def handle_STAC_based_endpoint(
212
216
  location["OverwriteBBox"],
213
217
  ]
214
218
  )
219
+ add_collection_information(catalog_config, collection, collection_config)
220
+ add_base_overlay_info(collection, catalog_config, collection_config)
215
221
  update_extents_from_collection_children(root_collection)
216
222
  else:
217
223
  bbox = None
218
- if endpoint_config.get("Bbox"):
219
- bbox = ",".join(map(str, endpoint_config["Bbox"]))
224
+ if endpoint_config.get("OverwriteBBox"):
225
+ bbox = ",".join(map(str, endpoint_config["OverwriteBBox"]))
220
226
  root_collection = process_STACAPI_Endpoint(
221
227
  catalog_config=catalog_config,
222
228
  endpoint_config=endpoint_config,
@@ -391,6 +397,7 @@ def handle_collection_only(
391
397
  properties={},
392
398
  geometry=None,
393
399
  datetime=dt,
400
+ assets={"dummy_asset": Asset(href="")},
394
401
  )
395
402
  link = collection.add_item(item)
396
403
  link.extra_fields["datetime"] = format_datetime_to_isostring_zulu(dt)
@@ -463,12 +470,14 @@ def handle_SH_WMS_endpoint(
463
470
  LOGGER.warn(f"NO datetimes configured for collection: {collection_config['Name']}!")
464
471
  add_visualization_info(collection, collection_config, endpoint_config)
465
472
  add_process_info_child_collection(collection, catalog_config, collection_config)
473
+ add_collection_information(catalog_config, collection, collection_config)
474
+ add_base_overlay_info(collection, catalog_config, collection_config)
466
475
  update_extents_from_collection_children(root_collection)
467
476
  else:
468
477
  # if locations are not provided, treat the collection as a
469
478
  # general proxy to the sentinel hub layer
470
479
  datetimes = get_collection_datetimes_from_config(endpoint_config)
471
- bbox = endpoint_config.get("Bbox", [-180, -85, 180, 85])
480
+ bbox = endpoint_config.get("OverwriteBBox", [-180, -85, 180, 85])
472
481
  items = []
473
482
  for dt in datetimes:
474
483
  item = Item(
@@ -538,6 +547,7 @@ def handle_rasdaman_endpoint(
538
547
  # add_example_info(collection, collection_config, endpoint_config, catalog_config)
539
548
  return collection
540
549
 
550
+
541
551
  def handle_GeoDB_Features_endpoint(
542
552
  catalog_config: dict,
543
553
  endpoint_config: dict,
@@ -546,7 +556,6 @@ def handle_GeoDB_Features_endpoint(
546
556
  catalog: Catalog,
547
557
  options: Options,
548
558
  ) -> Collection:
549
-
550
559
  # ID of collection is data["Name"] instead of CollectionId to be able to
551
560
  # create more STAC collections from one geoDB table
552
561
  collection = get_or_create_collection(
@@ -581,13 +590,9 @@ def handle_GeoDB_Features_endpoint(
581
590
  datetime(time_object.year, time_object.month, time_object.day).date()
582
591
  )
583
592
  case "month":
584
- unique_datetimes.add(
585
- datetime(time_object.year, time_object.month, 1).date()
586
- )
593
+ unique_datetimes.add(datetime(time_object.year, time_object.month, 1).date())
587
594
  case "year":
588
- unique_datetimes.add(
589
- datetime(time_object.year, 1, 1).date()
590
- )
595
+ unique_datetimes.add(datetime(time_object.year, 1, 1).date())
591
596
  case _:
592
597
  # default to day
593
598
  unique_datetimes.add(
@@ -610,10 +615,11 @@ def handle_GeoDB_Features_endpoint(
610
615
  updated_query = endpoint_config["Query"].replace("{{date_time}}", matching_string)
611
616
  assets = {
612
617
  "geodbfeatures": Asset(
613
- href=f"{endpoint_config['EndPoint']}{endpoint_config['Database']}_{endpoint_config['CollectionId']}?{updated_query}",
614
- media_type="application/geodb+json",
615
- roles=["data"],
616
- )}
618
+ href=f"{endpoint_config['EndPoint']}{endpoint_config['Database']}_{endpoint_config['CollectionId']}?{updated_query}",
619
+ media_type="application/geodb+json",
620
+ roles=["data"],
621
+ )
622
+ }
617
623
  item = Item(
618
624
  id=format_datetime_to_isostring_zulu(item_datetime),
619
625
  bbox=endpoint_config.get("OverwriteBBox", [-180, -90, 180, 90]),
@@ -641,13 +647,12 @@ def handle_GeoDB_Features_endpoint(
641
647
  item.add_link(style_link)
642
648
  add_projection_info(endpoint_config, item)
643
649
  items.append(item)
644
-
645
650
  save_items(
646
651
  collection,
647
652
  items,
648
653
  options.outputpath,
649
654
  catalog_config["id"],
650
- f"{coll_path_rel_to_root_catalog}/{collection.id}",
655
+ coll_path_rel_to_root_catalog,
651
656
  options.gp,
652
657
  )
653
658
  add_collection_information(catalog_config, collection, collection_config)
@@ -709,13 +714,13 @@ def handle_GeoDB_endpoint(
709
714
  latlon = unique_values["aoi"]
710
715
  [lat, lon] = [float(x) for x in latlon.split(",")]
711
716
  # create item for unique locations
712
- buff = 0.01
717
+ buff = 0.2
713
718
  bbox = [lon - buff, lat - buff, lon + buff, lat + buff]
714
719
 
715
720
  # create collection per available inputdata information
716
721
  sc_config = {
717
- "Title": city,
718
- "Description": f"{city} - {country}",
722
+ "Title": f"{city} - {collection_config['Name']}",
723
+ "Description": collection_config["Description"],
719
724
  }
720
725
  locations_collection = get_or_create_collection(
721
726
  collection, key, sc_config, catalog_config, endpoint_config
@@ -725,7 +730,33 @@ def handle_GeoDB_endpoint(
725
730
  input_data = []
726
731
  if len(input_data) > 0 or endpoint_config.get("FeatureCollection"):
727
732
  items = []
728
- for v in values:
733
+ content_for_individual_datetimes = values
734
+ if endpoint_config.get("MapTimesCollection"):
735
+ # extract datetimes from another table if configured so and match it based on aoi_id
736
+ # special for E13d
737
+ select = f"?select=time&aoi_id=eq.{key}"
738
+ url = (
739
+ endpoint_config["EndPoint"]
740
+ + endpoint_config["Database"]
741
+ + "_{}".format(endpoint_config["MapTimesCollection"])
742
+ + select
743
+ )
744
+ response = json.loads(requests.get(url).text)
745
+ content_for_individual_datetimes = []
746
+ for response_obj in response:
747
+ time_object = datetime.fromisoformat(response_obj["time"])
748
+ for searched_row in values:
749
+ search_datetime = datetime.fromisoformat(searched_row["time"])
750
+ if (
751
+ search_datetime.month == time_object.month
752
+ and search_datetime.year == time_object.year
753
+ ):
754
+ break
755
+ insert_row = copy.deepcopy(searched_row)
756
+ # overwrite time with one from another collection and save
757
+ insert_row["time"] = response_obj["time"]
758
+ content_for_individual_datetimes.append(insert_row)
759
+ for v in content_for_individual_datetimes:
729
760
  # add items based on inputData fields for each time step available in values
730
761
  first_match: dict = next(
731
762
  (item for item in input_data if item.get("Identifier") == v["input_data"]), None
@@ -814,7 +845,7 @@ def handle_GeoDB_endpoint(
814
845
  rel="wms",
815
846
  target=url,
816
847
  media_type=(endpoint_config.get("MimeType", "image/png")),
817
- title=collection_config["Name"],
848
+ title=first_match["Identifier"],
818
849
  extra_fields=extra_fields,
819
850
  )
820
851
  item.add_link(link)
@@ -887,6 +918,7 @@ def handle_GeoDB_endpoint(
887
918
  link.extra_fields["country"] = country
888
919
  link.extra_fields["name"] = city
889
920
  add_collection_information(catalog_config, locations_collection, collection_config)
921
+ add_base_overlay_info(locations_collection, catalog_config, collection_config)
890
922
 
891
923
  if "yAxis" not in collection_config:
892
924
  # fetch yAxis and store it to data, preventing need to save it per dataset in yml
@@ -925,7 +957,8 @@ def handle_SH_endpoint(
925
957
  ) -> Collection:
926
958
  token = get_SH_token(endpoint_config)
927
959
  headers = {"Authorization": f"Bearer {token}"}
928
- endpoint_config["EndPoint"] = "https://services.sentinel-hub.com/api/v1/catalog/1.0.0/"
960
+ endpoint_url_parts = urlparse(endpoint_config["EndPoint"])
961
+ endpoint_config["EndPoint"] = f"https://{endpoint_url_parts.netloc}/api/v1/catalog/1.0.0/"
929
962
  # Overwrite collection id with type, such as ZARR or BYOC
930
963
  if endpoint_config.get("Type"):
931
964
  endpoint_config["CollectionId"] = (
@@ -962,12 +995,14 @@ def handle_WMS_endpoint(
962
995
  # some endpoints allow "narrowed-down" capabilities per-layer, which we utilize to not
963
996
  # have to process full service capabilities XML
964
997
  capabilities_url = endpoint_config["EndPoint"]
965
- spatial_extent, datetimes = retrieveExtentFromWMSWMTS(
998
+ spatial_extent, datetimes_retrieved = retrieveExtentFromWMSWMTS(
966
999
  capabilities_url,
967
1000
  endpoint_config["LayerId"],
968
1001
  version=endpoint_config.get("Version", "1.1.1"),
969
1002
  wmts=wmts,
970
1003
  )
1004
+ if datetimes_retrieved:
1005
+ datetimes = datetimes_retrieved
971
1006
  # optionally filter time results
972
1007
  if query := endpoint_config.get("Query"):
973
1008
  datetimes = filter_time_entries(datetimes, query)
@@ -1031,6 +1066,10 @@ def generate_veda_tiles_link(endpoint_config: dict, item: str | None) -> str:
1031
1066
  color_formula = ""
1032
1067
  if endpoint_config.get("ColorFormula"):
1033
1068
  color_formula = "&color_formula={}".format(endpoint_config["ColorFormula"])
1069
+ rescale = ""
1070
+ if endpoint_config.get("Rescale"):
1071
+ for rescale in endpoint_config["Rescale"]:
1072
+ rescale += f"&rescale={rescale}"
1034
1073
  no_data = ""
1035
1074
  if endpoint_config.get("NoData"):
1036
1075
  no_data = "&no_data={}".format(endpoint_config["NoData"])
@@ -1038,7 +1077,7 @@ def generate_veda_tiles_link(endpoint_config: dict, item: str | None) -> str:
1038
1077
  target_url_base = endpoint_config["EndPoint"].replace("/stac/", "")
1039
1078
  target_url = (
1040
1079
  f"{target_url_base}/raster/collections/{collection}/items/{item}"
1041
- f"/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{assets}{color_formula}{no_data}"
1080
+ f"/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{assets}{color_formula}{no_data}{rescale}"
1042
1081
  )
1043
1082
  return target_url
1044
1083
 
@@ -1088,9 +1127,10 @@ def add_visualization_info(
1088
1127
 
1089
1128
  if dimensions != {}:
1090
1129
  extra_fields["wms:dimensions"] = dimensions
1130
+ endpoint_url_parts = urlparse(endpoint_config["EndPoint"])
1091
1131
  link = Link(
1092
1132
  rel="wms",
1093
- target=f"https://services.sentinel-hub.com/ogc/wms/{instanceId}",
1133
+ target=f"https://{endpoint_url_parts.netloc}/ogc/wms/{instanceId}",
1094
1134
  media_type=(endpoint_config.get("MimeType", "image/png")),
1095
1135
  title=collection_config["Name"],
1096
1136
  extra_fields=extra_fields,
@@ -1107,6 +1147,14 @@ def add_visualization_info(
1107
1147
  "role": ["data"],
1108
1148
  }
1109
1149
  )
1150
+ if collection_config.get("EodashIdentifier") == "FNF":
1151
+ extra_fields.update(
1152
+ {
1153
+ "wms:layers": endpoint_config.get("LayerId", "").replace(
1154
+ "{time}", (datetimes is not None and str(datetimes[0].year)) or "2020"
1155
+ ),
1156
+ }
1157
+ )
1110
1158
  dimensions = {}
1111
1159
  if dimensions_config := endpoint_config.get("Dimensions", {}):
1112
1160
  for key, value in dimensions_config.items():
@@ -1180,28 +1228,6 @@ def add_visualization_info(
1180
1228
  link,
1181
1229
  )
1182
1230
  stac_object.add_link(link)
1183
- elif endpoint_config["Name"] == "JAXA_WMTS_PALSAR":
1184
- target_url = "{}".format(endpoint_config.get("EndPoint"))
1185
- # custom time just for this special case as a default for collection wmts
1186
- time = None
1187
- if datetimes is not None:
1188
- time = datetimes[0]
1189
- extra_fields.update(
1190
- {
1191
- "wmts:layer": endpoint_config.get("LayerId", "").replace(
1192
- "{time}", (time and str(time.year)) or "2017"
1193
- )
1194
- }
1195
- )
1196
- stac_object.add_link(
1197
- Link(
1198
- rel="wmts",
1199
- target=target_url,
1200
- media_type="image/png",
1201
- title="wmts capabilities",
1202
- extra_fields=extra_fields,
1203
- )
1204
- )
1205
1231
  elif endpoint_config["Name"] == "xcube":
1206
1232
  if endpoint_config["Type"] == "zarr":
1207
1233
  # either preset ColormapName of left as a template
@@ -1460,3 +1486,93 @@ def handle_raw_source(
1460
1486
 
1461
1487
  add_collection_information(catalog_config, collection, collection_config)
1462
1488
  return collection
1489
+
1490
+
1491
+ def handle_vector_tile_source(
1492
+ catalog_config: dict,
1493
+ endpoint_config: dict,
1494
+ collection_config: dict,
1495
+ coll_path_rel_to_root_catalog: str,
1496
+ catalog: Catalog,
1497
+ options: Options,
1498
+ ) -> Collection:
1499
+ collection = get_or_create_collection(
1500
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
1501
+ )
1502
+ coll_path_rel_to_root_catalog = f'{coll_path_rel_to_root_catalog}/{collection_config["Name"]}'
1503
+ if len(endpoint_config.get("TimeEntries", [])) > 0:
1504
+ items = []
1505
+ style_link = None
1506
+ for time_entry in endpoint_config["TimeEntries"]:
1507
+ # create Item for each time entry
1508
+ media_type = "application/vnd.mapbox-vector-tile"
1509
+ style_type = "text/vector-styles"
1510
+ bbox = endpoint_config.get("Bbox", [-180, -85, 180, 85])
1511
+ dt = parse_datestring_to_tz_aware_datetime(time_entry["Time"])
1512
+
1513
+ item = Item(
1514
+ id=format_datetime_to_isostring_zulu(dt),
1515
+ bbox=bbox,
1516
+ properties={},
1517
+ geometry=create_geometry_from_bbox(bbox),
1518
+ datetime=dt,
1519
+ extra_fields={},
1520
+ assets={"dummy_asset": Asset(href="")},
1521
+ )
1522
+ extra_fields_link = {}
1523
+ add_authentication(item, time_entry["Url"], extra_fields_link)
1524
+ # add mapbox vector tile link
1525
+ identifier = str(uuid.uuid4())
1526
+ extra_fields_link["key"] = identifier
1527
+ if vector_tile_id_property := endpoint_config.get("idProperty"):
1528
+ extra_fields_link["idProperty"] = vector_tile_id_property
1529
+ if vector_tile_id_property := endpoint_config.get("layers"):
1530
+ extra_fields_link["layers"] = vector_tile_id_property
1531
+ link = Link(
1532
+ rel="vector-tile",
1533
+ target=time_entry["Url"],
1534
+ media_type=media_type,
1535
+ title=collection_config["Name"],
1536
+ extra_fields=extra_fields_link,
1537
+ )
1538
+ add_projection_info(
1539
+ endpoint_config,
1540
+ link,
1541
+ )
1542
+ item.add_link(link)
1543
+ add_projection_info(
1544
+ endpoint_config,
1545
+ item,
1546
+ )
1547
+ if endpoint_config.get("Attribution"):
1548
+ item.stac_extensions.append(
1549
+ "https://stac-extensions.github.io/attribution/v0.1.0/schema.json"
1550
+ )
1551
+ item.extra_fields["attribution"] = endpoint_config["Attribution"]
1552
+ # add style
1553
+ if ep_st := endpoint_config.get("Style"):
1554
+ style_link = Link(
1555
+ rel="style",
1556
+ target=ep_st
1557
+ if ep_st.startswith("http")
1558
+ else f"{catalog_config['assets_endpoint']}/{ep_st}",
1559
+ media_type=style_type,
1560
+ extra_fields={"links:keys": [identifier]},
1561
+ )
1562
+ item.add_link(style_link)
1563
+ items.append(item)
1564
+
1565
+ save_items(
1566
+ collection,
1567
+ items,
1568
+ options.outputpath,
1569
+ catalog_config["id"],
1570
+ coll_path_rel_to_root_catalog,
1571
+ options.gp,
1572
+ )
1573
+
1574
+ else:
1575
+ LOGGER.warn(f"NO datetimes configured for collection: {collection_config['Name']}!")
1576
+
1577
+ add_collection_information(catalog_config, collection, collection_config)
1578
+ return collection
@@ -24,6 +24,7 @@ from eodash_catalog.endpoints import (
24
24
  handle_raw_source,
25
25
  handle_SH_endpoint,
26
26
  handle_SH_WMS_endpoint,
27
+ handle_vector_tile_source,
27
28
  handle_VEDA_endpoint,
28
29
  handle_WMS_endpoint,
29
30
  handle_xcube_endpoint,
@@ -292,16 +293,14 @@ def process_collection_file(
292
293
  catalog,
293
294
  options,
294
295
  )
295
- elif endpoint_config["Name"] == "JAXA_WMTS_PALSAR":
296
- # somewhat one off creation of individual WMTS layers as individual items
297
- collection = handle_WMS_endpoint(
296
+ elif endpoint_config["Name"] == "VectorTile source":
297
+ collection = handle_vector_tile_source(
298
298
  catalog_config,
299
299
  endpoint_config,
300
300
  collection_config,
301
301
  coll_path_rel_to_root_catalog,
302
302
  catalog,
303
303
  options,
304
- wmts=True,
305
304
  )
306
305
  elif endpoint_config["Name"] == "Collection-only":
307
306
  collection = handle_collection_only(
@@ -360,9 +359,11 @@ def process_collection_file(
360
359
  countries.extend(sub_coll_def["Country"])
361
360
  else:
362
361
  countries.append(sub_coll_def["Country"])
363
- coll_path_rel_to_root_catalog = (
364
- f"{coll_path_rel_to_root_catalog}/{sub_coll_def['Collection']}"
365
- )
362
+ # commented out intentionally, because otherwise paths further down did
363
+ # not match, parquet file was one level deeper
364
+ # coll_path_rel_to_root_catalog = (
365
+ # f"{coll_path_rel_to_root_catalog}/{sub_coll_def['Collection']}"
366
+ # )
366
367
  process_collection_file(
367
368
  catalog_config,
368
369
  "{}/{}".format(options.collectionspath, sub_coll_def["Collection"]),
@@ -421,6 +422,7 @@ def process_collection_file(
421
422
  add_collection_information(catalog_config, parent_collection, collection_config, True)
422
423
  add_process_info(parent_collection, catalog_config, collection_config)
423
424
  update_extents_from_collection_children(parent_collection)
425
+ add_base_overlay_info(parent_collection, catalog_config, collection_config)
424
426
  # Fill summaries for locations
425
427
  parent_collection.summaries = Summaries(
426
428
  {
@@ -456,12 +458,20 @@ def add_to_catalog(
456
458
  link.extra_fields["endpointtype"] = endpoint["Name"]
457
459
  if collection_config.get("Subtitle"):
458
460
  link.extra_fields["subtitle"] = collection_config["Subtitle"]
461
+ if collection_config.get("ShortDescription"):
462
+ link.extra_fields["shortdescription"] = collection_config["ShortDescription"]
459
463
  link.extra_fields["title"] = collection.title
460
464
  if collection_config.get("EodashIdentifier"):
461
465
  link.extra_fields["code"] = collection_config["EodashIdentifier"]
462
466
  link.extra_fields["id"] = collection_config["Name"]
463
467
  if collection_config.get("Themes"):
464
468
  link.extra_fields["themes"] = collection_config["Themes"]
469
+ if collection_config.get("Provider"):
470
+ # get all provider names
471
+ link.extra_fields["providers"] = [
472
+ provider.get("Name") or provider.get("Url")
473
+ for provider in collection_config["Provider"]
474
+ ]
465
475
  # Check for summaries and bubble up info
466
476
  if disable:
467
477
  link.extra_fields["roles"] = ["disable"]
@@ -1,10 +1,10 @@
1
1
  import os
2
2
  import time
3
+ from urllib.parse import urlparse
3
4
 
4
5
  from oauthlib.oauth2 import BackendApplicationClient
5
6
  from requests_oauthlib import OAuth2Session
6
7
 
7
- SH_TOKEN_URL = "https://services.sentinel-hub.com/oauth/token"
8
8
  _token_cache: dict[str, dict] = {}
9
9
 
10
10
 
@@ -24,6 +24,8 @@ def get_SH_token(endpoint_config: dict) -> str:
24
24
  client = BackendApplicationClient(client_id=client_id)
25
25
  oauth = OAuth2Session(client=client)
26
26
  # Get token for the session
27
+ endpoint_url_parts = urlparse(endpoint_config["EndPoint"])
28
+ SH_TOKEN_URL = f"https://{endpoint_url_parts.netloc}/oauth/token"
27
29
  token = oauth.fetch_token(
28
30
  token_url=SH_TOKEN_URL,
29
31
  client_secret=client_secret,
@@ -1,4 +1,6 @@
1
+ import uuid
1
2
  from datetime import datetime, timezone
3
+ from urllib.parse import parse_qs, urlparse
2
4
 
3
5
  import requests
4
6
  import spdx_lookup as lookup
@@ -122,11 +124,14 @@ def create_service_link(endpoint_config: dict, catalog_config: dict) -> Link:
122
124
  return sl
123
125
 
124
126
 
125
- def create_web_map_link(layer_config: dict, role: str) -> Link:
127
+ def create_web_map_link(
128
+ collection: Collection, catalog_config: dict, layer_config: dict, role: str
129
+ ) -> Link:
126
130
  extra_fields = {
127
131
  "roles": [role],
128
132
  "id": layer_config["id"],
129
133
  }
134
+ media_type = (layer_config.get("media_type", "image/png"),)
130
135
  if layer_config.get("default"):
131
136
  extra_fields["roles"].append("default")
132
137
  if layer_config.get("visible"):
@@ -146,12 +151,34 @@ def create_web_map_link(layer_config: dict, role: str) -> Link:
146
151
  extra_fields["wmts:layer"] = layer_config["layer"]
147
152
  if layer_config.get("dimensions"):
148
153
  extra_fields["wmts:dimensions"] = layer_config["dimensions"]
154
+ case "vector-tile":
155
+ identifier = str(uuid.uuid4())
156
+ extra_fields["key"] = identifier
157
+ media_type = "application/vnd.mapbox-vector-tile"
158
+ if vector_tile_id_property := layer_config.get("idProperty"):
159
+ extra_fields["idProperty"] = vector_tile_id_property
160
+ if vector_tile_id_property := layer_config.get("layers"):
161
+ layer_config["layers"] = vector_tile_id_property
162
+ if ep_st := layer_config.get("Style"):
163
+ style_link = Link(
164
+ rel="style",
165
+ target=ep_st
166
+ if ep_st.startswith("http")
167
+ else f"{catalog_config['assets_endpoint']}/{ep_st}",
168
+ media_type="text/vector-styles",
169
+ extra_fields={"links:keys": [identifier]},
170
+ )
171
+ collection.add_link(style_link)
172
+ add_authentication(collection, layer_config["url"], extra_fields)
173
+
149
174
  if layer_config.get("Attribution"):
150
175
  extra_fields["attribution"] = layer_config["Attribution"]
176
+ if layer_config.get("Colorlegend"):
177
+ extra_fields["eox:colorlegend"] = layer_config["Colorlegend"]
151
178
  wml = Link(
152
179
  rel=layer_config["protocol"],
153
180
  target=layer_config["url"],
154
- media_type=layer_config.get("media_type", "image/png"),
181
+ media_type=media_type,
155
182
  title=layer_config["name"],
156
183
  extra_fields=extra_fields,
157
184
  )
@@ -288,6 +315,10 @@ def add_collection_information(
288
315
 
289
316
  if collection_config.get("Subtitle"):
290
317
  collection.extra_fields["subtitle"] = collection_config["Subtitle"]
318
+
319
+ if collection_config.get("ShortDescription"):
320
+ collection.extra_fields["shortdescription"] = collection_config["ShortDescription"]
321
+
291
322
  if collection_config.get("Legend"):
292
323
  collection.add_asset(
293
324
  "legend",
@@ -297,28 +328,42 @@ def add_collection_information(
297
328
  roles=["metadata"],
298
329
  ),
299
330
  )
300
- if collection_config.get("Story"):
301
- collection.add_asset(
302
- "story",
303
- Asset(
304
- href=f'{catalog_config["assets_endpoint"]}/{collection_config["Story"]}',
305
- media_type="text/markdown",
306
- roles=["metadata"],
307
- ),
308
- )
331
+ if stories := collection_config.get("Stories"):
332
+ for story in stories:
333
+ story_url = story.get("Url")
334
+ if not story_url.startswith("http"):
335
+ story_url = f'{catalog_config.get("stories_endpoint")}/{story_url}'
336
+ parsed_url = urlparse(story_url)
337
+ # check if it is URL with a query parameter id=story-identifier
338
+ if parsed_url.query and len(parse_qs(parsed_url.query).get("id")) > 0:
339
+ story_id = parse_qs(parsed_url.query).get("id")[0]
340
+ else:
341
+ story_id = parsed_url.path.rsplit("/")[-1].replace(".md", "").replace(".MD", "")
342
+ collection.add_asset(
343
+ story_id,
344
+ Asset(
345
+ title=story.get("Name"),
346
+ href=story_url,
347
+ media_type="text/markdown",
348
+ roles=["metadata", "story"],
349
+ ),
350
+ )
309
351
  if collection_config.get("Image"):
352
+ # Check if absolute URL or relative path
353
+ if collection_config["Image"].startswith("http"):
354
+ image_url = collection_config["Image"]
355
+ else:
356
+ image_url = f'{catalog_config["assets_endpoint"]}/{collection_config["Image"]}'
310
357
  collection.add_asset(
311
358
  "thumbnail",
312
359
  Asset(
313
- href=f'{catalog_config["assets_endpoint"]}/{collection_config["Image"]}',
360
+ href=image_url,
314
361
  media_type="image/png",
315
362
  roles=["thumbnail"],
316
363
  ),
317
364
  )
318
365
  # Bubble up thumbnail to extra fields
319
- collection.extra_fields["thumbnail"] = (
320
- f'{catalog_config["assets_endpoint"]}/' f'{collection_config["Image"]}'
321
- )
366
+ collection.extra_fields["thumbnail"] = image_url
322
367
  # Add extra fields to collection if available
323
368
  add_extra_fields(collection, collection_config, is_root_collection)
324
369
 
@@ -473,23 +518,31 @@ def add_base_overlay_info(
473
518
  collection: Collection, catalog_config: dict, collection_config: dict
474
519
  ) -> None:
475
520
  # add custom baselayers specially for this indicator
476
- if collection_config.get("BaseLayers"):
521
+ if "BaseLayers" in collection_config:
477
522
  for layer in collection_config["BaseLayers"]:
478
- collection.add_link(create_web_map_link(layer, role="baselayer"))
523
+ collection.add_link(
524
+ create_web_map_link(collection, catalog_config, layer, role="baselayer")
525
+ )
479
526
  # alternatively use default base layers defined
480
527
  elif catalog_config.get("default_base_layers"):
481
528
  base_layers = read_config_file(catalog_config["default_base_layers"])
482
529
  for layer in base_layers:
483
- collection.add_link(create_web_map_link(layer, role="baselayer"))
530
+ collection.add_link(
531
+ create_web_map_link(collection, catalog_config, layer, role="baselayer")
532
+ )
484
533
  # add custom overlays just for this indicator
485
- if collection_config.get("OverlayLayers"):
534
+ if "OverlayLayers" in collection_config:
486
535
  for layer in collection_config["OverlayLayers"]:
487
- collection.add_link(create_web_map_link(layer, role="overlay"))
536
+ collection.add_link(
537
+ create_web_map_link(collection, catalog_config, layer, role="overlay")
538
+ )
488
539
  # check if default overlay layers defined
489
540
  elif catalog_config.get("default_overlay_layers"):
490
541
  overlay_layers = read_config_file(catalog_config["default_overlay_layers"])
491
542
  for layer in overlay_layers:
492
- collection.add_link(create_web_map_link(layer, role="overlay"))
543
+ collection.add_link(
544
+ create_web_map_link(collection, catalog_config, layer, role="overlay")
545
+ )
493
546
 
494
547
 
495
548
  def add_extra_fields(
@@ -579,3 +632,20 @@ def add_projection_info(
579
632
  stac_object.extra_fields["eodash:proj4_def"] = proj
580
633
  else:
581
634
  raise Exception(f"Incorrect type of proj definition {proj}")
635
+
636
+
637
+ def add_authentication(stac_object: Item | Collection | Catalog, url: str, extra_fields_link: dict):
638
+ if "mapbox" in url:
639
+ # add authentication info
640
+ auth_extension = "https://stac-extensions.github.io/authentication/v1.1.0/schema.json"
641
+ if auth_extension not in stac_object.stac_extensions:
642
+ stac_object.stac_extensions.append(auth_extension)
643
+ stac_object.extra_fields["auth:schemes"] = {
644
+ "mapboxauth": {
645
+ "type": "apiKey",
646
+ "name": "access_token",
647
+ "in": "query",
648
+ }
649
+ }
650
+ extra_fields_link["auth:refs"] = ["mapboxauth"]
651
+ pass
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import re
3
3
  from pathlib import Path
4
+ from urllib.parse import urlparse
4
5
 
5
6
  import requests
6
7
  from pystac import (
@@ -46,7 +47,9 @@ def generate_thumbnail(
46
47
  # if it is start and end datetime have to exist
47
48
  if item_datetime:
48
49
  time = format_datetime_to_isostring_zulu(item_datetime)
49
- url = "https://services.sentinel-hub.com/ogc/wms/{}?{}&layers={}&time={}&{}".format(
50
+ endpoint_url_parts = urlparse(endpoint_config["EndPoint"])
51
+ url = "https://{}/ogc/wms/{}?{}&layers={}&time={}&{}".format(
52
+ endpoint_url_parts,
50
53
  instanceId,
51
54
  wms_config,
52
55
  endpoint_config["LayerId"],
eodash_catalog/utils.py CHANGED
@@ -328,6 +328,7 @@ def add_single_item_if_collection_empty(endpoint_config: dict, collection: Colle
328
328
  datetime=datetime(1970, 1, 1, 0, 0, 0, tzinfo=pytztimezone("UTC")),
329
329
  start_datetime=datetime(1970, 1, 1, 0, 0, 0, tzinfo=pytztimezone("UTC")),
330
330
  end_datetime=datetime.now(tz=pytztimezone("UTC")),
331
+ assets={"dummy_asset": Asset(href="")},
331
332
  )
332
333
  collection.add_item(item)
333
334
  if not endpoint_config.get("OverwriteBBox"):
@@ -468,6 +469,11 @@ def extract_extent_from_geoparquet(table) -> tuple[TemporalExtent, SpatialExtent
468
469
  # fallback to start_datetime
469
470
  min_datetime = pc.min(table["start_datetime"]).as_py()
470
471
  max_datetime = pc.max(table["start_datetime"]).as_py()
472
+ # Making sure time extent is timezone aware
473
+ if min_datetime and min_datetime.tzinfo is None:
474
+ min_datetime = min_datetime.replace(tzinfo=timezone.utc)
475
+ if max_datetime and max_datetime.tzinfo is None:
476
+ max_datetime = max_datetime.replace(tzinfo=timezone.utc)
471
477
  temporal = TemporalExtent([min_datetime, max_datetime])
472
478
  geoms = [wkb.loads(g.as_py()) for g in table["geometry"] if g is not None]
473
479
  bbox = sgeom.MultiPolygon(geoms).bounds
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eodash_catalog
3
- Version: 0.3.5
3
+ Version: 0.3.20
4
4
  Summary: This package is intended to help create a compatible STAC catalog for the eodash dashboard client. It supports configuration of multiple endpoint types for information extraction.
5
5
  Project-URL: Documentation, https://github.com/eodash/eodash_catalog#readme
6
6
  Project-URL: Issues, https://github.com/eodash/eodash_catalog/issues
@@ -0,0 +1,14 @@
1
+ eodash_catalog/__about__.py,sha256=btzIjHIy5zbLYBpkxUekQ8gMh9l-paHR_lzdzbCM8SI,138
2
+ eodash_catalog/__init__.py,sha256=_W_9emPYf6FUqc0P8L2SmADx6hGSd7PlQV3yRmCk5uM,115
3
+ eodash_catalog/duration.py,sha256=TBG7v1lCpbYowADK5uJ2M8kPxsvQneFAFi1NIE26dy4,10754
4
+ eodash_catalog/endpoints.py,sha256=uaNtbv4cxMgdoTFyCfd625vTO9DayJxLFmyUtvGEyLU,65290
5
+ eodash_catalog/generate_indicators.py,sha256=Lg_80KAIBuhxOhwORYCbRB5nZ1LoP894miWlNxiQKPs,22862
6
+ eodash_catalog/sh_endpoint.py,sha256=l95NRh__AnpEZBhlzTaGVmBnyX00Sr7RRjwy114nroY,1323
7
+ eodash_catalog/stac_handling.py,sha256=U5gsQH0mfb-9g2hrUMdNwpcyGjhrsGH7CqNEwV_NtfM,28896
8
+ eodash_catalog/thumbnails.py,sha256=9tPnEdwDaScGCMHpDYXayCnAQYoG_E2oIiBFVGvqOz0,2372
9
+ eodash_catalog/utils.py,sha256=2ov62y3OkYX4x2-XpH73FBZ71ulC0ie9m0rSGkuNw4g,24082
10
+ eodash_catalog-0.3.20.dist-info/METADATA,sha256=Y4mcHut0Eb9XT_yIq_lmRRpi6Vh72ZAGb70i_gHbRxs,3020
11
+ eodash_catalog-0.3.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ eodash_catalog-0.3.20.dist-info/entry_points.txt,sha256=kuUQrDG1PtYd8kPjf5XM6H_NtQd9Ozwl0jjiGtAvZSM,87
13
+ eodash_catalog-0.3.20.dist-info/licenses/LICENSE.txt,sha256=oJCW5zQxnFD-J0hGz6Zh5Lkpdk1oAndmWhseTmV224E,1107
14
+ eodash_catalog-0.3.20.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- eodash_catalog/__about__.py,sha256=gHsXXgTiHPfmLt7-0a6FTGSnLZz-WvigY1F818gTV4k,137
2
- eodash_catalog/__init__.py,sha256=_W_9emPYf6FUqc0P8L2SmADx6hGSd7PlQV3yRmCk5uM,115
3
- eodash_catalog/duration.py,sha256=TBG7v1lCpbYowADK5uJ2M8kPxsvQneFAFi1NIE26dy4,10754
4
- eodash_catalog/endpoints.py,sha256=b9rZ0TUyNYcPU4gJGH1Fz2OvT3DaUqhYQMhzWbv1BV8,59839
5
- eodash_catalog/generate_indicators.py,sha256=BoCOtBZYbJA_vRjkwyUYWuIJH91Ddo9RP7Jwzzf4xhU,22334
6
- eodash_catalog/sh_endpoint.py,sha256=XjZsZJ5jfJZLQenSTqUhiUZ5YAu9M9nv2KL1Qv3Be-I,1219
7
- eodash_catalog/stac_handling.py,sha256=xMhUK_gvijs6tL-1ecg28DtWBo4msd5NTYZpYtt7FHo,25877
8
- eodash_catalog/thumbnails.py,sha256=oNbWdRC8KTLUC4PbSMlSaiOeLXfkIpa0j-sOZdn1RGU,2262
9
- eodash_catalog/utils.py,sha256=X40eQNRq8kh7ZATofX12V9EtUpMRdTTfkozBdqkKMJU,23746
10
- eodash_catalog-0.3.5.dist-info/METADATA,sha256=CbHXrC__UkZFpdR6Sis2Ki9Eo71ELJ62frMYnydI48A,3019
11
- eodash_catalog-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- eodash_catalog-0.3.5.dist-info/entry_points.txt,sha256=kuUQrDG1PtYd8kPjf5XM6H_NtQd9Ozwl0jjiGtAvZSM,87
13
- eodash_catalog-0.3.5.dist-info/licenses/LICENSE.txt,sha256=oJCW5zQxnFD-J0hGz6Zh5Lkpdk1oAndmWhseTmV224E,1107
14
- eodash_catalog-0.3.5.dist-info/RECORD,,