eodash_catalog 0.1.12__tar.gz → 0.1.13__tar.gz
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.
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.bumpversion.cfg +1 -1
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.github/workflows/ci.yml +11 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.github/workflows/python-publish.yml +2 -3
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/Dockerfile +1 -1
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/PKG-INFO +1 -1
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/__about__.py +1 -1
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/endpoints.py +113 -39
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/generate_indicators.py +26 -16
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/stac_handling.py +123 -86
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/thumbnails.py +1 -1
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/utils.py +28 -5
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.dockerignore +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.gitignore +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.vscode/extensions.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/.vscode/settings.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/LICENSE.txt +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/README.md +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/pyproject.toml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/requirements.txt +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/ruff.toml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/__init__.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/duration.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/src/eodash_catalog/sh_endpoint.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/__init__.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/test-data/regional_forecast.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/test_generate.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/test_geoparquet.py +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-catalogs/testing-json.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-catalogs/testing.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_CROPOMAT1.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_locations_processing.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_see_solar_energy.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_1.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_1_json.json +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_2.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_wms_no_time.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-indicators/test_indicator.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-layers/baselayers.yaml +0 -0
- {eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-layers/overlays.yaml +0 -0
|
@@ -10,6 +10,9 @@ jobs:
|
|
|
10
10
|
matrix:
|
|
11
11
|
python-version: ["3.10", "3.11", "3.12"]
|
|
12
12
|
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
packages: write
|
|
13
16
|
steps:
|
|
14
17
|
- uses: actions/checkout@v4
|
|
15
18
|
- name: Set up Python ${{ matrix.python-version }}
|
|
@@ -25,3 +28,11 @@ jobs:
|
|
|
25
28
|
run: python -m pip install .
|
|
26
29
|
- name: Test
|
|
27
30
|
run: cd tests && python -m pytest -p no:cacheprovider
|
|
31
|
+
- if: github.ref == 'refs/heads/main'
|
|
32
|
+
name: Build and push latest docker image
|
|
33
|
+
run: |
|
|
34
|
+
IMAGE_ID=ghcr.io/${{ github.repository }}
|
|
35
|
+
VERSION=${{ github.ref_name }}
|
|
36
|
+
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
|
37
|
+
docker build -t $IMAGE_ID:latest .
|
|
38
|
+
docker push $IMAGE_ID:latest
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# This workflows will upload a Python Package using Twine when a release is created
|
|
2
2
|
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
|
3
3
|
|
|
4
|
-
name: Upload Python Package and Docker image
|
|
4
|
+
name: Upload Python Package and Docker image on tag
|
|
5
5
|
|
|
6
6
|
on:
|
|
7
7
|
push:
|
|
@@ -37,6 +37,5 @@ jobs:
|
|
|
37
37
|
IMAGE_ID=ghcr.io/${{ github.repository }}
|
|
38
38
|
VERSION=${{ github.ref_name }}
|
|
39
39
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
|
40
|
-
docker build -t $IMAGE_ID
|
|
41
|
-
docker push $IMAGE_ID:latest
|
|
40
|
+
docker build -t $IMAGE_ID:$VERSION .
|
|
42
41
|
docker push $IMAGE_ID:$VERSION
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eodash_catalog
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
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
|
|
@@ -11,6 +11,8 @@ from operator import itemgetter
|
|
|
11
11
|
import requests
|
|
12
12
|
from pystac import Asset, Catalog, Collection, Item, Link, SpatialExtent, Summaries
|
|
13
13
|
from pystac_client import Client
|
|
14
|
+
from shapely import wkt
|
|
15
|
+
from shapely.geometry import mapping
|
|
14
16
|
from structlog import get_logger
|
|
15
17
|
|
|
16
18
|
from eodash_catalog.sh_endpoint import get_SH_token
|
|
@@ -26,7 +28,6 @@ from eodash_catalog.thumbnails import generate_thumbnail
|
|
|
26
28
|
from eodash_catalog.utils import (
|
|
27
29
|
Options,
|
|
28
30
|
create_geojson_from_bbox,
|
|
29
|
-
create_geojson_point,
|
|
30
31
|
filter_time_entries,
|
|
31
32
|
format_datetime_to_isostring_zulu,
|
|
32
33
|
generate_veda_cog_link,
|
|
@@ -147,7 +148,7 @@ def handle_STAC_based_endpoint(
|
|
|
147
148
|
options: Options,
|
|
148
149
|
headers=None,
|
|
149
150
|
) -> Collection:
|
|
150
|
-
if "Locations"
|
|
151
|
+
if collection_config.get("Locations"):
|
|
151
152
|
root_collection = get_or_create_collection(
|
|
152
153
|
catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
|
|
153
154
|
)
|
|
@@ -169,7 +170,7 @@ def handle_STAC_based_endpoint(
|
|
|
169
170
|
collection.id = location.get("Identifier", uuid.uuid4())
|
|
170
171
|
collection.title = location.get("Name")
|
|
171
172
|
# See if description should be overwritten
|
|
172
|
-
if "Description"
|
|
173
|
+
if location.get("Description"):
|
|
173
174
|
collection.description = location["Description"]
|
|
174
175
|
else:
|
|
175
176
|
collection.description = location["Name"]
|
|
@@ -184,7 +185,7 @@ def handle_STAC_based_endpoint(
|
|
|
184
185
|
# eodash v4 compatibility
|
|
185
186
|
add_visualization_info(collection, collection_config, endpoint_config)
|
|
186
187
|
add_process_info_child_collection(collection, catalog_config, collection_config)
|
|
187
|
-
if "OverwriteBBox"
|
|
188
|
+
if location.get("OverwriteBBox"):
|
|
188
189
|
collection.extent.spatial = SpatialExtent(
|
|
189
190
|
[
|
|
190
191
|
location["OverwriteBBox"],
|
|
@@ -197,7 +198,7 @@ def handle_STAC_based_endpoint(
|
|
|
197
198
|
root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
|
|
198
199
|
else:
|
|
199
200
|
bbox = None
|
|
200
|
-
if "Bbox"
|
|
201
|
+
if endpoint_config.get("Bbox"):
|
|
201
202
|
bbox = ",".join(map(str, endpoint_config["Bbox"]))
|
|
202
203
|
root_collection = process_STACAPI_Endpoint(
|
|
203
204
|
catalog_config=catalog_config,
|
|
@@ -263,17 +264,17 @@ def process_STACAPI_Endpoint(
|
|
|
263
264
|
added_times[iso_date] = True
|
|
264
265
|
link = collection.add_item(item)
|
|
265
266
|
if options.tn:
|
|
266
|
-
if "cog_default"
|
|
267
|
+
if item.assets.get("cog_default"):
|
|
267
268
|
generate_thumbnail(
|
|
268
269
|
item, collection_config, endpoint_config, item.assets["cog_default"].href
|
|
269
270
|
)
|
|
270
271
|
else:
|
|
271
272
|
generate_thumbnail(item, collection_config, endpoint_config)
|
|
272
273
|
# Check if we can create visualization link
|
|
273
|
-
if "Assets"
|
|
274
|
+
if endpoint_config.get("Assets"):
|
|
274
275
|
add_visualization_info(item, collection_config, endpoint_config, item.id)
|
|
275
276
|
link.extra_fields["item"] = item.id
|
|
276
|
-
elif "cog_default"
|
|
277
|
+
elif item.assets.get("cog_default"):
|
|
277
278
|
add_visualization_info(
|
|
278
279
|
item, collection_config, endpoint_config, item.assets["cog_default"].href
|
|
279
280
|
)
|
|
@@ -282,7 +283,7 @@ def process_STACAPI_Endpoint(
|
|
|
282
283
|
add_visualization_info(
|
|
283
284
|
item, collection_config, endpoint_config, datetimes=[item_datetime]
|
|
284
285
|
)
|
|
285
|
-
elif "start_datetime"
|
|
286
|
+
elif item.properties.get("start_datetime") and item.properties.get("end_datetime"):
|
|
286
287
|
add_visualization_info(
|
|
287
288
|
item,
|
|
288
289
|
collection_config,
|
|
@@ -323,7 +324,7 @@ def process_STACAPI_Endpoint(
|
|
|
323
324
|
add_collection_information(catalog_config, collection, collection_config)
|
|
324
325
|
|
|
325
326
|
# Check if we need to overwrite the bbox after update from items
|
|
326
|
-
if "OverwriteBBox"
|
|
327
|
+
if endpoint_config.get("OverwriteBBox"):
|
|
327
328
|
collection.extent.spatial = SpatialExtent(
|
|
328
329
|
[
|
|
329
330
|
endpoint_config["OverwriteBBox"],
|
|
@@ -377,7 +378,7 @@ def handle_SH_WMS_endpoint(
|
|
|
377
378
|
root_collection = get_or_create_collection(
|
|
378
379
|
catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
|
|
379
380
|
)
|
|
380
|
-
if "Locations"
|
|
381
|
+
if collection_config.get("Locations"):
|
|
381
382
|
for location in collection_config["Locations"]:
|
|
382
383
|
# create and populate location collections based on times
|
|
383
384
|
# TODO: Should we add some new description per location?
|
|
@@ -483,7 +484,7 @@ def handle_GeoDB_endpoint(
|
|
|
483
484
|
collection = get_or_create_collection(
|
|
484
485
|
catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
|
|
485
486
|
)
|
|
486
|
-
select = "?select=aoi,aoi_id,country,city,time"
|
|
487
|
+
select = "?select=aoi,aoi_id,country,city,time,input_data,sub_aoi"
|
|
487
488
|
url = (
|
|
488
489
|
endpoint_config["EndPoint"]
|
|
489
490
|
+ endpoint_config["Database"]
|
|
@@ -501,12 +502,12 @@ def handle_GeoDB_endpoint(
|
|
|
501
502
|
for key, value in groupby(sorted_locations, key=itemgetter("aoi_id")):
|
|
502
503
|
# Finding min and max values for date
|
|
503
504
|
values = list(value)
|
|
504
|
-
times = [datetime.fromisoformat(t["time"]) for t in values]
|
|
505
505
|
unique_values = next(iter({v["aoi_id"]: v for v in values}.values()))
|
|
506
506
|
country = unique_values["country"]
|
|
507
507
|
city = unique_values["city"]
|
|
508
508
|
IdKey = endpoint_config.get("IdKey", "city")
|
|
509
509
|
IdValue = unique_values[IdKey]
|
|
510
|
+
|
|
510
511
|
if country not in countries:
|
|
511
512
|
countries.append(country)
|
|
512
513
|
# sanitize unique key identifier to be sure it is saveable as a filename
|
|
@@ -520,28 +521,92 @@ def handle_GeoDB_endpoint(
|
|
|
520
521
|
IdValue = key
|
|
521
522
|
if city not in cities:
|
|
522
523
|
cities.append(city)
|
|
523
|
-
min_date = min(times)
|
|
524
|
-
max_date = max(times)
|
|
525
524
|
latlon = unique_values["aoi"]
|
|
526
525
|
[lat, lon] = [float(x) for x in latlon.split(",")]
|
|
527
526
|
# create item for unique locations
|
|
528
527
|
buff = 0.01
|
|
529
528
|
bbox = [lon - buff, lat - buff, lon + buff, lat + buff]
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
529
|
+
|
|
530
|
+
# create collection per available inputdata information
|
|
531
|
+
sc_config = {
|
|
532
|
+
"Title": city,
|
|
533
|
+
"Description": f"{city} - {country}",
|
|
534
|
+
}
|
|
535
|
+
locations_collection = get_or_create_collection(
|
|
536
|
+
collection, key, sc_config, catalog_config, endpoint_config
|
|
538
537
|
)
|
|
539
|
-
|
|
538
|
+
input_data = endpoint_config.get("InputData")
|
|
539
|
+
if input_data:
|
|
540
|
+
for v in values:
|
|
541
|
+
# add items based on inputData fields for each time step available in values
|
|
542
|
+
first_match = next(
|
|
543
|
+
(item for item in input_data if item.get("Identifier") == v["input_data"]), None
|
|
544
|
+
)
|
|
545
|
+
time_object = datetime.fromisoformat(v["time"])
|
|
546
|
+
# extract wkt geometry from sub_aoi
|
|
547
|
+
if "sub_aoi" in v and v["sub_aoi"] != "/":
|
|
548
|
+
# create geometry from wkt
|
|
549
|
+
geometry = mapping(wkt.loads(v["sub_aoi"]))
|
|
550
|
+
else:
|
|
551
|
+
geometry = create_geojson_from_bbox(bbox)
|
|
552
|
+
item = Item(
|
|
553
|
+
id=v["time"],
|
|
554
|
+
bbox=bbox,
|
|
555
|
+
properties={},
|
|
556
|
+
geometry=geometry,
|
|
557
|
+
datetime=time_object,
|
|
558
|
+
)
|
|
559
|
+
if first_match:
|
|
560
|
+
match first_match["Type"]:
|
|
561
|
+
case "WMS":
|
|
562
|
+
url = first_match["Url"]
|
|
563
|
+
extra_fields = {
|
|
564
|
+
"wms:layers": [first_match["Layers"]],
|
|
565
|
+
"role": ["data"],
|
|
566
|
+
}
|
|
567
|
+
if url.startswith("https://services.sentinel-hub.com/ogc/wms/"):
|
|
568
|
+
instanceId = os.getenv("SH_INSTANCE_ID")
|
|
569
|
+
if "InstanceId" in endpoint_config:
|
|
570
|
+
instanceId = endpoint_config["InstanceId"]
|
|
571
|
+
start_date = format_datetime_to_isostring_zulu(time_object)
|
|
572
|
+
used_delta = timedelta(days=1)
|
|
573
|
+
if "TimeDelta" in first_match:
|
|
574
|
+
used_delta = timedelta(minutes=first_match["TimeDelta"])
|
|
575
|
+
end_date = format_datetime_to_isostring_zulu(
|
|
576
|
+
time_object + used_delta - timedelta(milliseconds=1)
|
|
577
|
+
)
|
|
578
|
+
extra_fields.update(
|
|
579
|
+
{"wms:dimensions": {"TIME": f"{start_date}/{end_date}"}}
|
|
580
|
+
)
|
|
581
|
+
# we add the instance id to the url
|
|
582
|
+
url = f"https://services.sentinel-hub.com/ogc/wms/{instanceId}"
|
|
583
|
+
else:
|
|
584
|
+
extra_fields.update({"wms:dimensions": {"TIME": v["time"]}})
|
|
585
|
+
link = Link(
|
|
586
|
+
rel="wms",
|
|
587
|
+
target=url,
|
|
588
|
+
media_type=(endpoint_config.get("MimeType", "image/png")),
|
|
589
|
+
title=collection_config["Name"],
|
|
590
|
+
extra_fields=extra_fields,
|
|
591
|
+
)
|
|
592
|
+
item.add_link(link)
|
|
593
|
+
itemlink = locations_collection.add_item(item)
|
|
594
|
+
itemlink.extra_fields["datetime"] = (
|
|
595
|
+
f"{format_datetime_to_isostring_zulu(time_object)}Z"
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
# add_visualization_info(
|
|
599
|
+
# item, collection_config, endpoint_config, file_url=first_match.get("FileUrl")
|
|
600
|
+
# )
|
|
601
|
+
locations_collection.extra_fields["subcode"] = key
|
|
602
|
+
link = collection.add_child(locations_collection)
|
|
603
|
+
locations_collection.update_extent_from_items()
|
|
604
|
+
# collection.update_extent_from_items()
|
|
540
605
|
# bubble up information we want to the link
|
|
541
606
|
link.extra_fields["id"] = key
|
|
542
607
|
link.extra_fields["latlng"] = latlon
|
|
543
608
|
link.extra_fields["country"] = country
|
|
544
|
-
link.extra_fields["
|
|
609
|
+
link.extra_fields["name"] = city
|
|
545
610
|
|
|
546
611
|
if "yAxis" not in collection_config:
|
|
547
612
|
# fetch yAxis and store it to data, preventing need to save it per dataset in yml
|
|
@@ -557,7 +622,7 @@ def handle_GeoDB_endpoint(
|
|
|
557
622
|
collection_config["yAxis"] = yAxis
|
|
558
623
|
add_collection_information(catalog_config, collection, collection_config)
|
|
559
624
|
add_example_info(collection, collection_config, endpoint_config, catalog_config)
|
|
560
|
-
collection.extra_fields["
|
|
625
|
+
collection.extra_fields["locations"] = True
|
|
561
626
|
|
|
562
627
|
collection.update_extent_from_items()
|
|
563
628
|
collection.summaries = Summaries(
|
|
@@ -580,7 +645,7 @@ def handle_SH_endpoint(
|
|
|
580
645
|
headers = {"Authorization": f"Bearer {token}"}
|
|
581
646
|
endpoint_config["EndPoint"] = "https://services.sentinel-hub.com/api/v1/catalog/1.0.0/"
|
|
582
647
|
# Overwrite collection id with type, such as ZARR or BYOC
|
|
583
|
-
if "Type"
|
|
648
|
+
if endpoint_config.get("Type"):
|
|
584
649
|
endpoint_config["CollectionId"] = (
|
|
585
650
|
endpoint_config["Type"] + "-" + endpoint_config["CollectionId"]
|
|
586
651
|
)
|
|
@@ -637,7 +702,7 @@ def handle_WMS_endpoint(
|
|
|
637
702
|
LOGGER.warn(f"NO datetimes returned for collection: {collection_config['Name']}!")
|
|
638
703
|
|
|
639
704
|
# Check if we should overwrite bbox
|
|
640
|
-
if "OverwriteBBox"
|
|
705
|
+
if endpoint_config.get("OverwriteBBox"):
|
|
641
706
|
collection.extent.spatial = SpatialExtent(
|
|
642
707
|
[
|
|
643
708
|
endpoint_config["OverwriteBBox"],
|
|
@@ -655,10 +720,10 @@ def generate_veda_tiles_link(endpoint_config: dict, item: str | None) -> str:
|
|
|
655
720
|
for asset in endpoint_config["Assets"]:
|
|
656
721
|
assets += f"&assets={asset}"
|
|
657
722
|
color_formula = ""
|
|
658
|
-
if "ColorFormula"
|
|
723
|
+
if endpoint_config.get("ColorFormula"):
|
|
659
724
|
color_formula = "&color_formula={}".format(endpoint_config["ColorFormula"])
|
|
660
725
|
no_data = ""
|
|
661
|
-
if "NoData"
|
|
726
|
+
if endpoint_config.get("NoData"):
|
|
662
727
|
no_data = "&no_data={}".format(endpoint_config["NoData"])
|
|
663
728
|
item = item if item else "{item}"
|
|
664
729
|
target_url = f"https://openveda.cloud/api/raster/collections/{collection}/items/{item}/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{assets}{color_formula}{no_data}"
|
|
@@ -673,7 +738,7 @@ def add_visualization_info(
|
|
|
673
738
|
datetimes: list[datetime] | None = None,
|
|
674
739
|
) -> None:
|
|
675
740
|
extra_fields: dict[str, list[str] | dict[str, str]] = {}
|
|
676
|
-
if "Attribution"
|
|
741
|
+
if endpoint_config.get("Attribution"):
|
|
677
742
|
stac_object.stac_extensions.append(
|
|
678
743
|
"https://stac-extensions.github.io/attribution/v0.1.0/schema.json"
|
|
679
744
|
)
|
|
@@ -681,7 +746,7 @@ def add_visualization_info(
|
|
|
681
746
|
# add extension reference
|
|
682
747
|
if endpoint_config["Name"] == "Sentinel Hub" or endpoint_config["Name"] == "Sentinel Hub WMS":
|
|
683
748
|
instanceId = os.getenv("SH_INSTANCE_ID")
|
|
684
|
-
if "InstanceId"
|
|
749
|
+
if endpoint_config.get("InstanceId"):
|
|
685
750
|
instanceId = endpoint_config["InstanceId"]
|
|
686
751
|
if env_id := endpoint_config.get("CustomSHEnvId"):
|
|
687
752
|
# special handling for custom environment
|
|
@@ -733,16 +798,21 @@ def add_visualization_info(
|
|
|
733
798
|
dimensions = {}
|
|
734
799
|
if dimensions_config := endpoint_config.get("Dimensions", {}):
|
|
735
800
|
for key, value in dimensions_config.items():
|
|
801
|
+
# special replace for world_settlement_footprint
|
|
802
|
+
if collection_config["EodashIdentifier"] == "WSF":
|
|
803
|
+
value = value.replace(
|
|
804
|
+
"{time}", datetimes is not None and str(datetimes[0].year) or "{time}"
|
|
805
|
+
)
|
|
736
806
|
dimensions[key] = value
|
|
737
807
|
if datetimes is not None:
|
|
738
808
|
dimensions["TIME"] = format_datetime_to_isostring_zulu(datetimes[0])
|
|
739
809
|
if dimensions != {}:
|
|
740
810
|
extra_fields["wms:dimensions"] = dimensions
|
|
741
|
-
if "Styles"
|
|
811
|
+
if endpoint_config.get("Styles"):
|
|
742
812
|
extra_fields["wms:styles"] = endpoint_config["Styles"]
|
|
743
|
-
if "TileSize"
|
|
813
|
+
if endpoint_config.get("TileSize"):
|
|
744
814
|
extra_fields["wms:tilesize"] = endpoint_config["TileSize"]
|
|
745
|
-
if "Version"
|
|
815
|
+
if endpoint_config.get("Version"):
|
|
746
816
|
extra_fields["wms:version"] = endpoint_config["Version"]
|
|
747
817
|
media_type = endpoint_config.get("MediaType", "image/jpeg")
|
|
748
818
|
endpoint_url = endpoint_config["EndPoint"]
|
|
@@ -775,7 +845,7 @@ def add_visualization_info(
|
|
|
775
845
|
dimensions["TIME"] = format_datetime_to_isostring_zulu(datetimes[0])
|
|
776
846
|
if dimensions != {}:
|
|
777
847
|
extra_fields["wms:dimensions"] = dimensions
|
|
778
|
-
if "Styles"
|
|
848
|
+
if endpoint_config.get("Styles"):
|
|
779
849
|
extra_fields["wms:styles"] = endpoint_config["Styles"]
|
|
780
850
|
media_type = endpoint_config.get("MediaType", "image/png")
|
|
781
851
|
endpoint_url = endpoint_config["EndPoint"]
|
|
@@ -821,14 +891,18 @@ def add_visualization_info(
|
|
|
821
891
|
# either preset Rescale of left as a template
|
|
822
892
|
vmin = "{vmin}"
|
|
823
893
|
vmax = "{vmax}"
|
|
824
|
-
if "Rescale"
|
|
894
|
+
if endpoint_config.get("Rescale"):
|
|
825
895
|
vmin = endpoint_config["Rescale"][0]
|
|
826
896
|
vmax = endpoint_config["Rescale"][1]
|
|
827
897
|
# depending on numerical input only
|
|
828
898
|
data_projection = str(endpoint_config.get("DataProjection", 3857))
|
|
829
899
|
epsg_prefix = "" if "EPSG:" in data_projection else "EPSG:"
|
|
830
900
|
crs = f"{epsg_prefix}{data_projection}"
|
|
831
|
-
time =
|
|
901
|
+
time = (
|
|
902
|
+
stac_object.get_datetime().strftime("%Y-%m-%dT%H:%M:%SZ") # type: ignore
|
|
903
|
+
if isinstance(stac_object, Item)
|
|
904
|
+
else "{time}"
|
|
905
|
+
)
|
|
832
906
|
target_url = (
|
|
833
907
|
"{}/tiles/{}/{}/{{z}}/{{y}}/{{x}}" "?crs={}&time={}&vmin={}&vmax={}&cbar={}"
|
|
834
908
|
).format(
|
|
@@ -998,7 +1072,7 @@ def handle_raw_source(
|
|
|
998
1072
|
assets=assets,
|
|
999
1073
|
extra_fields={},
|
|
1000
1074
|
)
|
|
1001
|
-
if "Attribution"
|
|
1075
|
+
if endpoint_config.get("Attribution"):
|
|
1002
1076
|
item.stac_extensions.append(
|
|
1003
1077
|
"https://stac-extensions.github.io/attribution/v0.1.0/schema.json"
|
|
1004
1078
|
)
|
|
@@ -39,7 +39,7 @@ from eodash_catalog.utils import (
|
|
|
39
39
|
Options,
|
|
40
40
|
RaisingThread,
|
|
41
41
|
add_single_item_if_collection_empty,
|
|
42
|
-
|
|
42
|
+
merge_bboxes,
|
|
43
43
|
read_config_file,
|
|
44
44
|
recursive_save,
|
|
45
45
|
retry,
|
|
@@ -88,7 +88,7 @@ def process_catalog_file(file_path: str, options: Options):
|
|
|
88
88
|
)
|
|
89
89
|
except FileNotFoundError:
|
|
90
90
|
LOGGER.info(f"Warning: neither collection nor indicator found for {collection}")
|
|
91
|
-
if "MapProjection"
|
|
91
|
+
if catalog_config.get("MapProjection"):
|
|
92
92
|
catalog.extra_fields["eodash:mapProjection"] = catalog_config["MapProjection"]
|
|
93
93
|
|
|
94
94
|
strategy = TemplateLayoutStrategy(item_template="${collection}/${year}")
|
|
@@ -163,10 +163,11 @@ def process_indicator_file(
|
|
|
163
163
|
):
|
|
164
164
|
LOGGER.info(f"Processing indicator: {file_path}")
|
|
165
165
|
indicator_config = read_config_file(file_path)
|
|
166
|
+
|
|
166
167
|
parent_indicator = get_or_create_collection(
|
|
167
168
|
catalog, indicator_config["Name"], indicator_config, catalog_config, {}
|
|
168
169
|
)
|
|
169
|
-
if "Collections"
|
|
170
|
+
if indicator_config.get("Collections"):
|
|
170
171
|
for collection in indicator_config["Collections"]:
|
|
171
172
|
process_collection_file(
|
|
172
173
|
catalog_config,
|
|
@@ -179,11 +180,20 @@ def process_indicator_file(
|
|
|
179
180
|
# we assume that collection files can also be loaded directly
|
|
180
181
|
process_collection_file(catalog_config, file_path, parent_indicator, options)
|
|
181
182
|
add_collection_information(catalog_config, parent_indicator, indicator_config, True)
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
# get shared extent of all of the collections
|
|
184
|
+
# we do not want to use update_extent_from_items() because
|
|
185
|
+
# they might have OverwriteBBox and that would discard it for indicator
|
|
186
|
+
merged_bbox = merge_bboxes(
|
|
187
|
+
[
|
|
188
|
+
c_child.extent.spatial.bboxes[0]
|
|
189
|
+
for c_child in parent_indicator.get_children()
|
|
190
|
+
if isinstance(c_child, Collection)
|
|
191
|
+
]
|
|
192
|
+
)
|
|
193
|
+
parent_indicator.extent.spatial.bboxes = [merged_bbox]
|
|
184
194
|
# Add bbox extents from children
|
|
185
195
|
for c_child in parent_indicator.get_children():
|
|
186
|
-
if isinstance(c_child, Collection)
|
|
196
|
+
if isinstance(c_child, Collection) and merged_bbox != c_child.extent.spatial.bboxes[0]:
|
|
187
197
|
parent_indicator.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
|
|
188
198
|
# extract collection information and add it to summary indicator level
|
|
189
199
|
extract_indicator_info(parent_indicator)
|
|
@@ -203,7 +213,7 @@ def process_collection_file(
|
|
|
203
213
|
):
|
|
204
214
|
LOGGER.info(f"Processing collection: {file_path}")
|
|
205
215
|
collection_config = read_config_file(file_path)
|
|
206
|
-
if "Resources"
|
|
216
|
+
if collection_config.get("Resources"):
|
|
207
217
|
for endpoint_config in collection_config["Resources"]:
|
|
208
218
|
try:
|
|
209
219
|
collection = None
|
|
@@ -275,7 +285,7 @@ def process_collection_file(
|
|
|
275
285
|
LOGGER.warn(f"""Exception: {e.args[0]} with config: {endpoint_config}""")
|
|
276
286
|
raise e
|
|
277
287
|
|
|
278
|
-
elif "Subcollections"
|
|
288
|
+
elif collection_config.get("Subcollections"):
|
|
279
289
|
# if no endpoint is specified we check for definition of subcollections
|
|
280
290
|
parent_collection = get_or_create_collection(
|
|
281
291
|
catalog, collection_config["Name"], collection_config, catalog_config, {}
|
|
@@ -286,7 +296,7 @@ def process_collection_file(
|
|
|
286
296
|
for sub_coll_def in collection_config["Subcollections"]:
|
|
287
297
|
# Subcollection has only data on one location which
|
|
288
298
|
# is defined for the entire collection
|
|
289
|
-
if "Name"
|
|
299
|
+
if sub_coll_def.get("Name") and sub_coll_def.get("Point"):
|
|
290
300
|
locations.append(sub_coll_def["Name"])
|
|
291
301
|
if isinstance(sub_coll_def["Country"], list):
|
|
292
302
|
countries.extend(sub_coll_def["Country"])
|
|
@@ -302,7 +312,7 @@ def process_collection_file(
|
|
|
302
312
|
for link in parent_collection.links:
|
|
303
313
|
if (
|
|
304
314
|
link.rel == "child"
|
|
305
|
-
and "id"
|
|
315
|
+
and link.extra_fields.get("id")
|
|
306
316
|
and link.extra_fields["id"] == sub_coll_def["Identifier"]
|
|
307
317
|
):
|
|
308
318
|
latlng = "{},{}".format(
|
|
@@ -330,9 +340,9 @@ def process_collection_file(
|
|
|
330
340
|
links = tmp_catalog.get_child(sub_coll_def["Identifier"]).get_links() # type: ignore
|
|
331
341
|
for link in links:
|
|
332
342
|
# extract summary information
|
|
333
|
-
if "city"
|
|
343
|
+
if link.extra_fields.get("city"):
|
|
334
344
|
locations.append(link.extra_fields["city"])
|
|
335
|
-
if "country"
|
|
345
|
+
if link.extra_fields.get("country"):
|
|
336
346
|
if isinstance(link.extra_fields["country"], list):
|
|
337
347
|
countries.extend(link.extra_fields["country"])
|
|
338
348
|
else:
|
|
@@ -340,7 +350,7 @@ def process_collection_file(
|
|
|
340
350
|
|
|
341
351
|
parent_collection.add_links(links)
|
|
342
352
|
|
|
343
|
-
add_collection_information(catalog_config, parent_collection, collection_config)
|
|
353
|
+
add_collection_information(catalog_config, parent_collection, collection_config, True)
|
|
344
354
|
add_process_info(parent_collection, catalog_config, collection_config)
|
|
345
355
|
parent_collection.update_extent_from_items()
|
|
346
356
|
# Add bbox extents from children
|
|
@@ -368,7 +378,7 @@ def add_to_catalog(
|
|
|
368
378
|
|
|
369
379
|
link: Link = catalog.add_child(collection)
|
|
370
380
|
# bubble fields we want to have up to collection link and add them to collection
|
|
371
|
-
if endpoint and "Type" in
|
|
381
|
+
if endpoint and endpoint.get("Type") and endpoint["Type"] not in ["GeoDB"]:
|
|
372
382
|
collection.extra_fields["endpointtype"] = "{}_{}".format(
|
|
373
383
|
endpoint["Name"],
|
|
374
384
|
endpoint["Type"],
|
|
@@ -380,12 +390,12 @@ def add_to_catalog(
|
|
|
380
390
|
elif endpoint:
|
|
381
391
|
collection.extra_fields["endpointtype"] = endpoint["Name"]
|
|
382
392
|
link.extra_fields["endpointtype"] = endpoint["Name"]
|
|
383
|
-
if "Subtitle"
|
|
393
|
+
if collection_config.get("Subtitle"):
|
|
384
394
|
link.extra_fields["subtitle"] = collection_config["Subtitle"]
|
|
385
395
|
link.extra_fields["title"] = collection.title
|
|
386
396
|
link.extra_fields["code"] = collection_config["EodashIdentifier"]
|
|
387
397
|
link.extra_fields["id"] = collection_config["Name"]
|
|
388
|
-
if "Themes"
|
|
398
|
+
if collection_config.get("Themes"):
|
|
389
399
|
link.extra_fields["themes"] = collection_config["Themes"]
|
|
390
400
|
# Check for summaries and bubble up info
|
|
391
401
|
if disable:
|
|
@@ -51,9 +51,9 @@ def get_or_create_collection(
|
|
|
51
51
|
temporal_extent = TemporalExtent([[times_datetimes[0], times_datetimes[-1]]])
|
|
52
52
|
|
|
53
53
|
extent = Extent(spatial=spatial_extent, temporal=temporal_extent)
|
|
54
|
-
|
|
54
|
+
description = ""
|
|
55
55
|
# Check if description is link to markdown file
|
|
56
|
-
if "Description"
|
|
56
|
+
if collection_config.get("Description"):
|
|
57
57
|
description = collection_config["Description"]
|
|
58
58
|
if description.endswith((".md", ".MD")):
|
|
59
59
|
if description.startswith("http"):
|
|
@@ -61,7 +61,7 @@ def get_or_create_collection(
|
|
|
61
61
|
response = requests.get(description)
|
|
62
62
|
if response.status_code == 200:
|
|
63
63
|
description = response.text
|
|
64
|
-
elif "Subtitle"
|
|
64
|
+
elif collection_config.get("Subtitle"):
|
|
65
65
|
LOGGER.warn("Markdown file could not be fetched")
|
|
66
66
|
description = collection_config["Subtitle"]
|
|
67
67
|
else:
|
|
@@ -69,10 +69,10 @@ def get_or_create_collection(
|
|
|
69
69
|
response = requests.get(f'{catalog_config["assets_endpoint"]}/{description}')
|
|
70
70
|
if response.status_code == 200:
|
|
71
71
|
description = response.text
|
|
72
|
-
elif "Subtitle"
|
|
72
|
+
elif collection_config.get("Subtitle"):
|
|
73
73
|
LOGGER.warn("Markdown file could not be fetched")
|
|
74
74
|
description = collection_config["Subtitle"]
|
|
75
|
-
elif "Subtitle"
|
|
75
|
+
elif collection_config.get("Subtitle"):
|
|
76
76
|
# Try to use at least subtitle to fill some information
|
|
77
77
|
description = collection_config["Subtitle"]
|
|
78
78
|
|
|
@@ -90,12 +90,28 @@ def create_service_link(endpoint_config: dict, catalog_config: dict) -> Link:
|
|
|
90
90
|
"id": endpoint_config["Identifier"],
|
|
91
91
|
"method": endpoint_config.get("Method", "GET"),
|
|
92
92
|
}
|
|
93
|
-
if "EndPoint"
|
|
93
|
+
if endpoint_config.get("EndPoint"):
|
|
94
94
|
extra_fields["endpoint"] = endpoint_config["EndPoint"]
|
|
95
|
-
if "Body"
|
|
95
|
+
if endpoint_config.get("Body"):
|
|
96
96
|
extra_fields["body"] = get_full_url(endpoint_config["Body"], catalog_config)
|
|
97
|
-
if "Flatstyle"
|
|
98
|
-
|
|
97
|
+
if endpoint_config.get("Flatstyle"):
|
|
98
|
+
# either a string
|
|
99
|
+
if isinstance(endpoint_config["Flatstyle"], str):
|
|
100
|
+
# update URL if needed
|
|
101
|
+
extra_fields["eox:flatstyle"] = get_full_url(
|
|
102
|
+
endpoint_config["Flatstyle"], catalog_config
|
|
103
|
+
)
|
|
104
|
+
elif isinstance(endpoint_config["Flatstyle"], list):
|
|
105
|
+
# or a list of objects - update URL if needed
|
|
106
|
+
extra_fields["eox:flatstyle"] = []
|
|
107
|
+
for flatstyle_config in endpoint_config["Flatstyle"]:
|
|
108
|
+
flatstyle_obj = {
|
|
109
|
+
"Identifier": flatstyle_config.get("Identifier"),
|
|
110
|
+
"Url": get_full_url(flatstyle_config.get("Url"), catalog_config),
|
|
111
|
+
}
|
|
112
|
+
extra_fields["eox:flatstyle"].append(flatstyle_obj)
|
|
113
|
+
else:
|
|
114
|
+
LOGGER.warn("Flatstyle is invalid type", endpoint_config["Flatstyle"])
|
|
99
115
|
sl = Link(
|
|
100
116
|
rel="service",
|
|
101
117
|
target=endpoint_config["Url"],
|
|
@@ -121,15 +137,15 @@ def create_web_map_link(layer_config: dict, role: str) -> Link:
|
|
|
121
137
|
case "wms":
|
|
122
138
|
# handle wms special config options
|
|
123
139
|
extra_fields["wms:layers"] = layer_config["layers"]
|
|
124
|
-
if "styles"
|
|
140
|
+
if layer_config.get("styles"):
|
|
125
141
|
extra_fields["wms:styles"] = layer_config["styles"]
|
|
126
|
-
if "dimensions"
|
|
142
|
+
if layer_config.get("dimensions"):
|
|
127
143
|
extra_fields["wms:dimensions"] = layer_config["dimensions"]
|
|
128
144
|
case "wmts":
|
|
129
145
|
extra_fields["wmts:layer"] = layer_config["layer"]
|
|
130
|
-
if "dimensions"
|
|
146
|
+
if layer_config.get("dimensions"):
|
|
131
147
|
extra_fields["wmts:dimensions"] = layer_config["dimensions"]
|
|
132
|
-
if "Attribution"
|
|
148
|
+
if layer_config.get("Attribution"):
|
|
133
149
|
extra_fields["attribution"] = layer_config["Attribution"]
|
|
134
150
|
wml = Link(
|
|
135
151
|
rel=layer_config["protocol"],
|
|
@@ -148,7 +164,7 @@ def add_example_info(
|
|
|
148
164
|
endpoint_config: dict,
|
|
149
165
|
catalog_config: dict,
|
|
150
166
|
) -> None:
|
|
151
|
-
if "Services"
|
|
167
|
+
if collection_config.get("Services"):
|
|
152
168
|
for service in collection_config["Services"]:
|
|
153
169
|
if service["Name"] == "Statistical API":
|
|
154
170
|
service_type = service.get("Type", "byoc")
|
|
@@ -182,7 +198,7 @@ def add_example_info(
|
|
|
182
198
|
Link(
|
|
183
199
|
rel="example",
|
|
184
200
|
target=service["Url"],
|
|
185
|
-
title=(
|
|
201
|
+
title=service.get("Title", service.get("Name")),
|
|
186
202
|
media_type="application/x-ipynb+json",
|
|
187
203
|
extra_fields={
|
|
188
204
|
"example:language": "Jupyter Notebook",
|
|
@@ -190,26 +206,6 @@ def add_example_info(
|
|
|
190
206
|
},
|
|
191
207
|
)
|
|
192
208
|
)
|
|
193
|
-
elif "Resources" in collection_config:
|
|
194
|
-
for service in collection_config["Resources"]:
|
|
195
|
-
if service.get("Name") == "xcube":
|
|
196
|
-
target_url = "{}/timeseries/{}/{}?aggMethods=median".format(
|
|
197
|
-
endpoint_config["EndPoint"],
|
|
198
|
-
endpoint_config["DatacubeId"],
|
|
199
|
-
endpoint_config["Variable"],
|
|
200
|
-
)
|
|
201
|
-
stac_object.add_link(
|
|
202
|
-
Link(
|
|
203
|
-
rel="example",
|
|
204
|
-
target=target_url,
|
|
205
|
-
title=service["Name"] + " analytics",
|
|
206
|
-
media_type="application/json",
|
|
207
|
-
extra_fields={
|
|
208
|
-
"example:language": "JSON",
|
|
209
|
-
"example:method": "POST",
|
|
210
|
-
},
|
|
211
|
-
)
|
|
212
|
-
)
|
|
213
209
|
|
|
214
210
|
|
|
215
211
|
def add_collection_information(
|
|
@@ -220,7 +216,7 @@ def add_collection_information(
|
|
|
220
216
|
) -> None:
|
|
221
217
|
# Add metadata information
|
|
222
218
|
# Check license identifier
|
|
223
|
-
if "License"
|
|
219
|
+
if collection_config.get("License"):
|
|
224
220
|
# Check if list was provided
|
|
225
221
|
if isinstance(collection_config["License"], list):
|
|
226
222
|
if len(collection_config["License"]) == 1:
|
|
@@ -230,7 +226,7 @@ def add_collection_information(
|
|
|
230
226
|
target=collection_config["License"][0]["Url"],
|
|
231
227
|
media_type=(collection_config["License"][0].get("Type", "text/html")),
|
|
232
228
|
)
|
|
233
|
-
if
|
|
229
|
+
if collection_config["License"][0].get("Title"):
|
|
234
230
|
link.title = collection_config["License"][0]["Title"]
|
|
235
231
|
collection.links.append(link)
|
|
236
232
|
elif len(collection_config["License"]) > 1:
|
|
@@ -239,11 +235,9 @@ def add_collection_information(
|
|
|
239
235
|
link = Link(
|
|
240
236
|
rel="license",
|
|
241
237
|
target=license_entry["Url"],
|
|
242
|
-
media_type="text/html"
|
|
243
|
-
if "Type" in license_entry
|
|
244
|
-
else license_entry["Type"],
|
|
238
|
+
media_type=license_entry.get("Type", "text/html"),
|
|
245
239
|
)
|
|
246
|
-
if "Title"
|
|
240
|
+
if license_entry.get("Title"):
|
|
247
241
|
link.title = license_entry["Title"]
|
|
248
242
|
collection.links.append(link)
|
|
249
243
|
else:
|
|
@@ -267,7 +261,7 @@ def add_collection_information(
|
|
|
267
261
|
else:
|
|
268
262
|
pass
|
|
269
263
|
|
|
270
|
-
if "Provider"
|
|
264
|
+
if collection_config.get("Provider"):
|
|
271
265
|
try:
|
|
272
266
|
collection.providers = [
|
|
273
267
|
Provider(
|
|
@@ -279,21 +273,21 @@ def add_collection_information(
|
|
|
279
273
|
except Exception:
|
|
280
274
|
LOGGER.warn(f"Issue creating provider information for collection: {collection.id}")
|
|
281
275
|
|
|
282
|
-
if "Citation"
|
|
283
|
-
if
|
|
276
|
+
if collection_config.get("Citation"):
|
|
277
|
+
if collection_config["Citation"].get("DOI"):
|
|
284
278
|
collection.extra_fields["sci:doi"] = collection_config["Citation"]["DOI"]
|
|
285
|
-
if "Citation"
|
|
279
|
+
if collection_config["Citation"].get("Citation"):
|
|
286
280
|
collection.extra_fields["sci:citation"] = collection_config["Citation"]["Citation"]
|
|
287
|
-
if
|
|
281
|
+
if collection_config["Citation"].get("Publication"):
|
|
288
282
|
collection.extra_fields["sci:publications"] = [
|
|
289
283
|
# convert keys to lower case
|
|
290
284
|
{k.lower(): v for k, v in publication.items()}
|
|
291
285
|
for publication in collection_config["Citation"]["Publication"]
|
|
292
286
|
]
|
|
293
287
|
|
|
294
|
-
if "Subtitle"
|
|
288
|
+
if collection_config.get("Subtitle"):
|
|
295
289
|
collection.extra_fields["subtitle"] = collection_config["Subtitle"]
|
|
296
|
-
if "Legend"
|
|
290
|
+
if collection_config.get("Legend"):
|
|
297
291
|
collection.add_asset(
|
|
298
292
|
"legend",
|
|
299
293
|
Asset(
|
|
@@ -302,7 +296,7 @@ def add_collection_information(
|
|
|
302
296
|
roles=["metadata"],
|
|
303
297
|
),
|
|
304
298
|
)
|
|
305
|
-
if "Story"
|
|
299
|
+
if collection_config.get("Story"):
|
|
306
300
|
collection.add_asset(
|
|
307
301
|
"story",
|
|
308
302
|
Asset(
|
|
@@ -311,7 +305,7 @@ def add_collection_information(
|
|
|
311
305
|
roles=["metadata"],
|
|
312
306
|
),
|
|
313
307
|
)
|
|
314
|
-
if "Image"
|
|
308
|
+
if collection_config.get("Image"):
|
|
315
309
|
collection.add_asset(
|
|
316
310
|
"thumbnail",
|
|
317
311
|
Asset(
|
|
@@ -327,10 +321,10 @@ def add_collection_information(
|
|
|
327
321
|
# Add extra fields to collection if available
|
|
328
322
|
add_extra_fields(collection, collection_config, is_root_collection)
|
|
329
323
|
|
|
330
|
-
if "References"
|
|
324
|
+
if collection_config.get("References"):
|
|
331
325
|
generic_counter = 1
|
|
332
326
|
for ref in collection_config["References"]:
|
|
333
|
-
if "Key"
|
|
327
|
+
if ref.get("Key"):
|
|
334
328
|
key = ref["Key"]
|
|
335
329
|
else:
|
|
336
330
|
key = f"reference_{generic_counter}"
|
|
@@ -344,14 +338,14 @@ def add_collection_information(
|
|
|
344
338
|
roles=["metadata"],
|
|
345
339
|
),
|
|
346
340
|
)
|
|
347
|
-
if "Colorlegend"
|
|
341
|
+
if collection_config.get("Colorlegend"):
|
|
348
342
|
collection.extra_fields["eox:colorlegend"] = collection_config["Colorlegend"]
|
|
349
343
|
|
|
350
344
|
|
|
351
345
|
def add_process_info(collection: Collection, catalog_config: dict, collection_config: dict) -> None:
|
|
352
|
-
if any(key
|
|
346
|
+
if any(collection_config.get(key) for key in ["Locations", "Subcollections"]):
|
|
353
347
|
# add the generic geodb-like selection process on the root collection instead of Processes
|
|
354
|
-
if "geodb_default_form"
|
|
348
|
+
if catalog_config.get("geodb_default_form"):
|
|
355
349
|
# adding default geodb-like map handling for Locations
|
|
356
350
|
collection.extra_fields["eodash:jsonform"] = get_full_url(
|
|
357
351
|
catalog_config["geodb_default_form"], catalog_config
|
|
@@ -369,29 +363,44 @@ def add_process_info(collection: Collection, catalog_config: dict, collection_co
|
|
|
369
363
|
},
|
|
370
364
|
)
|
|
371
365
|
collection.add_link(sl)
|
|
366
|
+
# adding additional service links
|
|
367
|
+
if collection_config["Name"] == "GeoDB" and collection_config.get("Process", {}).get(
|
|
368
|
+
"EndPoints"
|
|
369
|
+
):
|
|
370
|
+
for endpoint in collection_config["Process"]["EndPoints"]:
|
|
371
|
+
collection.add_link(create_service_link(endpoint, catalog_config))
|
|
372
|
+
|
|
373
|
+
# for geodb collections now based on locations, we want to make sure
|
|
374
|
+
# also manually defined processes are added to the collection
|
|
375
|
+
if collection_config["Name"] == "GeoDB" and collection_config.get("Process", {}).get(
|
|
376
|
+
"VegaDefinition"
|
|
377
|
+
):
|
|
378
|
+
collection.extra_fields["eodash:vegadefinition"] = get_full_url(
|
|
379
|
+
collection_config["Process"]["VegaDefinition"], catalog_config
|
|
380
|
+
)
|
|
372
381
|
# elif is intentional for cases when Process is defined on collection with Locations
|
|
373
382
|
# then we want to only add it to the "children", not the root
|
|
374
|
-
elif "Process"
|
|
375
|
-
if
|
|
383
|
+
elif collection_config.get("Process"):
|
|
384
|
+
if collection_config["Process"].get("EndPoints"):
|
|
376
385
|
for endpoint in collection_config["Process"]["EndPoints"]:
|
|
377
386
|
collection.add_link(create_service_link(endpoint, catalog_config))
|
|
378
|
-
if
|
|
387
|
+
if collection_config["Process"].get("JsonForm"):
|
|
379
388
|
collection.extra_fields["eodash:jsonform"] = get_full_url(
|
|
380
389
|
collection_config["Process"]["JsonForm"], catalog_config
|
|
381
390
|
)
|
|
382
|
-
if
|
|
391
|
+
if collection_config["Process"].get("VegaDefinition"):
|
|
383
392
|
collection.extra_fields["eodash:vegadefinition"] = get_full_url(
|
|
384
393
|
collection_config["Process"]["VegaDefinition"], catalog_config
|
|
385
394
|
)
|
|
386
|
-
elif "Resources"
|
|
395
|
+
elif collection_config.get("Resources"):
|
|
387
396
|
# see if geodb resource configured use defaults if available
|
|
388
397
|
for resource in collection_config["Resources"]:
|
|
389
398
|
if resource["Name"] == "GeoDB":
|
|
390
|
-
if "geodb_default_form"
|
|
399
|
+
if catalog_config.get("geodb_default_form"):
|
|
391
400
|
collection.extra_fields["eodash:jsonform"] = get_full_url(
|
|
392
401
|
catalog_config["geodb_default_form"], catalog_config
|
|
393
402
|
)
|
|
394
|
-
if "geodb_default_vega"
|
|
403
|
+
if catalog_config.get("geodb_default_vega"):
|
|
395
404
|
collection.extra_fields["eodash:vegadefinition"] = get_full_url(
|
|
396
405
|
catalog_config["geodb_default_vega"], catalog_config
|
|
397
406
|
)
|
|
@@ -412,21 +421,49 @@ def add_process_info(collection: Collection, catalog_config: dict, collection_co
|
|
|
412
421
|
},
|
|
413
422
|
)
|
|
414
423
|
)
|
|
424
|
+
elif resource["Name"] == "xcube" and catalog_config.get("default_xcube_process"):
|
|
425
|
+
target_url = "{}/timeseries/{}/{}?aggMethods=median".format(
|
|
426
|
+
resource["EndPoint"],
|
|
427
|
+
resource["DatacubeId"],
|
|
428
|
+
resource["Variable"],
|
|
429
|
+
)
|
|
430
|
+
process_endpoint_config = catalog_config["default_xcube_process"]["EndPoints"][0]
|
|
431
|
+
extra_fields = {
|
|
432
|
+
"id": process_endpoint_config["Identifier"],
|
|
433
|
+
"method": process_endpoint_config.get("Method", "GET"),
|
|
434
|
+
}
|
|
435
|
+
extra_fields["body"] = get_full_url(process_endpoint_config["Body"], catalog_config)
|
|
436
|
+
if catalog_config["default_xcube_process"].get("JsonForm"):
|
|
437
|
+
collection.extra_fields["eodash:jsonform"] = get_full_url(
|
|
438
|
+
catalog_config["default_xcube_process"]["JsonForm"], catalog_config
|
|
439
|
+
)
|
|
440
|
+
if catalog_config["default_xcube_process"].get("VegaDefinition"):
|
|
441
|
+
collection.extra_fields["eodash:vegadefinition"] = get_full_url(
|
|
442
|
+
catalog_config["default_xcube_process"]["VegaDefinition"], catalog_config
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
sl = Link(
|
|
446
|
+
rel="service",
|
|
447
|
+
target=target_url,
|
|
448
|
+
media_type=process_endpoint_config["Type"],
|
|
449
|
+
extra_fields=extra_fields,
|
|
450
|
+
)
|
|
451
|
+
collection.add_link(sl)
|
|
415
452
|
|
|
416
453
|
|
|
417
454
|
def add_process_info_child_collection(
|
|
418
455
|
collection: Collection, catalog_config: dict, collection_config: dict
|
|
419
456
|
) -> None:
|
|
420
457
|
# in case of locations, we add the process itself on a child collection
|
|
421
|
-
if "Process"
|
|
422
|
-
if
|
|
458
|
+
if collection_config.get("Process"):
|
|
459
|
+
if collection_config["Process"].get("EndPoints"):
|
|
423
460
|
for endpoint in collection_config["Process"]["EndPoints"]:
|
|
424
461
|
collection.add_link(create_service_link(endpoint, catalog_config))
|
|
425
|
-
if
|
|
462
|
+
if collection_config["Process"].get("JsonForm"):
|
|
426
463
|
collection.extra_fields["eodash:jsonform"] = get_full_url(
|
|
427
464
|
collection_config["Process"]["JsonForm"], catalog_config
|
|
428
465
|
)
|
|
429
|
-
if
|
|
466
|
+
if collection_config["Process"].get("VegaDefinition"):
|
|
430
467
|
collection.extra_fields["eodash:vegadefinition"] = get_full_url(
|
|
431
468
|
collection_config["Process"]["VegaDefinition"], catalog_config
|
|
432
469
|
)
|
|
@@ -436,20 +473,20 @@ def add_base_overlay_info(
|
|
|
436
473
|
collection: Collection, catalog_config: dict, collection_config: dict
|
|
437
474
|
) -> None:
|
|
438
475
|
# add custom baselayers specially for this indicator
|
|
439
|
-
if "BaseLayers"
|
|
476
|
+
if collection_config.get("BaseLayers"):
|
|
440
477
|
for layer in collection_config["BaseLayers"]:
|
|
441
478
|
collection.add_link(create_web_map_link(layer, role="baselayer"))
|
|
442
479
|
# alternatively use default base layers defined
|
|
443
|
-
elif "default_base_layers"
|
|
480
|
+
elif catalog_config.get("default_base_layers"):
|
|
444
481
|
base_layers = read_config_file(catalog_config["default_base_layers"])
|
|
445
482
|
for layer in base_layers:
|
|
446
483
|
collection.add_link(create_web_map_link(layer, role="baselayer"))
|
|
447
484
|
# add custom overlays just for this indicator
|
|
448
|
-
if "OverlayLayers"
|
|
485
|
+
if collection_config.get("OverlayLayers"):
|
|
449
486
|
for layer in collection_config["OverlayLayers"]:
|
|
450
487
|
collection.add_link(create_web_map_link(layer, role="overlay"))
|
|
451
488
|
# check if default overlay layers defined
|
|
452
|
-
elif "default_overlay_layers"
|
|
489
|
+
elif catalog_config.get("default_overlay_layers"):
|
|
453
490
|
overlay_layers = read_config_file(catalog_config["default_overlay_layers"])
|
|
454
491
|
for layer in overlay_layers:
|
|
455
492
|
collection.add_link(create_web_map_link(layer, role="overlay"))
|
|
@@ -458,41 +495,41 @@ def add_base_overlay_info(
|
|
|
458
495
|
def add_extra_fields(
|
|
459
496
|
stac_object: Collection | Link, collection_config: dict, is_root_collection: bool = False
|
|
460
497
|
) -> None:
|
|
461
|
-
if "yAxis"
|
|
498
|
+
if collection_config.get("yAxis"):
|
|
462
499
|
stac_object.extra_fields["yAxis"] = collection_config["yAxis"]
|
|
463
|
-
if "Themes"
|
|
500
|
+
if collection_config.get("Themes"):
|
|
464
501
|
stac_object.extra_fields["themes"] = collection_config["Themes"]
|
|
465
502
|
if (
|
|
466
|
-
"Locations"
|
|
503
|
+
collection_config.get("Locations") or collection_config.get("Subcollections")
|
|
467
504
|
) and is_root_collection:
|
|
468
505
|
stac_object.extra_fields["locations"] = True
|
|
469
|
-
if "Tags"
|
|
506
|
+
if collection_config.get("Tags"):
|
|
470
507
|
stac_object.extra_fields["tags"] = collection_config["Tags"]
|
|
471
|
-
if "Satellite"
|
|
508
|
+
if collection_config.get("Satellite"):
|
|
472
509
|
stac_object.extra_fields["satellite"] = collection_config["Satellite"]
|
|
473
|
-
if "Sensor"
|
|
510
|
+
if collection_config.get("Sensor"):
|
|
474
511
|
stac_object.extra_fields["sensor"] = collection_config["Sensor"]
|
|
475
|
-
if "Agency"
|
|
512
|
+
if collection_config.get("Agency"):
|
|
476
513
|
stac_object.extra_fields["agency"] = collection_config["Agency"]
|
|
477
|
-
if "EodashIdentifier"
|
|
514
|
+
if collection_config.get("EodashIdentifier"):
|
|
478
515
|
stac_object.extra_fields["subcode"] = collection_config["EodashIdentifier"]
|
|
479
|
-
if "CollectionGroup"
|
|
516
|
+
if collection_config.get("CollectionGroup"):
|
|
480
517
|
stac_object.extra_fields["collection_group"] = collection_config["CollectionGroup"]
|
|
481
|
-
if "DataSource"
|
|
482
|
-
if
|
|
483
|
-
if
|
|
518
|
+
if collection_config.get("DataSource"):
|
|
519
|
+
if collection_config["DataSource"].get("Spaceborne"):
|
|
520
|
+
if collection_config["DataSource"]["Spaceborne"].get("Sensor"):
|
|
484
521
|
stac_object.extra_fields["sensor"] = collection_config["DataSource"]["Spaceborne"][
|
|
485
522
|
"Sensor"
|
|
486
523
|
]
|
|
487
|
-
if
|
|
524
|
+
if collection_config["DataSource"]["Spaceborne"].get("Satellite"):
|
|
488
525
|
stac_object.extra_fields["satellite"] = collection_config["DataSource"][
|
|
489
526
|
"Spaceborne"
|
|
490
527
|
]["Satellite"]
|
|
491
|
-
if
|
|
528
|
+
if collection_config["DataSource"].get("InSitu"):
|
|
492
529
|
stac_object.extra_fields["insituSources"] = collection_config["DataSource"]["InSitu"]
|
|
493
|
-
if
|
|
530
|
+
if collection_config["DataSource"].get("Other"):
|
|
494
531
|
stac_object.extra_fields["otherSources"] = collection_config["DataSource"]["Other"]
|
|
495
|
-
if "MapProjection"
|
|
532
|
+
if collection_config.get("MapProjection"):
|
|
496
533
|
stac_object.extra_fields["eodash:mapProjection"] = collection_config["MapProjection"]
|
|
497
534
|
|
|
498
535
|
|
|
@@ -31,7 +31,7 @@ def generate_thumbnail(
|
|
|
31
31
|
) -> None:
|
|
32
32
|
if endpoint_config["Name"] == "Sentinel Hub" or endpoint_config["Name"] == "WMS":
|
|
33
33
|
instanceId = os.getenv("SH_INSTANCE_ID")
|
|
34
|
-
if "InstanceId"
|
|
34
|
+
if endpoint_config.get("InstanceId"):
|
|
35
35
|
instanceId = endpoint_config["InstanceId"]
|
|
36
36
|
# Build example url
|
|
37
37
|
wms_config = (
|
|
@@ -82,6 +82,7 @@ def retrieveExtentFromWCS(
|
|
|
82
82
|
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
|
|
83
83
|
message = template.format(type(e).__name__, e.args)
|
|
84
84
|
LOGGER.warn(message)
|
|
85
|
+
raise e
|
|
85
86
|
|
|
86
87
|
bbox = [-180.0, -90.0, 180.0, 90.0]
|
|
87
88
|
owsnmspc = "{http://www.opengis.net/ows/2.0}"
|
|
@@ -143,6 +144,7 @@ def retrieveExtentFromWMSWMTS(
|
|
|
143
144
|
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
|
|
144
145
|
message = template.format(type(e).__name__, e.args)
|
|
145
146
|
LOGGER.warn(message)
|
|
147
|
+
raise e
|
|
146
148
|
|
|
147
149
|
bbox = [-180.0, -90.0, 180.0, 90.0]
|
|
148
150
|
if service and service[layer].boundingBoxWGS84:
|
|
@@ -285,7 +287,7 @@ def iter_len_at_least(i, n: int) -> int:
|
|
|
285
287
|
|
|
286
288
|
def generate_veda_cog_link(endpoint_config: dict, file_url: str | None) -> str:
|
|
287
289
|
bidx = ""
|
|
288
|
-
if "Bidx"
|
|
290
|
+
if endpoint_config.get("Bidx"):
|
|
289
291
|
# Check if an array was provided
|
|
290
292
|
if hasattr(endpoint_config["Bidx"], "__len__"):
|
|
291
293
|
for band in endpoint_config["Bidx"]:
|
|
@@ -294,25 +296,29 @@ def generate_veda_cog_link(endpoint_config: dict, file_url: str | None) -> str:
|
|
|
294
296
|
bidx = "&bidx={}".format(endpoint_config["Bidx"])
|
|
295
297
|
|
|
296
298
|
colormap = ""
|
|
297
|
-
if "Colormap"
|
|
299
|
+
if endpoint_config.get("Colormap"):
|
|
298
300
|
colormap = "&colormap={}".format(endpoint_config["Colormap"])
|
|
299
301
|
# TODO: For now we assume a already urlparsed colormap definition
|
|
300
302
|
# it could be nice to allow a json and better convert it on the fly
|
|
301
303
|
# colormap = "&colormap=%s"%(urllib.parse.quote(str(endpoint_config["Colormap"])))
|
|
302
304
|
|
|
305
|
+
Nodata = ""
|
|
306
|
+
if endpoint_config.get("Nodata"):
|
|
307
|
+
Nodata = "&nodata={}".format(endpoint_config["Nodata"])
|
|
308
|
+
|
|
303
309
|
colormap_name = ""
|
|
304
|
-
if "ColormapName"
|
|
310
|
+
if endpoint_config.get("ColormapName"):
|
|
305
311
|
colormap_name = "&colormap_name={}".format(endpoint_config["ColormapName"])
|
|
306
312
|
|
|
307
313
|
rescale = ""
|
|
308
|
-
if "Rescale"
|
|
314
|
+
if endpoint_config.get("Rescale"):
|
|
309
315
|
rescale = "&rescale={},{}".format(
|
|
310
316
|
endpoint_config["Rescale"][0], endpoint_config["Rescale"][1]
|
|
311
317
|
)
|
|
312
318
|
|
|
313
319
|
file_url = f"url={file_url}&" if file_url else ""
|
|
314
320
|
|
|
315
|
-
target_url = f"https://openveda.cloud/api/raster/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{file_url}resampling_method=nearest{bidx}{colormap}{colormap_name}{rescale}"
|
|
321
|
+
target_url = f"https://openveda.cloud/api/raster/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{file_url}resampling_method=nearest{bidx}{colormap}{colormap_name}{rescale}{Nodata}"
|
|
316
322
|
return target_url
|
|
317
323
|
|
|
318
324
|
|
|
@@ -458,3 +464,20 @@ def _load_file(filepath):
|
|
|
458
464
|
return yaml.safe_load(content)
|
|
459
465
|
except yaml.YAMLError as err:
|
|
460
466
|
raise ValueError(f"Failed to parse '{filepath}' as JSON or YAML: {err}") from err
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def merge_bboxes(bboxes: list[list[float]]) -> list[float]:
|
|
470
|
+
"""
|
|
471
|
+
Merge bounding boxes into one bounding box that contains them all.
|
|
472
|
+
Returns:
|
|
473
|
+
A list representing the merged bbox: [min_lon, min_lat, max_lon, max_lat]
|
|
474
|
+
"""
|
|
475
|
+
if not bboxes:
|
|
476
|
+
raise ValueError("No bounding boxes provided.")
|
|
477
|
+
|
|
478
|
+
min_lon = min(b[0] for b in bboxes)
|
|
479
|
+
min_lat = min(b[1] for b in bboxes)
|
|
480
|
+
max_lon = max(b[2] for b in bboxes)
|
|
481
|
+
max_lat = max(b[3] for b in bboxes)
|
|
482
|
+
|
|
483
|
+
return [min_lon, min_lat, max_lon, max_lat]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_CROPOMAT1.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_see_solar_energy.yaml
RENAMED
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_1.yaml
RENAMED
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_1_json.json
RENAMED
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_tif_demo_2.yaml
RENAMED
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-collections/test_wms_no_time.yaml
RENAMED
|
File without changes
|
{eodash_catalog-0.1.12 → eodash_catalog-0.1.13}/tests/testing-indicators/test_indicator.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|