earthcode 0.1.0__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.
- earthcode/__init__.py +0 -0
- earthcode/fairtool.py +577 -0
- earthcode/git_add.py +383 -0
- earthcode/gitclerk_add.py +21 -0
- earthcode/metadata_input_definitions.py +338 -0
- earthcode/search.py +209 -0
- earthcode/static.py +569 -0
- earthcode/validator.py +605 -0
- earthcode-0.1.0.dist-info/METADATA +70 -0
- earthcode-0.1.0.dist-info/RECORD +12 -0
- earthcode-0.1.0.dist-info/WHEEL +4 -0
- earthcode-0.1.0.dist-info/licenses/LICENSE +21 -0
earthcode/static.py
ADDED
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
import pystac
|
|
6
|
+
from pystac.extensions.scientific import ScientificExtension
|
|
7
|
+
|
|
8
|
+
from earthcode.metadata_input_definitions import (
|
|
9
|
+
ExperimentMetadata,
|
|
10
|
+
ItemMetadata,
|
|
11
|
+
ProductCollectionMetadata,
|
|
12
|
+
ProjectCollectionMetadata,
|
|
13
|
+
WorkflowMetadata,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
OSC_SCHEMA_URI = "https://stac-extensions.github.io/osc/v1.0.0/schema.json"
|
|
17
|
+
THEMES_SCHEMA_URI = "https://stac-extensions.github.io/themes/v1.0.0/schema.json"
|
|
18
|
+
CONTACTS_SCHEMA_URI = "https://stac-extensions.github.io/contacts/v0.1.1/schema.json"
|
|
19
|
+
CF_SCHEMA_URI = "https://stac-extensions.github.io/cf/v0.2.0/schema.json"
|
|
20
|
+
THEMES_SCHEME_URI = "https://github.com/stac-extensions/osc#theme"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_extent(bboxes: list[list[float]], start_datetime: datetime, end_datetime: datetime) -> pystac.Extent:
|
|
24
|
+
"""Builds a STAC extent object from bbox coordinates and start/end datetimes."""
|
|
25
|
+
|
|
26
|
+
return pystac.Extent(
|
|
27
|
+
spatial=pystac.SpatialExtent(bboxes),
|
|
28
|
+
temporal=pystac.TemporalExtent([[start_datetime, end_datetime]]),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _add_links(collection: pystac.Collection, relations: list[str], targets: list[str], titles: list[str]) -> None:
|
|
33
|
+
"""Adds a batch of links from relation, target, and title lists."""
|
|
34
|
+
|
|
35
|
+
links = [pystac.Link(rel=rel, target=target, title=title) for rel, target, title in zip(relations, targets, titles)]
|
|
36
|
+
collection.add_links(links)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ensure_extension(collection: pystac.Collection, schema_uri: str) -> None:
|
|
40
|
+
"""Adds a schema URI to collection extensions if it is not already present."""
|
|
41
|
+
|
|
42
|
+
if schema_uri not in collection.stac_extensions:
|
|
43
|
+
collection.stac_extensions.append(schema_uri)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _set_osc_fields(
|
|
47
|
+
collection: pystac.Collection,
|
|
48
|
+
*,
|
|
49
|
+
project: str | None = None,
|
|
50
|
+
status: str | None = None,
|
|
51
|
+
region: str | None = None,
|
|
52
|
+
osc_type: str | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Sets OSC core properties on a collection and ensures OSC extension registration."""
|
|
55
|
+
|
|
56
|
+
_ensure_extension(collection, OSC_SCHEMA_URI)
|
|
57
|
+
if project is not None:
|
|
58
|
+
collection.extra_fields["osc:project"] = project
|
|
59
|
+
if status is not None:
|
|
60
|
+
collection.extra_fields["osc:status"] = status
|
|
61
|
+
if region is not None:
|
|
62
|
+
collection.extra_fields["osc:region"] = region
|
|
63
|
+
if osc_type is not None:
|
|
64
|
+
collection.extra_fields["osc:type"] = osc_type
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _apply_themes(collection: pystac.Collection, theme_ids: list[str]) -> None:
|
|
68
|
+
"""Adds theme links and writes themes metadata to a collection."""
|
|
69
|
+
|
|
70
|
+
_ensure_extension(collection, THEMES_SCHEMA_URI)
|
|
71
|
+
|
|
72
|
+
themes_list = []
|
|
73
|
+
for theme in theme_ids:
|
|
74
|
+
# add the correct link
|
|
75
|
+
collection.add_link(
|
|
76
|
+
pystac.Link(rel="related",
|
|
77
|
+
target=f'../../themes/{theme}/catalog.json',
|
|
78
|
+
media_type="application/json",
|
|
79
|
+
title=f"Theme: {theme.capitalize()}")
|
|
80
|
+
)
|
|
81
|
+
themes_list.append(
|
|
82
|
+
{
|
|
83
|
+
"scheme": "https://github.com/stac-extensions/osc#theme",
|
|
84
|
+
"concepts": [{"id": theme}]
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Add themes to the custom fields
|
|
89
|
+
collection.extra_fields.update({
|
|
90
|
+
"themes": themes_list
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _apply_missions(collection: pystac.Collection, mission_ids: list[str]) -> None:
|
|
96
|
+
"""Adds mission links and OSC missions metadata to a collection."""
|
|
97
|
+
|
|
98
|
+
_ensure_extension(collection, OSC_SCHEMA_URI)
|
|
99
|
+
for mission in mission_ids:
|
|
100
|
+
collection.add_link(
|
|
101
|
+
pystac.Link(
|
|
102
|
+
rel="related",
|
|
103
|
+
target=f"../../eo-missions/{mission}/catalog.json",
|
|
104
|
+
media_type="application/json",
|
|
105
|
+
title=f"Mission: {mission.capitalize()}",
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
collection.extra_fields["osc:missions"] = mission_ids
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _apply_variables(collection: pystac.Collection, variable_ids: list[str]) -> None:
|
|
112
|
+
"""Adds variable links and OSC variables metadata to a collection."""
|
|
113
|
+
|
|
114
|
+
_ensure_extension(collection, OSC_SCHEMA_URI)
|
|
115
|
+
for variable in variable_ids:
|
|
116
|
+
collection.add_link(
|
|
117
|
+
pystac.Link(
|
|
118
|
+
rel="related",
|
|
119
|
+
target=f"../../variables/{variable}/catalog.json",
|
|
120
|
+
media_type="application/json",
|
|
121
|
+
title=f"Variable: {' '.join(segment.capitalize() for segment in variable.split('-'))}",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
collection.extra_fields["osc:variables"] = variable_ids
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _create_contact(name: str, roles: list[str], emails: list[str] | None = None) -> dict:
|
|
128
|
+
"""Builds a contacts entry with optional email objects."""
|
|
129
|
+
|
|
130
|
+
contact = {"name": name, "roles": [role for role in roles]}
|
|
131
|
+
if emails:
|
|
132
|
+
contact["emails"] = [{"value": email} for email in emails]
|
|
133
|
+
return contact
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _apply_project_contacts(
|
|
137
|
+
collection: pystac.Collection, technical_officer_name: str, technical_officer_email: str, consortium_members: list[tuple[str, str]]
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Builds and writes project contacts from technical officer and consortium members."""
|
|
140
|
+
|
|
141
|
+
_ensure_extension(collection, CONTACTS_SCHEMA_URI)
|
|
142
|
+
to_contact = _create_contact(technical_officer_name, ["technical_officer"], [technical_officer_email])
|
|
143
|
+
consortium_contacts = [_create_contact(name, ["consoritum_member"], [email]) for name, email in consortium_members]
|
|
144
|
+
collection.extra_fields["contacts"] = [to_contact] + consortium_contacts
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _apply_cf_parameters(collection: pystac.Collection, parameter_names: list[str]) -> None:
|
|
148
|
+
"""Writes CF parameter metadata to a collection."""
|
|
149
|
+
|
|
150
|
+
_ensure_extension(collection, CF_SCHEMA_URI)
|
|
151
|
+
collection.extra_fields["cf:parameter"] = [{"name": parameter_name} for parameter_name in parameter_names]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_project_collection(project_metadata: ProjectCollectionMetadata) -> pystac.Collection:
|
|
155
|
+
"""Creates an OSC project collection from validated metadata inputs."""
|
|
156
|
+
|
|
157
|
+
extent = _build_extent(
|
|
158
|
+
bboxes=project_metadata.project_bbox,
|
|
159
|
+
start_datetime=project_metadata.project_start_datetime,
|
|
160
|
+
end_datetime=project_metadata.project_end_datetime,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
collection = pystac.Collection(
|
|
164
|
+
id=project_metadata.project_id,
|
|
165
|
+
description=project_metadata.project_description,
|
|
166
|
+
extent=extent,
|
|
167
|
+
license=project_metadata.project_license,
|
|
168
|
+
title=project_metadata.project_title,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
common = pystac.CommonMetadata(collection)
|
|
172
|
+
now = datetime.now(timezone.utc)
|
|
173
|
+
common.created = now
|
|
174
|
+
common.updated = now
|
|
175
|
+
|
|
176
|
+
_set_osc_fields(collection, status=project_metadata.project_status, osc_type="project")
|
|
177
|
+
|
|
178
|
+
collection.add_links(
|
|
179
|
+
[
|
|
180
|
+
pystac.Link(rel="root", target="../../catalog.json", media_type="application/json", title="Open Science Catalog"),
|
|
181
|
+
pystac.Link(rel="parent", target="../catalog.json", media_type="application/json", title="Projects"),
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if project_metadata.eo4society_link is None:
|
|
186
|
+
_add_links(collection, ["via"], [project_metadata.website_link], ["Website"])
|
|
187
|
+
else:
|
|
188
|
+
_add_links(
|
|
189
|
+
collection,
|
|
190
|
+
["via", "via"],
|
|
191
|
+
[project_metadata.website_link, project_metadata.eo4society_link],
|
|
192
|
+
["Website", "EO4Society Link"],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
_apply_themes(collection, project_metadata.project_themes)
|
|
196
|
+
|
|
197
|
+
_apply_project_contacts(
|
|
198
|
+
technical_officer_name=project_metadata.to_name,
|
|
199
|
+
technical_officer_email=project_metadata.to_email,
|
|
200
|
+
collection=collection,
|
|
201
|
+
consortium_members=project_metadata.consortium_members,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return collection
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def manually_add_product_links(collection: pystac.Collection, product_metadata: ProductCollectionMetadata) -> None:
|
|
208
|
+
"""Adds access, documentation, and child data links from product metadata."""
|
|
209
|
+
|
|
210
|
+
if product_metadata.access_link:
|
|
211
|
+
_add_links(collection, ["via"], [product_metadata.access_link], ["Access"])
|
|
212
|
+
if product_metadata.documentation_link:
|
|
213
|
+
_add_links(collection, ["via"], [product_metadata.documentation_link], ["Documentation"])
|
|
214
|
+
if product_metadata.item_link:
|
|
215
|
+
_add_links(collection, ["child"], [product_metadata.item_link], [product_metadata.item_title])
|
|
216
|
+
if product_metadata.license_link and product_metadata.product_license == 'other':
|
|
217
|
+
_add_links(collection, ["license"], [product_metadata.license_link], ["License"])
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def create_product_collection(product_metadata: ProductCollectionMetadata) -> pystac.Collection:
|
|
221
|
+
"""Creates an OSC product collection from validated metadata inputs."""
|
|
222
|
+
|
|
223
|
+
extent = _build_extent(
|
|
224
|
+
bboxes=product_metadata.product_bbox,
|
|
225
|
+
start_datetime=product_metadata.product_start_datetime,
|
|
226
|
+
end_datetime=product_metadata.product_end_datetime,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
collection = pystac.Collection(
|
|
230
|
+
id=product_metadata.product_id,
|
|
231
|
+
title=product_metadata.product_title,
|
|
232
|
+
description=product_metadata.product_description,
|
|
233
|
+
extent=extent,
|
|
234
|
+
license=product_metadata.product_license,
|
|
235
|
+
keywords=product_metadata.product_keywords,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
collection.add_links(
|
|
239
|
+
[
|
|
240
|
+
pystac.Link(rel="root", target="../../catalog.json", media_type="application/json", title="Open Science Catalog"),
|
|
241
|
+
pystac.Link(rel="parent", target="../catalog.json", media_type="application/json", title="Products"),
|
|
242
|
+
pystac.Link(
|
|
243
|
+
rel="related",
|
|
244
|
+
target=f"../../projects/{product_metadata.project_id}/collection.json",
|
|
245
|
+
media_type="application/json",
|
|
246
|
+
title=f"Project: {product_metadata.project_title}",
|
|
247
|
+
),
|
|
248
|
+
]
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
common = pystac.CommonMetadata(collection)
|
|
252
|
+
now = datetime.now(timezone.utc)
|
|
253
|
+
common.created = now
|
|
254
|
+
common.updated = now
|
|
255
|
+
|
|
256
|
+
_set_osc_fields(
|
|
257
|
+
collection,
|
|
258
|
+
project=product_metadata.project_id,
|
|
259
|
+
status=product_metadata.product_status,
|
|
260
|
+
region=product_metadata.product_region,
|
|
261
|
+
osc_type="product",
|
|
262
|
+
)
|
|
263
|
+
_apply_missions(collection, product_metadata.product_missions)
|
|
264
|
+
_apply_variables(collection, product_metadata.product_variables)
|
|
265
|
+
_apply_themes(collection, product_metadata.product_themes)
|
|
266
|
+
|
|
267
|
+
if product_metadata.product_doi is not None:
|
|
268
|
+
_ensure_extension(collection, "https://stac-extensions.github.io/scientific/v1.0.0/schema.json")
|
|
269
|
+
ScientificExtension.ext(collection, add_if_missing=True).doi = product_metadata.product_doi
|
|
270
|
+
|
|
271
|
+
if product_metadata.product_parameters:
|
|
272
|
+
_apply_cf_parameters(collection, product_metadata.product_parameters)
|
|
273
|
+
|
|
274
|
+
manually_add_product_links(collection, product_metadata)
|
|
275
|
+
|
|
276
|
+
return collection
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def create_workflow_record(workflow_metadata: WorkflowMetadata) -> dict:
|
|
280
|
+
"""Creates an OSC workflow record dictionary from validated metadata inputs."""
|
|
281
|
+
|
|
282
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
283
|
+
|
|
284
|
+
collection = {
|
|
285
|
+
"id": workflow_metadata.workflow_id,
|
|
286
|
+
"type": "Feature",
|
|
287
|
+
"geometry": None,
|
|
288
|
+
"conformsTo": ["http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core"],
|
|
289
|
+
"properties": {
|
|
290
|
+
"title": workflow_metadata.workflow_title,
|
|
291
|
+
"description": workflow_metadata.workflow_description,
|
|
292
|
+
"type": "workflow",
|
|
293
|
+
"osc:project": workflow_metadata.project_id,
|
|
294
|
+
"osc:status": "completed",
|
|
295
|
+
"formats": [{"name": format_name} for format_name in workflow_metadata.workflow_formats],
|
|
296
|
+
"updated": now,
|
|
297
|
+
"created": now,
|
|
298
|
+
"keywords": workflow_metadata.workflow_keywords,
|
|
299
|
+
"license": workflow_metadata.workflow_license,
|
|
300
|
+
"version": "1",
|
|
301
|
+
"themes": [
|
|
302
|
+
{
|
|
303
|
+
"scheme": THEMES_SCHEME_URI,
|
|
304
|
+
"concepts": [{"id": theme_id} for theme_id in workflow_metadata.workflow_themes],
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
"linkTemplates": [],
|
|
309
|
+
"links": [
|
|
310
|
+
{"rel": "root", "href": "../../catalog.json", "type": "application/json", "title": "Open Science Catalog"},
|
|
311
|
+
{"rel": "parent", "href": "../catalog.json", "type": "application/json", "title": "Workflows"},
|
|
312
|
+
{
|
|
313
|
+
"rel": "related",
|
|
314
|
+
"href": f"../../projects/{workflow_metadata.project_id}/collection.json",
|
|
315
|
+
"type": "application/json",
|
|
316
|
+
"title": f"Project: {workflow_metadata.project_title}",
|
|
317
|
+
},
|
|
318
|
+
{"rel": "git", "href": workflow_metadata.codeurl, "type": "application/json", "title": "Git source repository"},
|
|
319
|
+
],
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for theme_id in workflow_metadata.workflow_themes:
|
|
323
|
+
collection["links"].append(
|
|
324
|
+
{
|
|
325
|
+
"rel": "related",
|
|
326
|
+
"href": f"../../themes/{theme_id}/catalog.json",
|
|
327
|
+
"type": "application/json",
|
|
328
|
+
"title": f"Theme: {theme_id.capitalize()}",
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if workflow_metadata.workflow_doi:
|
|
333
|
+
collection["properties"]["DOI"] = workflow_metadata.workflow_doi
|
|
334
|
+
|
|
335
|
+
if workflow_metadata.workflow_bbox:
|
|
336
|
+
collection["bbox"] = workflow_metadata.workflow_bbox[0]
|
|
337
|
+
|
|
338
|
+
if workflow_metadata.workflow_start_datetime:
|
|
339
|
+
collection["properties"]["start_datetime"] = workflow_metadata.workflow_start_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
340
|
+
|
|
341
|
+
if workflow_metadata.workflow_end_datetime:
|
|
342
|
+
collection["properties"]["end_datetime"] = workflow_metadata.workflow_end_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
343
|
+
|
|
344
|
+
return collection
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def create_experiment_record(experiment_metadata: ExperimentMetadata) -> dict:
|
|
348
|
+
"""Creates an OSC experiment record dictionary from validated metadata inputs."""
|
|
349
|
+
|
|
350
|
+
contacts_payload = experiment_metadata.contacts
|
|
351
|
+
if contacts_payload is None:
|
|
352
|
+
contacts_payload = [
|
|
353
|
+
{
|
|
354
|
+
"name": "EarthCODE Demo",
|
|
355
|
+
"organization": "EarthCODE",
|
|
356
|
+
"links": [{"rel": "about", "type": "text/html", "href": "https://opensciencedata.esa.int/"}],
|
|
357
|
+
"contactInstructions": "Contact via EarthCODE",
|
|
358
|
+
"roles": ["host"],
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
363
|
+
|
|
364
|
+
collection = {
|
|
365
|
+
"id": experiment_metadata.experiment_id,
|
|
366
|
+
"type": "Feature",
|
|
367
|
+
"conformsTo": ["http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core"],
|
|
368
|
+
"geometry": None,
|
|
369
|
+
"properties": {
|
|
370
|
+
"created": now,
|
|
371
|
+
"updated": now,
|
|
372
|
+
"type": "experiment",
|
|
373
|
+
"title": experiment_metadata.experiment_title,
|
|
374
|
+
"description": experiment_metadata.experiment_description,
|
|
375
|
+
"keywords": experiment_metadata.experiment_keywords,
|
|
376
|
+
"contacts": contacts_payload,
|
|
377
|
+
"themes": [
|
|
378
|
+
{
|
|
379
|
+
"scheme": THEMES_SCHEME_URI,
|
|
380
|
+
"concepts": [{"id": theme_id} for theme_id in experiment_metadata.experiment_themes],
|
|
381
|
+
}
|
|
382
|
+
],
|
|
383
|
+
"formats": [{"name": format_name} for format_name in experiment_metadata.experiment_formats],
|
|
384
|
+
"license": experiment_metadata.experiment_license,
|
|
385
|
+
"osc:workflow": experiment_metadata.workflow_id,
|
|
386
|
+
},
|
|
387
|
+
"linkTemplates": [],
|
|
388
|
+
"links": [
|
|
389
|
+
{"rel": "root", "href": "../../catalog.json", "type": "application/json", "title": "Open Science Catalog"},
|
|
390
|
+
{"rel": "parent", "href": "../catalog.json", "type": "application/json", "title": "Experiments"},
|
|
391
|
+
{
|
|
392
|
+
"rel": "related",
|
|
393
|
+
"href": f"../../products/{experiment_metadata.product_id}/collection.json",
|
|
394
|
+
"type": "application/json",
|
|
395
|
+
"title": experiment_metadata.product_title,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"rel": "related",
|
|
399
|
+
"href": f"../../workflows/{experiment_metadata.workflow_id}/record.json",
|
|
400
|
+
"type": "application/json",
|
|
401
|
+
"title": f"Workflow: {experiment_metadata.workflow_title}",
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"rel": "input",
|
|
405
|
+
"href": experiment_metadata.experiment_input_parameters_link,
|
|
406
|
+
"type": "application/yaml",
|
|
407
|
+
"title": "Input parameters",
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"rel": "environment",
|
|
411
|
+
"href": experiment_metadata.experiment_enviroment_link,
|
|
412
|
+
"type": "application/yaml",
|
|
413
|
+
"title": "Execution environment",
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
for theme_id in experiment_metadata.experiment_themes:
|
|
419
|
+
collection["links"].append(
|
|
420
|
+
{
|
|
421
|
+
"rel": "related",
|
|
422
|
+
"href": f"../../themes/{theme_id}/catalog.json",
|
|
423
|
+
"type": "application/json",
|
|
424
|
+
"title": f"Theme: {theme_id.capitalize()}",
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if experiment_metadata.experiment_bbox:
|
|
429
|
+
collection["bbox"] = experiment_metadata.experiment_bbox[0]
|
|
430
|
+
|
|
431
|
+
if experiment_metadata.experiment_start_datetime:
|
|
432
|
+
collection["properties"]["start_datetime"] = experiment_metadata.experiment_start_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
433
|
+
|
|
434
|
+
if experiment_metadata.experiment_end_datetime:
|
|
435
|
+
collection["properties"]["end_datetime"] = experiment_metadata.experiment_end_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
436
|
+
|
|
437
|
+
return collection
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def generate_OSC_dummy_entries(id_extension: str = "+123"):
|
|
441
|
+
"""Generates demo OSC project, product, workflow, and experiment records."""
|
|
442
|
+
|
|
443
|
+
project_id = "4datlantic-ohc" + id_extension
|
|
444
|
+
project_title = "4DAtlantic-OHC"
|
|
445
|
+
|
|
446
|
+
project_collection = create_project_collection(
|
|
447
|
+
ProjectCollectionMetadata(
|
|
448
|
+
project_id=project_id,
|
|
449
|
+
project_title=project_title,
|
|
450
|
+
project_description=(
|
|
451
|
+
"Given the major role of the ocean in the climate system, it is essential to characterize "
|
|
452
|
+
"the temporal and spatial variations of its heat content."
|
|
453
|
+
),
|
|
454
|
+
project_status="completed",
|
|
455
|
+
project_license="various",
|
|
456
|
+
project_bbox=[[-180.0, -90.0, 180.0, 90.0]],
|
|
457
|
+
project_start_datetime=datetime(2021, 7, 6),
|
|
458
|
+
project_end_datetime=datetime(2025, 6, 12),
|
|
459
|
+
project_themes=["oceans"],
|
|
460
|
+
to_name="Roberto Sabia",
|
|
461
|
+
to_email="roberto.sabia@esa.int",
|
|
462
|
+
consortium_members=[("Magellium", "magellium.fr")],
|
|
463
|
+
website_link="https://www.4datlantic-ohc.org/",
|
|
464
|
+
eo4society_link="https://eo4society.esa.int/projects/4datlantic-ohc/",
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
product_id = "4d-atlantic-ohc-global" + id_extension
|
|
469
|
+
product_collection = create_product_collection(
|
|
470
|
+
ProductCollectionMetadata(
|
|
471
|
+
product_id=product_id,
|
|
472
|
+
product_title="Global Ocean Heat Content",
|
|
473
|
+
product_description="Given the major role of the ocean in the climate system.",
|
|
474
|
+
product_bbox=[[-180.0, -90.0, 180.0, 90.0]],
|
|
475
|
+
product_start_datetime=datetime(2021, 1, 1),
|
|
476
|
+
product_end_datetime=datetime(2021, 12, 31),
|
|
477
|
+
product_license="various",
|
|
478
|
+
product_keywords=["ocean", "heat", "content"],
|
|
479
|
+
product_status="completed",
|
|
480
|
+
product_region="Global",
|
|
481
|
+
product_themes=["oceans"],
|
|
482
|
+
product_missions=["in-situ-observations", "grace"],
|
|
483
|
+
product_variables=["ocean-heat-budget"],
|
|
484
|
+
project_id=project_id,
|
|
485
|
+
project_title=project_title,
|
|
486
|
+
product_parameters=["ocean-heat-budget"],
|
|
487
|
+
access_link="https://opensciencedata.esa.int/stac-browser/#/external/https://s3.waw4-1.cloudferro.com/EarthCODE/Catalogs/4datlantic-ohc/collection.json",
|
|
488
|
+
documentation_link="https://www.aviso.altimetry.fr/fileadmin/documents/data/tools/OHC-EEI/OHCATL-DT-035-MAG_EDD_V3.0.pdf",
|
|
489
|
+
item_link="https://s3.waw4-1.cloudferro.com/EarthCODE/Catalogs/4datlantic-ohc/collection.json",
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
workflow_id = "4datlantic-wf" + id_extension
|
|
494
|
+
workflow_title = "4D-Atlantic-Workflow"
|
|
495
|
+
workflow_collection = create_workflow_record(
|
|
496
|
+
WorkflowMetadata(
|
|
497
|
+
workflow_id=workflow_id,
|
|
498
|
+
workflow_title=workflow_title,
|
|
499
|
+
workflow_description="This describes the OHC workflow",
|
|
500
|
+
workflow_license="CC-BY-4.0",
|
|
501
|
+
workflow_keywords=["ocean", "heat", "content"],
|
|
502
|
+
workflow_formats=["netcdf64"],
|
|
503
|
+
workflow_themes=["oceans"],
|
|
504
|
+
codeurl="https://github.com/ESA-EarthCODE/open-science-catalog-metadata",
|
|
505
|
+
project_id=project_id,
|
|
506
|
+
project_title=project_title,
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
experiment = create_experiment_record(
|
|
511
|
+
ExperimentMetadata(
|
|
512
|
+
experiment_id="4datlantic-experiment" + id_extension,
|
|
513
|
+
experiment_title="4D-Atlantic-Experiment",
|
|
514
|
+
experiment_description="This describes the OHC experiment",
|
|
515
|
+
experiment_license="CC-BY-SA-4.0",
|
|
516
|
+
experiment_keywords=["ocean", "heat", "content"],
|
|
517
|
+
experiment_formats=["GeoTIFF"],
|
|
518
|
+
experiment_themes=["oceans"],
|
|
519
|
+
experiment_input_parameters_link="https://github.com/deepesdl/cube-gen",
|
|
520
|
+
experiment_enviroment_link="https://github.com/deepesdl/cube-gen",
|
|
521
|
+
workflow_id=workflow_id,
|
|
522
|
+
workflow_title=workflow_title,
|
|
523
|
+
product_id=product_id,
|
|
524
|
+
product_title="Global Ocean Heat Content",
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
return project_collection, product_collection, workflow_collection, experiment
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def add_item_link_to_product_collection(product_collection: pystac.Collection, item_id: str, item_title: str) -> None:
|
|
532
|
+
"""Adds an item relation link to a product collection."""
|
|
533
|
+
|
|
534
|
+
product_collection.add_link(
|
|
535
|
+
pystac.Link(rel="item", target=f"./{item_id}.json", media_type="application/json", title=item_title)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def create_item(item_metadata: ItemMetadata, stac_version='1.0.0') -> pystac.Item:
|
|
540
|
+
"""Creates a STAC item with one data asset and optional extra fields."""
|
|
541
|
+
|
|
542
|
+
item = pystac.Item(
|
|
543
|
+
id=item_metadata.itemid,
|
|
544
|
+
geometry=item_metadata.geometry,
|
|
545
|
+
datetime=item_metadata.data_time,
|
|
546
|
+
bbox=item_metadata.bbox,
|
|
547
|
+
collection=item_metadata.product_id,
|
|
548
|
+
properties={
|
|
549
|
+
"license": item_metadata.license,
|
|
550
|
+
"description": item_metadata.description,
|
|
551
|
+
},
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
extra_fields = {}
|
|
555
|
+
for key, value in (item_metadata.extra_fields or {}).items():
|
|
556
|
+
extra_fields[key] = value
|
|
557
|
+
|
|
558
|
+
item.add_asset(
|
|
559
|
+
key="data",
|
|
560
|
+
asset=pystac.Asset(
|
|
561
|
+
href=item_metadata.data_url,
|
|
562
|
+
media_type=item_metadata.data_mime_type,
|
|
563
|
+
roles=["data"],
|
|
564
|
+
title=item_metadata.data_title,
|
|
565
|
+
extra_fields=extra_fields
|
|
566
|
+
),
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
return item
|