fusion-platform-python-sdk 1.11.1__tar.gz → 1.13.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {fusion-platform-python-sdk-1.11.1/fusion_platform_python_sdk.egg-info → fusion-platform-python-sdk-1.13.1}/PKG-INFO +1 -1
  2. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/__init__.py +5 -2
  3. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/common/utilities.py +44 -0
  4. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/fusion_platform_sdk.pdf +0 -0
  5. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/data.py +80 -0
  6. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/data_file.py +177 -1
  7. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/model.py +17 -0
  8. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process.py +5 -3
  9. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/service.py +2 -1
  10. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/translations.py +2 -0
  11. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1/fusion_platform_python_sdk.egg-info}/PKG-INFO +1 -1
  12. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/setup.py +1 -1
  13. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/LICENSE.txt +0 -0
  14. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/MANIFEST.in +0 -0
  15. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/README.md +0 -0
  16. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/__main__.py +0 -0
  17. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/base.py +0 -0
  18. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/command.py +0 -0
  19. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/common/__init__.py +0 -0
  20. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/common/raise_thread.py +0 -0
  21. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/detailed_example.py +0 -0
  22. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/documentation.py +0 -0
  23. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/glasgow.geojson +0 -0
  24. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/lake_district.geojson +0 -0
  25. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/localisations.py +0 -0
  26. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/localise.py +0 -0
  27. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/__init__.py +0 -0
  28. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/credit.md +0 -0
  29. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/credit.py +0 -0
  30. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/data.md +0 -0
  31. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/data_file.md +0 -0
  32. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/fields.py +0 -0
  33. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/organisation.md +0 -0
  34. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/organisation.py +0 -0
  35. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process.md +0 -0
  36. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_execution.md +0 -0
  37. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_execution.py +0 -0
  38. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_service_execution.md +0 -0
  39. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_service_execution.py +0 -0
  40. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_service_execution_log.md +0 -0
  41. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/process_service_execution_log.py +0 -0
  42. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/service.md +0 -0
  43. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/user.md +0 -0
  44. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/models/user.py +0 -0
  45. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/quick_example.py +0 -0
  46. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform/session.py +0 -0
  47. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform_python_sdk.egg-info/SOURCES.txt +0 -0
  48. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform_python_sdk.egg-info/dependency_links.txt +0 -0
  49. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform_python_sdk.egg-info/entry_points.txt +0 -0
  50. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform_python_sdk.egg-info/requires.txt +0 -0
  51. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/fusion_platform_python_sdk.egg-info/top_level.txt +0 -0
  52. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/setup.cfg +0 -0
  53. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/tests/test_base.py +0 -0
  54. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/tests/test_command.py +0 -0
  55. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/tests/test_init.py +0 -0
  56. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/tests/test_localise.py +0 -0
  57. {fusion-platform-python-sdk-1.11.1 → fusion-platform-python-sdk-1.13.1}/tests/test_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fusion-platform-python-sdk
3
- Version: 1.11.1
3
+ Version: 1.13.1
4
4
  Summary: Python SDK used to interact with the Fusion Platform(r)
5
5
  Home-page: https://github.com/d-cat-support/fusion-platform-python-sdk
6
6
  Author: Digital Content Analysis Technology Ltd
@@ -11,8 +11,8 @@ API, and this is defined within the corresponding OpenAPI 3.0 specification, whi
11
11
  """
12
12
 
13
13
  # Do not modify the following two lines as they are maintained by the version.sh script.
14
- __version__ = '1.11.1'
15
- __version_date__ = '2024-04-08T10:20:23Z'
14
+ __version__ = '1.13.1'
15
+ __version_date__ = '2024-07-15T06:23:24Z'
16
16
 
17
17
  # Exclude certain sub-modules from documentation.
18
18
  # @formatter:off
@@ -102,6 +102,9 @@ FILE_TYPE_CSV = 'CSV'
102
102
  FILE_TYPE_ESRI_SHAPEFILE = 'ESRI Shapefile'
103
103
  FILE_TYPE_JPEG = 'JPEG'
104
104
  FILE_TYPE_PNG = 'PNG'
105
+ FILE_TYPE_PDF = 'PDF'
106
+ FILE_TYPE_GZIP = 'GZIP'
107
+ FILE_TYPE_ZIP = 'ZIP'
105
108
  FILE_TYPE_OTHER = 'Other'
106
109
 
107
110
  # Run types.
@@ -8,9 +8,40 @@ author: Matthew Casey
8
8
 
9
9
  from datetime import date, datetime, time, timezone
10
10
  from decimal import Decimal
11
+ import re
11
12
  from types import MappingProxyType
12
13
 
13
14
 
15
+ def datetime_parse(string_or_blank):
16
+ """
17
+ Attempts to parse an ISO8601 datetime from a string using common formats.
18
+
19
+ Args:
20
+ string_or_blank: The datetime to parse, or blank.
21
+
22
+ Returns:
23
+ The parsed datetime or None if not parsable or blank.
24
+ """
25
+ parsed = None
26
+
27
+ if not string_blank(string_or_blank):
28
+ # Strip out all optional delimiters and then attempt to parse with and without the "Z".
29
+ stripped = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', str(string_or_blank))
30
+ formats = ['%Y%m%dT%H%M%S.%f', '%Y%m%dT%H%M%S.%f%z', '%Y%m%dT%H%M%S', '%Y%m%dT%H%M%S%z']
31
+
32
+ for format in formats:
33
+ try:
34
+ parsed = datetime.strptime(stripped, format)
35
+ parsed = parsed.astimezone(timezone.utc) if parsed.tzinfo is None else parsed # Assume UTC if no timezone is provided.
36
+ except:
37
+ pass
38
+
39
+ if parsed is not None:
40
+ break
41
+
42
+ return parsed
43
+
44
+
14
45
  def dict_nested_get(dictionary_or_value, keys, default=None):
15
46
  """
16
47
  Performs a dictionary.get(key, default) using the supplied list of keys assuming that each successive key is nested.
@@ -60,6 +91,19 @@ def json_default(value):
60
91
  return str(value)
61
92
 
62
93
 
94
+ def string_blank(string_or_blank):
95
+ """
96
+ Checks if a string is None or blank.
97
+
98
+ Args:
99
+ string_or_blank: The string to check.
100
+
101
+ Returns:
102
+ True if the string is None, blank ("") or just made up with spaces.
103
+ """
104
+ return (string_or_blank is None) or (not bool(string_or_blank.strip()))
105
+
106
+
63
107
  def string_camel_to_underscore(string):
64
108
  """
65
109
  Converts a camel case string to underscore case.
@@ -44,10 +44,12 @@ class DataSchema(Schema):
44
44
  bounds = fields.List(fields.Decimal(required=True), allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
45
45
  file_with_preview = fields.UUID(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
46
46
 
47
+ # Removed uploaded.
47
48
  uploaded_organisation_id = fields.UUID(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
48
49
  deletable = fields.String(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
49
50
 
50
51
  # Removed creator.
52
+
51
53
  # Removed search.
52
54
 
53
55
  class Meta:
@@ -341,3 +343,81 @@ class Data(Model):
341
343
  ModelError: if a model could not be loaded or validated from the Fusion Platform<sup>&reg;</sup>.
342
344
  """
343
345
  return DataFile._models_from_api_path(self._session, self._get_path(self.__class__._PATH_FILES), organisation_id=self.organisation_id)
346
+
347
+ def get_stac_collection(self, items, collection_file_name=DataFile._STAC_COLLECTION_FILE_NAME, owner=None, created_at=None, detail=None):
348
+ """
349
+ Converts the data representation into a STAC collection with the specified STAC item file names.
350
+
351
+ Args:
352
+ items: The list of items to be put into the collection. This should be a tuple of (STAC item, item_file_name).
353
+ collection_file_name: The optional collection file name.
354
+ owner: The optional owner of the collection. Default None.
355
+ created_at: Optionally when the collection was created. Default None.
356
+ detail: The optional collection detail as a dictionary. Default None.
357
+
358
+ Returns:
359
+ A tuple of the STAC collection definition as a dictionary and the collection file name used in the definition.
360
+ """
361
+ stac_extensions = []
362
+
363
+ # Form the providers.
364
+ provider = {
365
+ 'name': i18n.t('fusion_platform.organisation'),
366
+ 'roles': ['producer', 'licensor'],
367
+ 'url': i18n.t('fusion_platform.url')
368
+ }
369
+
370
+ # Add in the optional processing information, if any.
371
+ if owner is not None:
372
+ provider['processing:lineage'] = owner
373
+
374
+ if created_at is not None:
375
+ provider['processing.datetime'] = created_at
376
+
377
+ if detail is not None:
378
+ provider['processing:software'] = detail
379
+
380
+ if (owner is not None) or (created_at is not None) or (detail is not None):
381
+ stac_extensions.append('https://stac-extensions.github.io/processing/v1.2.0/schema.json')
382
+
383
+ # Form the links.
384
+ links = [
385
+ {'rel': 'self', 'href': collection_file_name, 'type': 'application/json'},
386
+ {'rel': 'root', 'href': collection_file_name, 'type': 'application/json'},
387
+ ]
388
+
389
+ # Add in the files calculating the maximal spatial and temporal extents.
390
+ bbox = None
391
+ interval_start = None
392
+ interval_end = None
393
+
394
+ for item, item_file_name in items:
395
+ # Calculate the spatial extent.
396
+ item_bbox = item.get('bbox')
397
+
398
+ if (bbox is None) or (len(bbox) < 4):
399
+ bbox = item_bbox
400
+
401
+ if (bbox is not None) and (len(bbox) >= 4) and (item_bbox is not None) and (len(item_bbox) >= 4):
402
+ bbox = [min(bbox[0], item_bbox[0]), min(bbox[1], item_bbox[1]), max(bbox[2], item_bbox[2]), max(bbox[3], item_bbox[3])]
403
+
404
+ # Calculate the temporal extent.
405
+ item_datetime = dict_nested_get(item, ['properties', 'datetime'])
406
+ interval_start = item_datetime if interval_start is None else min(interval_start, item_datetime)
407
+ interval_end = item_datetime if interval_end is None else max(interval_end, item_datetime)
408
+
409
+ # Add in the link.
410
+ links.append({'rel': 'item', 'href': item_file_name, 'type': 'application/geo+json'})
411
+
412
+ # Build the STAC collection.
413
+ return {
414
+ 'type': 'Collection',
415
+ 'stac_version': '1.0.0',
416
+ 'stac_extensions': stac_extensions,
417
+ 'id': self.id,
418
+ 'description': self.name,
419
+ 'license': 'proprietary',
420
+ 'providers': [provider],
421
+ 'extent': {'spatial': {'bbox': [bbox]}, 'temporal': {'interval': [[interval_start, interval_end]]}},
422
+ 'links': links
423
+ }, collection_file_name
@@ -8,9 +8,11 @@ author: Matthew Casey
8
8
 
9
9
  import i18n
10
10
  from marshmallow import Schema, EXCLUDE
11
+ import os
11
12
 
13
+ import fusion_platform
12
14
  from fusion_platform.common.raise_thread import RaiseThread
13
- from fusion_platform.common.utilities import dict_nested_get
15
+ from fusion_platform.common.utilities import datetime_parse, dict_nested_get
14
16
  from fusion_platform.models import fields
15
17
  from fusion_platform.models.model import Model, ModelError
16
18
 
@@ -65,6 +67,7 @@ class DataFileSchema(Schema):
65
67
  organisation_id = fields.UUID(allow_none=True, metadata={'read_only': True, 'title': i18n.t('models.data_file.organisation_id.title'), 'description': i18n.t(
66
68
  'models.data_file.organisation_id.description')}) # Added parameter so that it can be used.
67
69
  data_id = fields.UUID(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
70
+ # Removed organisation_id_file_type.
68
71
 
69
72
  file_id = fields.UUID(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
70
73
  preview_file_id = fields.UUID(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
@@ -120,6 +123,34 @@ class DataFile(Model):
120
123
  # Plus query parameters.
121
124
  _QUERY_PREVIEW = 'preview'
122
125
 
126
+ # Geospatial file types.
127
+ _GEOSPATIAL_RASTER = 'raster'
128
+ _GEOSPATIAL_VECTOR = 'vector'
129
+
130
+ # File type information - a tuple of typical extension and whether the file type is a geospatial vector, raster or not.
131
+ # @formatter:off
132
+ _FILE_TYPES = {
133
+ fusion_platform.FILE_TYPE_GEOTIFF: ('tif', _GEOSPATIAL_RASTER),
134
+ fusion_platform.FILE_TYPE_JPEG2000: ('jpeg', _GEOSPATIAL_RASTER),
135
+ fusion_platform.FILE_TYPE_DEM: ('dem', _GEOSPATIAL_RASTER),
136
+ fusion_platform.FILE_TYPE_GEOJSON: ('json', _GEOSPATIAL_VECTOR),
137
+ fusion_platform.FILE_TYPE_KML: ('kml', _GEOSPATIAL_VECTOR),
138
+ fusion_platform.FILE_TYPE_KMZ: ('kmz', _GEOSPATIAL_VECTOR),
139
+ fusion_platform.FILE_TYPE_CSV: ('csv', None),
140
+ fusion_platform.FILE_TYPE_ESRI_SHAPEFILE: ('zip', _GEOSPATIAL_VECTOR),
141
+ fusion_platform.FILE_TYPE_JPEG: ('jpg', None),
142
+ fusion_platform.FILE_TYPE_PNG: ('png', None),
143
+ fusion_platform.FILE_TYPE_PDF: ('pdf', None),
144
+ fusion_platform.FILE_TYPE_GZIP: ('gz', None),
145
+ fusion_platform.FILE_TYPE_ZIP: ('zip', None),
146
+ fusion_platform.FILE_TYPE_OTHER: ('*', None),
147
+ }
148
+ # @formatter:on
149
+
150
+ # STAC file name constants. We do not use ".json" because this is used by services and specific file types. Similarly, the name starts with "." to hide it.
151
+ _STAC_COLLECTION_FILE_NAME = '.collection.stac'
152
+ _STAC_ITEM_FILE_NAME_FORMAT = '.{file_name}.stac'
153
+
123
154
  def __init__(self, session):
124
155
  """
125
156
  Initialises the object.
@@ -249,3 +280,148 @@ class DataFile(Model):
249
280
  raise ModelError(i18n.t('models.data_file.failed_download_url'))
250
281
 
251
282
  return url
283
+
284
+ def get_stac_item(self, item_file_name_format=_STAC_ITEM_FILE_NAME_FORMAT, collection_file_name=_STAC_COLLECTION_FILE_NAME):
285
+ """
286
+ Converts the data file representation into a STAC item.
287
+
288
+ Args:
289
+ item_file_name_format: The optional item file name format string, where the "file_name" placeholder is replaced with the item's file name.
290
+ collection_file_name: The optional owning collection file name.
291
+
292
+ Returns:
293
+ A tuple of the STAC item definition as a dictionary and the item file name used in the definition.
294
+ """
295
+
296
+ # Obtain all the relevant information.
297
+ bounds = self.bounds if hasattr(self, Model._FIELD_BOUNDS) else None
298
+ crs = self.crs if hasattr(self, Model._FIELD_CRS) else None
299
+ file_name = self.file_name if hasattr(self, Model._FIELD_FILE_NAME) else None
300
+ size = self.size if hasattr(self, Model._FIELD_SIZE) else None
301
+
302
+ # Attempt to find the file type to get its extension and geospatial type. Default to "Other" if not found.
303
+ found_file_type = DataFile._FILE_TYPES.get(self.file_type)
304
+
305
+ if found_file_type is None:
306
+ found_file_type = DataFile._FILE_TYPES.get(fusion_platform.FILE_TYPE_OTHER)
307
+
308
+ geospatial = found_file_type[1]
309
+
310
+ # Get the file extension.
311
+ extension = os.path.splitext(file_name)[1] if file_name is not None else None
312
+ extension = found_file_type[0] if extension is None else extension
313
+
314
+ if (extension is None) or (extension == '*'):
315
+ extension = ''
316
+ else:
317
+ extension = f".{extension}" if not extension.startswith('.') else extension
318
+
319
+ # Work out a suitable name for the file if one is not available.
320
+ if file_name is None:
321
+ file_name = f"{self.id}{extension}"
322
+
323
+ item_file_name = item_file_name_format.format(file_name=file_name)
324
+
325
+ # Start to build the STAC extensions list.
326
+ stac_extensions = ['https://stac-extensions.github.io/processing/v1.2.0/schema.json'] # For properties#processing.[software,version].
327
+
328
+ # Get the bounding box and geometry, if there is one.
329
+ bbox = []
330
+ geometry = {}
331
+
332
+ if (bounds is not None) and (len(bounds) >= 4):
333
+ bbox = bounds
334
+ geometry = {'type': 'Polygon', 'coordinates': [[[bbox[0], bbox[1]], [bbox[2], bbox[1]], [bbox[2], bbox[3]], [bbox[0], bbox[3]], [bbox[0], bbox[1]]]]}
335
+
336
+ # Get the datetime from the filename, if possible. We try various iterations of sections the file name until we find something.
337
+ for file_part in file_name.split('_'):
338
+ extracted_datetime = datetime_parse(file_part) or datetime_parse(f"{file_part}T00:00:00Z") # Assume UTC.
339
+
340
+ if extracted_datetime is not None:
341
+ break
342
+
343
+ if extracted_datetime is None:
344
+ extracted_datetime = self.created_at
345
+
346
+ # Start to build the properties.
347
+ properties = {'processing.software': i18n.t('fusion_platform.sdk'), 'processing.version': fusion_platform.__version__, 'datetime': extracted_datetime}
348
+
349
+ # Add in the CRS, if there is one.
350
+ if crs is not None:
351
+ properties['proj:epsg'] = crs
352
+ stac_extensions.append('https://stac-extensions.github.io/projection/v1.1.0/schema.json') # For properties#proj:epsg.
353
+
354
+ # Build the links.
355
+ links = [{'rel': 'self', 'href': item_file_name, 'type': 'application/json'}]
356
+
357
+ # Add in the optional owning collection.
358
+ if collection_file_name is not None:
359
+ links.append({'rel': 'root', 'href': collection_file_name, 'type': 'application/json'})
360
+ links.append({'rel': 'parent', 'href': collection_file_name, 'type': 'application/json'})
361
+
362
+ # Start to build the asset from the associated file.
363
+ asset = {'href': file_name, 'roles': []}
364
+
365
+ # Extract out the relevant values depending upon the file type.
366
+ raster_bands = None
367
+ eo_bands = None
368
+
369
+ if (geospatial == DataFile._GEOSPATIAL_RASTER) and hasattr(self, Model._FIELD_SELECTORS) and (self.selectors is not None):
370
+ for selector in self.selectors:
371
+ raster_band = {}
372
+ eo_band = {}
373
+
374
+ if hasattr(self, Model._FIELD_RESOLUTION):
375
+ raster_band['spatial_resolution'] = self.resolution
376
+
377
+ if selector.get(Model._FIELD_UNIT) is not None:
378
+ raster_band['unit'] = selector.get(Model._FIELD_UNIT)
379
+
380
+ if (selector.get(Model._FIELD_MINIMUM) is not None) and (selector.get(Model._FIELD_MAXIMUM) is not None) and (
381
+ selector.get(Model._FIELD_MEAN) is not None) and (selector.get(Model._FIELD_SD) is not None):
382
+ raster_band['statistics'] = {
383
+ 'minimum': selector.get(Model._FIELD_MINIMUM),
384
+ 'maximum': selector.get(Model._FIELD_MAXIMUM),
385
+ 'mean': selector.get(Model._FIELD_MEAN),
386
+ 'stddev': selector.get(Model._FIELD_SD)
387
+ }
388
+
389
+ if (selector.get(Model._FIELD_HISTOGRAM_MINIMUM) is not None) and (selector.get(Model._FIELD_HISTOGRAM_MAXIMUM) is not None) and (
390
+ selector.get(Model._FIELD_HISTOGRAM) is not None):
391
+ raster_band['histogram'] = {
392
+ 'count': len(selector.get(Model._FIELD_HISTOGRAM)),
393
+ 'min': selector.get(Model._FIELD_HISTOGRAM_MINIMUM),
394
+ 'max': selector.get(Model._FIELD_HISTOGRAM_MAXIMUM),
395
+ 'buckets': selector.get(Model._FIELD_HISTOGRAM),
396
+ }
397
+
398
+ if selector.get(Model._FIELD_CATEGORY) is not None:
399
+ eo_band['name'] = selector.get(Model._FIELD_CATEGORY)
400
+
401
+ raster_bands = [raster_band] if raster_bands is None else [raster_band] + raster_bands
402
+ eo_bands = [eo_band] if eo_bands is None else [eo_band] + eo_bands
403
+
404
+ stac_extensions.append('https://stac-extensions.github.io/eo/v1.1.0/schema.json') # For assets#eo:bands.
405
+ stac_extensions.append('https://stac-extensions.github.io/raster/v1.1.0/schema.json') # For assets#raster:bands.
406
+
407
+ if raster_bands is not None:
408
+ asset['raster:bands'] = raster_bands
409
+ asset['eo:bands'] = eo_bands
410
+
411
+ # Add in the file size, if there is one.
412
+ if size is not None:
413
+ asset['file:size'] = size
414
+ stac_extensions.append('https://stac-extensions.github.io/file/v2.1.0/schema.json') # For assets#file:size.
415
+
416
+ # Build the STAC item.
417
+ return {
418
+ 'type': 'Feature',
419
+ 'stac_version': '1.0.0',
420
+ 'stac_extensions': stac_extensions,
421
+ 'id': self.id,
422
+ 'bbox': bbox,
423
+ 'geometry': geometry,
424
+ 'properties': properties,
425
+ 'links': links,
426
+ 'assets': {'asset': asset}
427
+ }, item_file_name
@@ -41,8 +41,11 @@ class Model(Base):
41
41
 
42
42
  # Useful fields and templates.
43
43
  _FIELD_AVAILABLE_DISPATCHERS = 'available_dispatchers'
44
+ _FIELD_BOUNDS = 'bounds'
45
+ _FIELD_CATEGORY = 'category'
44
46
  _FIELD_CONSTRAINED_NAMES = 'constrained_names'
45
47
  _FIELD_CONSTRAINED_VALUES = 'constrained_values'
48
+ _FIELD_CRS = 'crs'
46
49
  _FIELD_DATA_TYPE = 'data_type'
47
50
  _FIELD_DISPATCH_INTERMEDIATE = 'dispatch_intermediate'
48
51
  _FIELD_DISPATCHERS = 'dispatchers'
@@ -50,23 +53,37 @@ class Model(Base):
50
53
  _FIELD_DOCUMENTATION_DESCRIPTION = 'documentation_description'
51
54
  _FIELD_ERROR = 'error'
52
55
  _FIELD_EXECUTIONS = 'executions'
56
+ _FIELD_EXTENSIONS = 'extensions'
57
+ _FIELD_FILE_NAME = 'file_name'
53
58
  _FIELD_FILE_TYPE = 'file_type'
59
+ _FIELD_GEOSPATIAL = 'geospatial'
54
60
  _FIELD_GROUP_COUNT = 'group_count'
55
61
  _FIELD_GROUP_ID = 'group_id'
56
62
  _FIELD_GROUP_INDEX = 'group_index'
57
63
  _FIELD_HAS_EXECUTIONS = 'has_executions'
64
+ _FIELD_HISTOGRAM = 'histogram'
65
+ _FIELD_HISTOGRAM_MINIMUM = 'histogram_minimum'
66
+ _FIELD_HISTOGRAM_MAXIMUM = 'histogram_maximum'
58
67
  _FIELD_ID = 'id'
59
68
  _FIELD_INPUT = 'input'
60
69
  _FIELD_INPUTS = 'inputs'
61
70
  _FIELD_KEYWORDS = 'keywords'
71
+ _FIELD_MAXIMUM = 'maximum'
72
+ _FIELD_MEAN = 'mean'
73
+ _FIELD_MINIMUM = 'minimum'
62
74
  _FIELD_NAME = 'name'
63
75
  _FIELD_OPTIONS = 'options'
64
76
  _FIELD_OUTPUTS = 'outputs'
65
77
  _FIELD_PROCESS_STATUS = 'process_status'
78
+ _FIELD_RESOLUTION = 'resolution'
79
+ _FIELD_SD = 'sd'
80
+ _FIELD_SELECTOR = 'selector'
81
+ _FIELD_SELECTORS = 'selectors'
66
82
  _FIELD_SSD_ID = 'ssd_id'
67
83
  _FIELD_STARTED_AT = 'started_at'
68
84
  _FIELD_PUBLISHABLE = 'publishable'
69
85
  _FIELD_SIZE = 'size'
86
+ _FIELD_UNIT = 'unit'
70
87
  _FIELD_VALIDATION = 'validation'
71
88
  _FIELD_VALUE = 'value'
72
89
 
@@ -473,8 +473,8 @@ class ProcessSchema(Schema):
473
473
  has_executions = fields.String(required=True, metadata={'hide': True}) # Changed to hide as an attribute.
474
474
  has_executions_organisation_id = fields.String(required=True, metadata={'hide': True}) # Changed to hide as an attribute.
475
475
  executions = fields.List(fields.Nested(ProcessExecutionStatusSchema()), allow_none=True, metadata={'hide': True}) # Changed to hide as an attribute.
476
-
477
476
  # Removed creator.
477
+
478
478
  # Removed search.
479
479
 
480
480
  available_dispatchers = fields.List(fields.Nested(ProcessDispatcherSchema()), allow_none=True, metadata={'hide': True}) # Added pseudo-field.
@@ -528,14 +528,16 @@ class Process(Model):
528
528
  fusion_platform.FILE_TYPE_GEOTIFF: [fusion_platform.FILE_TYPE_GEOTIFF, fusion_platform.FILE_TYPE_DEM, fusion_platform.FILE_TYPE_JPEG2000],
529
529
  fusion_platform.FILE_TYPE_JPEG2000: [fusion_platform.FILE_TYPE_JPEG2000],
530
530
  fusion_platform.FILE_TYPE_DEM: [fusion_platform.FILE_TYPE_DEM],
531
- fusion_platform.FILE_TYPE_GEOJSON: [fusion_platform.FILE_TYPE_GEOJSON, fusion_platform.FILE_TYPE_KML, fusion_platform.FILE_TYPE_KMZ,
532
- fusion_platform.FILE_TYPE_ESRI_SHAPEFILE],
531
+ fusion_platform.FILE_TYPE_GEOJSON: [fusion_platform.FILE_TYPE_GEOJSON, fusion_platform.FILE_TYPE_KML, fusion_platform.FILE_TYPE_KMZ, fusion_platform.FILE_TYPE_ESRI_SHAPEFILE],
533
532
  fusion_platform.FILE_TYPE_KML: [fusion_platform.FILE_TYPE_KML],
534
533
  fusion_platform.FILE_TYPE_KMZ: [fusion_platform.FILE_TYPE_KMZ],
535
534
  fusion_platform.FILE_TYPE_CSV: [fusion_platform.FILE_TYPE_CSV],
536
535
  fusion_platform.FILE_TYPE_ESRI_SHAPEFILE: [fusion_platform.FILE_TYPE_ESRI_SHAPEFILE],
537
536
  fusion_platform.FILE_TYPE_JPEG: [fusion_platform.FILE_TYPE_JPEG],
538
537
  fusion_platform.FILE_TYPE_PNG: [fusion_platform.FILE_TYPE_PNG],
538
+ fusion_platform.FILE_TYPE_PDF: [fusion_platform.FILE_TYPE_PDF],
539
+ fusion_platform.FILE_TYPE_GZIP: [fusion_platform.FILE_TYPE_GZIP],
540
+ fusion_platform.FILE_TYPE_ZIP: [fusion_platform.FILE_TYPE_ZIP],
539
541
  fusion_platform.FILE_TYPE_OTHER: [fusion_platform.FILE_TYPE_OTHER],
540
542
  }
541
543
  # @formatter:on
@@ -203,8 +203,8 @@ class ServiceSchema(Schema):
203
203
  version = fields.Integer(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
204
204
  approval_status = fields.String(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
205
205
  # Removed latest.
206
- # Removed dispatcher.
207
206
  featured = fields.String(allow_none=True)
207
+ # Removed dispatcher.
208
208
  show_in_latest = fields.Boolean(allow_none=True) # Changed to optional.
209
209
  name = fields.String(required=True)
210
210
 
@@ -234,6 +234,7 @@ class ServiceSchema(Schema):
234
234
 
235
235
  # Removed creator.
236
236
  # Removed approver.
237
+
237
238
  # Removed search.
238
239
 
239
240
  documentation_summary = fields.String(required=True)
@@ -15,10 +15,12 @@ i18n.add_translation('session.request_failed', 'API request failed: %{message}',
15
15
  i18n.add_translation('session.login_failed', 'Login failed', 'en')
16
16
  i18n.add_translation('session.missing_password', 'Password must be specified', 'en')
17
17
  i18n.add_translation('session.missing_email_user_id', 'Either an email address or a user id must be specified', 'en')
18
+ i18n.add_translation('fusion_platform.url', 'https://www.d-cat.co.uk', 'en')
18
19
  i18n.add_translation('fusion_platform.support', 'Support: support@d-cat.co.uk', 'en')
19
20
  i18n.add_translation('fusion_platform.version_date', 'Date: %{version_date}', 'en')
20
21
  i18n.add_translation('fusion_platform.version', 'Version: %{version}', 'en')
21
22
  i18n.add_translation('fusion_platform.sdk', 'Fusion Platform(r) SDK', 'en')
23
+ i18n.add_translation('fusion_platform.organisation', 'Digital Content Analysis Technology Ltd', 'en')
22
24
  i18n.add_translation('models.data_file.failed_download_url', 'Failed to get URL from download file response', 'en')
23
25
  i18n.add_translation('models.data_file.no_download', 'No download is in progress', 'en')
24
26
  i18n.add_translation('models.data_file.download_already_in_progress', 'Cannot download file as the download is already in progress', 'en')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fusion-platform-python-sdk
3
- Version: 1.11.1
3
+ Version: 1.13.1
4
4
  Summary: Python SDK used to interact with the Fusion Platform(r)
5
5
  Home-page: https://github.com/d-cat-support/fusion-platform-python-sdk
6
6
  Author: Digital Content Analysis Technology Ltd
@@ -19,7 +19,7 @@ PACKAGE_DIR = pathlib.Path(__file__).parent
19
19
  # @formatter:off
20
20
  setup(
21
21
  name='fusion-platform-python-sdk',
22
- version='1.11.1',
22
+ version='1.13.1',
23
23
  description='Python SDK used to interact with the Fusion Platform(r)',
24
24
  long_description=(PACKAGE_DIR / 'README.md').read_text(),
25
25
  long_description_content_type='text/markdown',