Skip to content

Commit

Permalink
Merge pull request #270 from volaya/file_extension
Browse files Browse the repository at this point in the history
added support for STAC file extension
  • Loading branch information
lossyrob authored Mar 11, 2021
2 parents 621bd7b + b9c3cda commit 60b3ae6
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 1 deletion.
1 change: 1 addition & 0 deletions .codespellignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
filetest
4 changes: 3 additions & 1 deletion pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class STACError(Exception):
import pystac.extensions.timestamps
import pystac.extensions.version
import pystac.extensions.view
import pystac.extensions.file

STAC_EXTENSIONS = extensions.base.RegisteredSTACExtensions([
extensions.eo.EO_EXTENSION_DEFINITION, extensions.label.LABEL_EXTENSION_DEFINITION,
Expand All @@ -49,7 +50,8 @@ class STACError(Exception):
extensions.sat.SAT_EXTENSION_DEFINITION, extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION,
extensions.single_file_stac.SFS_EXTENSION_DEFINITION,
extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION,
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION,
extensions.file.FILE_EXTENSION_DEFINITION
])


Expand Down
1 change: 1 addition & 0 deletions pystac/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ def __str__(self):
TIMESTAMPS = 'timestamps'
VERSION = 'version'
VIEW = 'view'
FILE = 'file'
227 changes: 227 additions & 0 deletions pystac/extensions/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import enum

from pystac import Extensions
from pystac.item import Item
from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject)


class FileDataType(enum.Enum):
INT8 = "int8"
INT16 = "int16"
INT32 = "int32"
INT64 = "int64"
UINT8 = "uint8"
UINT16 = "uint16"
UINT32 = "uint32"
UINT64 = "uint64"
FLOAT16 = "float16"
FLOAT32 = "float32"
FLOAT64 = "float64"
CINT16 = "cint16"
CINT32 = "cint32"
CFLOAT32 = "cfloat32"
CFLOAT64 = "cfloat64"
OTHER = "other"


class FileItemExt(ItemExtension):
"""FileItemExt is the extension of the Item in the file extension which
adds file related details such as checksum, data type and size for assets.
Args:
item (Item): The item to be extended.
Attributes:
item (Item): The Item that is being extended.
Note:
Using FileItemExt to directly wrap an item will add the 'file' extension ID to
the item's stac_extensions.
"""
def __init__(self, item):
if item.stac_extensions is None:
item.stac_extensions = [Extensions.FILE]
elif Extensions.FILE not in item.stac_extensions:
item.stac_extensions.append(Extensions.FILE)

self.item = item

def apply(self, data_type=None, size=None, nodata=None, checksum=None):
"""Applies file extension properties to the extended Item.
Args:
data_type (FileDataType): The data type of the file.
size (int or None): size of the file in bytes.
nodata (List[Object] or None): Value(s) for no-data.
checksum (str or None): Multihash for the corresponding file,
encoded as hexadecimal (base 16) string with lowercase letters.
"""
self.data_type = data_type
self.size = size
self.nodata = nodata
self.checksum = checksum

def _set_property(self, key, value, asset):
target = self.item.properties if asset is None else asset.properties
if value is None:
target.pop(key, None)
else:
target[key] = value

@property
def data_type(self):
"""Get or sets the data_type of the file.
Returns:
FileDataType
"""
return self.get_data_type()

@data_type.setter
def data_type(self, v):
self.set_data_type(v)

def get_data_type(self, asset=None):
"""Gets an Item or an Asset data_type.
If an Asset is supplied and the data_type property exists on the Asset,
returns the Asset's value. Otherwise returns the Item's value
Returns:
FileDataType
"""
if asset is not None and 'file:data_type' in asset.properties:
data_type = asset.properties.get('file:data_type')
else:
data_type = self.item.properties.get('file:data_type')

if data_type is not None:
return FileDataType(data_type)

def set_data_type(self, data_type, asset=None):
"""Set an Item or an Asset data_type.
If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._set_property('file:data_type', data_type.value, asset)

@property
def size(self):
"""Get or sets the size in bytes of the file
Returns:
int or None
"""
return self.get_size()

@size.setter
def size(self, v):
self.set_size(v)

def get_size(self, asset=None):
"""Gets an Item or an Asset file size.
If an Asset is supplied and the Item property exists on the Asset,
returns the Asset's value. Otherwise returns the Item's value
Returns:
float
"""
if asset is None or 'file:size' not in asset.properties:
return self.item.properties.get('file:size')
else:
return asset.properties.get('file:size')

def set_size(self, size, asset=None):
"""Set an Item or an Asset size.
If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._set_property('file:size', size, asset)

@property
def nodata(self):
"""Get or sets the no data values
Returns:
int or None
"""
return self.get_nodata()

@nodata.setter
def nodata(self, v):
self.set_nodata(v)

def get_nodata(self, asset=None):
"""Gets an Item or an Asset nodata values.
If an Asset is supplied and the Item property exists on the Asset,
returns the Asset's value. Otherwise returns the Item's value
Returns:
list[object]
"""
if asset is None or 'file:nodata' not in asset.properties:
return self.item.properties.get('file:nodata')
else:
return asset.properties.get('file:nodata')

def set_nodata(self, nodata, asset=None):
"""Set an Item or an Asset nodata values.
If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._set_property('file:nodata', nodata, asset)

@property
def checksum(self):
"""Get or sets the checksum
Returns:
str or None
"""
return self.get_checksum()

@checksum.setter
def checksum(self, v):
self.set_checksum(v)

def get_checksum(self, asset=None):
"""Gets an Item or an Asset checksum.
If an Asset is supplied and the Item property exists on the Asset,
returns the Asset's value. Otherwise returns the Item's value
Returns:
list[object]
"""
if asset is None or 'file:checksum' not in asset.properties:
return self.item.properties.get('file:checksum')
else:
return asset.properties.get('file:checksum')

def set_checksum(self, checksum, asset=None):
"""Set an Item or an Asset checksum.
If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._set_property('file:checksum', checksum, asset)

def __repr__(self):
return '<FileItemExt Item id={}>'.format(self.item.id)

@classmethod
def _object_links(cls):
return []

@classmethod
def from_item(cls, item):
return cls(item)


FILE_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.FILE,
[ExtendedObject(Item, FileItemExt)])
100 changes: 100 additions & 0 deletions tests/data-files/file/file-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616",
"type": "Feature",
"stac_version": "1.0.0-beta.2",
"stac_extensions": [
"file"
],
"bbox": [
-70.275032,
-64.72924,
-65.087479,
-51.105831
],
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-67.071648,
-64.72924
],
[
-65.087479,
-56.674374
],
[
-68.033211,
-51.105831
],
[
-70.275032,
-59.805672
],
[
-67.071648,
-64.72924
]
]
]
},
"properties": {
"datetime": "2018-11-03T23:58:55Z"
},
"assets": {
"noises": {
"href": "./annotation/calibration/noise-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
"title": "Calibration Schema",
"type": "text/xml",
"file:checksum": "90e40210a30d1711e81a4b11ef67b28744321659"
},
"calibrations": {
"href": "./annotation/calibration/calibration-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
"title": "Noise Schema",
"type": "text/xml",
"file:checksum": "90e402104fc5351af67db0b8f1746efe421a05e4"
},
"products": {
"href": "./annotation/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
"title": "Product Schema",
"type": "text/xml",
"file:checksum": "90e402107a7f2588a85362b9beea2a12d4514d45"
},
"measurement": {
"href": "./measurement/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.tiff",
"title": "Measurements",
"type": "image/tiff",
"file:byte_order": "little-endian",
"file:data_type": "uint16",
"file:size": 209715200,
"file:header_size": 4096,
"file:checksum": "90e40210163700a8a6501eccd00b6d3b44ddaed0"
},
"thumbnail": {
"href": "./preview/quick-look.png",
"title": "Thumbnail",
"type": "image/png",
"file:byte_order": "big-endian",
"file:data_type": "uint8",
"file:size": 146484,
"file:checksum": "90e40210f52acd32b09769d3b1871b420789456c",
"file:nodata": []
}
},
"links": [
{
"rel": "self",
"href": "https://example.com/collections/sentinel-1/items/S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616"
},
{
"rel": "parent",
"href": "https://example.com/collections/sentinel-1",
"file:checksum": "11146d97123fd2c02dec9a1b6d3b13136dbe600cf966"
},
{
"rel": "root",
"href": "https://example.com/collections",
"file:checksum": "1114fa4b9d69fdddc7c1be7bed9440621400b383b43f"
}
]
}
Loading

0 comments on commit 60b3ae6

Please sign in to comment.