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