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.
- eodash_catalog/__about__.py +1 -1
- eodash_catalog/endpoints.py +164 -48
- eodash_catalog/generate_indicators.py +17 -7
- eodash_catalog/sh_endpoint.py +3 -1
- eodash_catalog/stac_handling.py +91 -21
- eodash_catalog/thumbnails.py +4 -1
- eodash_catalog/utils.py +6 -0
- {eodash_catalog-0.3.5.dist-info → eodash_catalog-0.3.20.dist-info}/METADATA +1 -1
- eodash_catalog-0.3.20.dist-info/RECORD +14 -0
- eodash_catalog-0.3.5.dist-info/RECORD +0 -14
- {eodash_catalog-0.3.5.dist-info → eodash_catalog-0.3.20.dist-info}/WHEEL +0 -0
- {eodash_catalog-0.3.5.dist-info → eodash_catalog-0.3.20.dist-info}/entry_points.txt +0 -0
- {eodash_catalog-0.3.5.dist-info → eodash_catalog-0.3.20.dist-info}/licenses/LICENSE.txt +0 -0
eodash_catalog/__about__.py
CHANGED
eodash_catalog/endpoints.py
CHANGED
|
@@ -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("
|
|
219
|
-
bbox = ",".join(map(str, endpoint_config["
|
|
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("
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
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.
|
|
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":
|
|
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
|
-
|
|
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=
|
|
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"]
|
|
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,
|
|
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://
|
|
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"] == "
|
|
296
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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"]
|
eodash_catalog/sh_endpoint.py
CHANGED
|
@@ -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,
|
eodash_catalog/stac_handling.py
CHANGED
|
@@ -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(
|
|
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=
|
|
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("
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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=
|
|
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
|
|
521
|
+
if "BaseLayers" in collection_config:
|
|
477
522
|
for layer in collection_config["BaseLayers"]:
|
|
478
|
-
collection.add_link(
|
|
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(
|
|
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
|
|
534
|
+
if "OverlayLayers" in collection_config:
|
|
486
535
|
for layer in collection_config["OverlayLayers"]:
|
|
487
|
-
collection.add_link(
|
|
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(
|
|
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
|
eodash_catalog/thumbnails.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|