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/git_add.py ADDED
@@ -0,0 +1,383 @@
1
+ import pystac
2
+ from pathlib import Path
3
+ import json
4
+ from typing import Any, Mapping
5
+
6
+ REMOTE_URL = 'https://esa-earthcode.github.io/open-science-catalog-metadata/'
7
+
8
+
9
+ def _add_link_if_missing(stac_obj: pystac.STACObject, link: pystac.Link) -> None:
10
+ """Adds a link only when no existing link has the same rel and href."""
11
+
12
+ for existing in stac_obj.get_links():
13
+ if existing.rel == link.rel and existing.href == link.href:
14
+ return
15
+ stac_obj.add_link(link)
16
+
17
+
18
+ def _require_product_field(product_dict: Mapping[str, Any], key: str) -> Any:
19
+ """Ensures a required product metadata key exists and is non-empty."""
20
+
21
+ value = product_dict.get(key)
22
+ if value is None:
23
+ raise ValueError(f"Missing required product field: {key}")
24
+ if isinstance(value, (list, str, dict)) and len(value) == 0:
25
+ raise ValueError(f"Empty required product field: {key}")
26
+ return value
27
+
28
+
29
+ def _collection_to_dict(
30
+ collection: dict[str, Any] | pystac.Collection, context_name: str
31
+ ) -> dict[str, Any]:
32
+ """Normalizes a collection input to a dictionary representation."""
33
+
34
+ if isinstance(collection, pystac.Collection):
35
+ return collection.to_dict()
36
+ if isinstance(collection, dict):
37
+ return collection
38
+ raise TypeError(f"{context_name} must be a dict or pystac.Collection")
39
+
40
+
41
+ def save_catalog_with_remote_selfhref(
42
+ catalog_object: pystac.Catalog,
43
+ local_catalog_path: Path,
44
+ catalog_extension: str,
45
+ ) -> None:
46
+ """Saves a catalog JSON file while forcing the self link to the configured remote OSC URL."""
47
+
48
+ # set remote href
49
+ remote_catalog_path = REMOTE_URL + catalog_extension
50
+
51
+ # overwrite self reference to be the online one
52
+ catalog_dict = catalog_object.to_dict()
53
+ for link in catalog_dict['links']:
54
+ if link['rel'] == 'self':
55
+ link['href'] = remote_catalog_path
56
+
57
+ with open(local_catalog_path, 'w', encoding='utf-8') as f:
58
+ json.dump(catalog_dict, f, indent=2, ensure_ascii=False)
59
+
60
+
61
+ # save project to catalog
62
+ def save_project_collection_to_osc(
63
+ project_collection: dict[str, Any] | pystac.Collection,
64
+ catalog_root: Path,
65
+ ) -> None:
66
+ """Writes a project collection into the local OSC tree and links it from the projects catalog."""
67
+
68
+ project_dict = _collection_to_dict(project_collection, "project_collection")
69
+ project_id = project_dict["id"]
70
+ project_title = project_dict.get("title")
71
+
72
+ # create a directory under /projects with the same ID as the project ID
73
+ project_dir = catalog_root / 'projects' / project_id
74
+ project_dir.mkdir(parents=True, exist_ok=True)
75
+
76
+ # save the collection in the new folder
77
+ with open(project_dir / 'collection.json', 'w', encoding='utf-8') as f:
78
+ json.dump(project_dict, f, indent=2, ensure_ascii=False)
79
+
80
+ # create a link from the parent Projects catalog to the new item.
81
+ catalog_extension = 'projects/catalog.json'
82
+ local_catalog_path = catalog_root / catalog_extension
83
+ projects_catalog = pystac.Catalog.from_file(local_catalog_path)
84
+ _add_link_if_missing(
85
+ projects_catalog,
86
+ pystac.Link(
87
+ rel='child',
88
+ target=f'./{project_id}/collection.json',
89
+ media_type="application/json",
90
+ title=project_title
91
+
92
+ )
93
+ )
94
+ save_catalog_with_remote_selfhref(projects_catalog, local_catalog_path, catalog_extension)
95
+
96
+
97
+
98
+
99
+ def save_product_collection_to_catalog(
100
+ product_collection: dict[str, Any] | pystac.Collection,
101
+ catalog_root: Path,
102
+ ) -> None:
103
+ """Writes a product collection and updates all related reverse links in projects, themes, variables, and missions catalogs."""
104
+
105
+ product_dict = _collection_to_dict(product_collection, "product_collection")
106
+ product_id = product_dict["id"]
107
+ product_title = product_dict.get("title")
108
+ project_id = _require_product_field(product_dict, 'osc:project')
109
+ product_themes = [p['concepts'][0]['id'] for p in _require_product_field(product_dict, 'themes')]
110
+ product_variables = [v for v in _require_product_field(product_dict, 'osc:variables')]
111
+ product_missions = [m for m in _require_product_field(product_dict, 'osc:missions')]
112
+
113
+
114
+ # create a directory under /projects with the same ID as the project ID
115
+ product_dir = catalog_root / 'products' / product_id
116
+ product_dir.mkdir(parents=True, exist_ok=True)
117
+
118
+
119
+ # create a link from the parent products catalog to the new item.
120
+ catalog_extension = 'products/catalog.json'
121
+ local_catalog_path = catalog_root / catalog_extension
122
+ products_catalog = pystac.Catalog.from_file(local_catalog_path)
123
+ _add_link_if_missing(
124
+ products_catalog,
125
+ pystac.Link(
126
+ rel='child',
127
+ target=f'./{product_id}/collection.json',
128
+ media_type="application/json",
129
+ title=f'{product_title}'
130
+ )
131
+ )
132
+ save_catalog_with_remote_selfhref(products_catalog, local_catalog_path, catalog_extension)
133
+
134
+ # add product to project Collection
135
+ with open(catalog_root / f'projects/{project_id}/collection.json') as f:
136
+ project_collection = json.load(f)
137
+ project_collection = pystac.Collection.from_dict(project_collection,
138
+ migrate=False,
139
+ root=None,
140
+ preserve_dict=True)
141
+ _add_link_if_missing(
142
+ project_collection,
143
+ pystac.Link(
144
+ rel='child',
145
+ target=f'../../products/{product_id}/collection.json',
146
+ media_type="application/json",
147
+ title=f'{product_title}'
148
+ )
149
+ )
150
+ with open(catalog_root / f'projects/{project_id}/collection.json', 'w') as f:
151
+ json.dump(
152
+ project_collection.to_dict(include_self_link=False, transform_hrefs=False),
153
+ f, ensure_ascii=False, indent=2)
154
+
155
+
156
+ # add theme return links
157
+ for theme in product_themes:
158
+ catalog_extension = f'themes/{theme}/catalog.json'
159
+ local_catalog_path = catalog_root / catalog_extension
160
+ theme_catalog = pystac.Catalog.from_file(local_catalog_path)
161
+ _add_link_if_missing(
162
+ theme_catalog,
163
+ pystac.Link(
164
+ rel='child',
165
+ target=f'../../products/{product_id}/collection.json',
166
+ media_type="application/json",
167
+ title=f'{product_title}'
168
+ )
169
+ )
170
+ save_catalog_with_remote_selfhref(theme_catalog, local_catalog_path, catalog_extension)
171
+
172
+ # add variable return links
173
+ for var in product_variables:
174
+ catalog_extension = f'variables/{var}/catalog.json'
175
+ local_catalog_path = catalog_root / catalog_extension
176
+ var_catalog = pystac.Catalog.from_file(local_catalog_path)
177
+ _add_link_if_missing(
178
+ var_catalog,
179
+ pystac.Link(
180
+ rel='child',
181
+ target=f'../../products/{product_id}/collection.json',
182
+ media_type="application/json",
183
+ title=f'{product_title}'
184
+ )
185
+ )
186
+ save_catalog_with_remote_selfhref(var_catalog, local_catalog_path, catalog_extension)
187
+
188
+
189
+ # add mission return links
190
+ for mission in product_missions:
191
+ catalog_extension = f'eo-missions/{mission}/catalog.json'
192
+ local_catalog_path = catalog_root / catalog_extension
193
+ mission_catalog = pystac.Catalog.from_file(local_catalog_path)
194
+ _add_link_if_missing(
195
+ mission_catalog,
196
+ pystac.Link(
197
+ rel='child',
198
+ target=f'../../products/{product_id}/collection.json',
199
+ media_type="application/json",
200
+ title=f'{product_title}'
201
+ )
202
+ )
203
+ save_catalog_with_remote_selfhref(mission_catalog, local_catalog_path, catalog_extension)
204
+
205
+ # update link titles
206
+ for link in product_dict.get("links", []):
207
+ if link.get("rel") != "related":
208
+ continue
209
+ href = link.get("href", "")
210
+ link_elements = href.split('/')
211
+ if len(link_elements) > 3 and link_elements[2] in ['variables', 'eo-missions']:
212
+ catalog_title = pystac.Catalog.from_file(
213
+ catalog_root / f'{link_elements[2]}/{link_elements[3]}/catalog.json'
214
+ ).title
215
+ prefix = 'Variable: ' if link_elements[2] == 'variables' else 'EO Mission: '
216
+ link["title"] = prefix + catalog_title
217
+
218
+ # save the collection in the new folder
219
+ with open(product_dir / 'collection.json', 'w', encoding='utf-8') as f:
220
+ json.dump(product_dict, f, indent=2, ensure_ascii=False)
221
+
222
+
223
+ def save_workflow_record_to_osc(
224
+ workflow_record: dict[str, Any], catalog_root: Path
225
+ ) -> None:
226
+ """Writes a workflow record into the local OSC tree and links it from the workflows catalog."""
227
+
228
+ # create a directory under /projects with the same ID as the project ID
229
+ wf_dir = catalog_root / 'workflows' / workflow_record['id']
230
+ wf_dir.mkdir(parents=True, exist_ok=True)
231
+
232
+ # save the record in the new folder
233
+ with open(wf_dir / 'record.json', 'w', encoding='utf-8') as f:
234
+ json.dump(workflow_record, f, indent=2, ensure_ascii=False)
235
+
236
+ # create a link from the parent Projects catalog to the new item.
237
+ catalog_extension = 'workflows/catalog.json'
238
+ local_catalog_path = catalog_root / catalog_extension
239
+ wf_catalog = pystac.Catalog.from_file(local_catalog_path)
240
+ _add_link_if_missing(
241
+ wf_catalog,
242
+ pystac.Link(
243
+ rel='item',
244
+ target=f"./{workflow_record['id']}/record.json",
245
+ media_type="application/json",
246
+ title=workflow_record['properties']['title']
247
+
248
+ )
249
+ )
250
+ save_catalog_with_remote_selfhref(wf_catalog, local_catalog_path, catalog_extension)
251
+
252
+
253
+ def save_experiment_record_to_osc(
254
+ experiment_record: dict[str, Any], catalog_root: Path
255
+ ) -> None:
256
+ """Writes an experiment record into the local OSC tree and links it from the experiments catalog."""
257
+
258
+ # create a directory under /projects with the same ID as the project ID
259
+ experiment_dir = catalog_root / 'experiments' / experiment_record['id']
260
+ experiment_dir.mkdir(parents=True, exist_ok=True)
261
+
262
+ # save the record in the new folder
263
+ with open(experiment_dir / 'record.json', 'w', encoding='utf-8') as f:
264
+ json.dump(experiment_record, f, indent=2, ensure_ascii=False)
265
+
266
+ # create a link from the parent Projects catalog to the new item.
267
+ catalog_extension = 'experiments/catalog.json'
268
+ local_catalog_path = catalog_root / catalog_extension
269
+ experiments_catalog = pystac.Catalog.from_file(local_catalog_path)
270
+ _add_link_if_missing(
271
+ experiments_catalog,
272
+ pystac.Link(
273
+ rel='item',
274
+ target=f"./{experiment_record['id']}/record.json",
275
+ media_type="application/json",
276
+ title=experiment_record['properties']['title']
277
+
278
+ )
279
+ )
280
+ save_catalog_with_remote_selfhref(experiments_catalog, local_catalog_path, catalog_extension)
281
+
282
+
283
+ def save_item_to_product_collection(
284
+ item: pystac.Item, product_collection: pystac.Collection | str, catalog_root: Path
285
+ ) -> None:
286
+ """Adds parent and collection links to an item and saves it under the target product directory."""
287
+
288
+ if type(product_collection) is str:
289
+ with open(catalog_root/f'products/{product_collection}/collection.json', 'r', encoding='utf-8') as f:
290
+ product_collection = json.load(f)
291
+ product_collection = pystac.Collection.from_dict(product_collection,
292
+ migrate=False,
293
+ root=None,
294
+ preserve_dict=True)
295
+
296
+ item.add_link(pystac.Link.from_dict(
297
+ {
298
+ "rel": "collection",
299
+ "href": "./collection.json",
300
+ "type": "application/json",
301
+ "title": product_collection.title
302
+ }
303
+ ))
304
+
305
+ item.add_link(pystac.Link.from_dict({
306
+ "rel": "parent",
307
+ "href": "./collection.json",
308
+ "type": "application/json",
309
+ "title": product_collection.title
310
+ },
311
+ ))
312
+
313
+
314
+ item.save_object(
315
+ include_self_link=False,
316
+ dest_href=catalog_root/f'products/{product_collection.id}/{item.id}.json'
317
+ )
318
+
319
+ # add to product collection if not already existing
320
+ with open(catalog_root / f'products/{product_collection.id}/collection.json') as f:
321
+ existing_product_collection = json.load(f)
322
+ existing_product_collection = pystac.Collection.from_dict(existing_product_collection,
323
+ migrate=False,
324
+ root=None,
325
+ preserve_dict=True)
326
+ _add_link_if_missing(
327
+ existing_product_collection,
328
+ pystac.Link(rel="item", target=f"./{item.id}.json", media_type="application/json", title=item.assets['data'].title)
329
+ )
330
+
331
+ with open(catalog_root / f'products/{product_collection.id}/collection.json', 'w', encoding='utf-8') as f:
332
+ json.dump(
333
+ existing_product_collection.to_dict(include_self_link=False, transform_hrefs=False),
334
+ f, ensure_ascii=False, indent=2)
335
+
336
+
337
+
338
+ def save_item_links_to_product_collection(catalog_root: Path, product_id: str, item_link: str, access_link: str=None, documentation_link: str=None):
339
+ """Adds links to an existing product collection"""
340
+
341
+ with open(catalog_root/f'products/{product_id}/collection.json', 'r', encoding='utf-8') as f:
342
+ product_collection = json.load(f)
343
+ product_collection = pystac.Collection.from_dict(product_collection,
344
+ migrate=False,
345
+ root=None,
346
+ preserve_dict=True)
347
+ links = [
348
+ pystac.Link.from_dict({
349
+ "rel": "child",
350
+ "href": item_link,
351
+ "type": "application/json",
352
+ "title": "PRR Data Collection"
353
+ }
354
+ )
355
+ ]
356
+
357
+ if documentation_link:
358
+ links.append(
359
+ pystac.Link.from_dict({
360
+ "rel": "via",
361
+ "href": documentation_link,
362
+ "type": "application/json",
363
+ "title": "Documentation"
364
+ }
365
+ )
366
+ )
367
+
368
+ if access_link:
369
+ links.append(
370
+ pystac.Link.from_dict({
371
+ "rel": "via",
372
+ "href": access_link,
373
+ "type": "application/json",
374
+ "title": "Access"
375
+ }
376
+ )
377
+ )
378
+
379
+ product_collection.add_links(links)
380
+ with open(catalog_root / f'products/{product_collection.id}/collection.json', 'w', encoding='utf-8') as f:
381
+ json.dump(
382
+ product_collection.to_dict(include_self_link=False, transform_hrefs=False),
383
+ f, ensure_ascii=False, indent=2)
@@ -0,0 +1,21 @@
1
+ import base64
2
+ import json
3
+ from urllib.parse import quote
4
+ import pystac
5
+
6
+ def generate_osc_editor_link(json_object, object_type, session_title=None) -> None:
7
+
8
+ if type(json_object) is pystac.Collection:
9
+ json_object = json_object.to_dict()
10
+
11
+ if session_title is None:
12
+ session_title = json_object['title']
13
+ session_title = quote(session_title, safe="")
14
+ # Use URL-safe base64 encoding (replaces + with - and / with _)
15
+
16
+ base64_content = base64.urlsafe_b64encode(json.dumps(json_object).encode("utf-8")).decode("utf-8")
17
+
18
+ # https://workspace.earthcode-staging.earthcode.eox.at/osc-editor?session=<your session title, e.g. "Add File">&automation=add-file&type=<osc type, e.g. "product">&file=<base64encoded content>
19
+ url = f"https://workspace.earthcode-staging.earthcode.eox.at/osc-editor?session={session_title}&automation=add-file&&type={object_type}&file={base64_content}"
20
+
21
+ return url