Skip to content

Commit 793290f

Browse files
committed
added support for STAC file extension
1 parent ad06c61 commit 793290f

File tree

5 files changed

+417
-1
lines changed

5 files changed

+417
-1
lines changed

pystac/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class STACError(Exception):
4141
import pystac.extensions.timestamps
4242
import pystac.extensions.version
4343
import pystac.extensions.view
44+
import pystac.extensions.file
4445

4546
STAC_EXTENSIONS = extensions.base.RegisteredSTACExtensions([
4647
extensions.eo.EO_EXTENSION_DEFINITION, extensions.label.LABEL_EXTENSION_DEFINITION,
@@ -49,7 +50,8 @@ class STACError(Exception):
4950
extensions.sat.SAT_EXTENSION_DEFINITION, extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION,
5051
extensions.single_file_stac.SFS_EXTENSION_DEFINITION,
5152
extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION,
52-
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION
53+
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION,
54+
extensions.file.FILE_EXTENSION_DEFINITION
5355
])
5456

5557

pystac/extensions/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ def __str__(self):
2929
TIMESTAMPS = 'timestamps'
3030
VERSION = 'version'
3131
VIEW = 'view'
32+
FILE = 'file'

pystac/extensions/file.py

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import enum
2+
3+
from pystac import Extensions
4+
from pystac.item import Item
5+
from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject)
6+
7+
8+
class FileDataType(enum.Enum):
9+
INT8 = "int8"
10+
INT16 = "int16"
11+
INT32 = "int32"
12+
INT64 = "int64"
13+
UINT8 = "uint8"
14+
UINT16 = "uint16"
15+
UINT32 = "uint32"
16+
UINT64 = "uint64"
17+
FLOAT16 = "float16"
18+
FLOAT32 = "float32"
19+
FLOAT64 = "float64"
20+
CINT16 = "cint16"
21+
CINT32 = "cint32"
22+
CFLOAT32 = "cfloat32"
23+
CFLOAT64 = "cfloat64"
24+
OTHER = "other"
25+
26+
27+
class FileItemExt(ItemExtension):
28+
"""FileItemExt is the extension of the Item in the file extension which
29+
adds file related details such as checksum, data type and size for assets.
30+
31+
Args:
32+
item (Item): The item to be extended.
33+
34+
Attributes:
35+
item (Item): The Item that is being extended.
36+
37+
Note:
38+
Using FileItemExt to directly wrap an item will add the 'file' extension ID to
39+
the item's stac_extensions.
40+
"""
41+
def __init__(self, item):
42+
if item.stac_extensions is None:
43+
item.stac_extensions = [Extensions.FILE]
44+
elif Extensions.FILE not in item.stac_extensions:
45+
item.stac_extensions.append(Extensions.FILE)
46+
47+
self.item = item
48+
49+
def apply(self,
50+
data_type=None,
51+
size=None,
52+
nodata=None,
53+
checksum=None):
54+
"""Applies file extension properties to the extended Item.
55+
56+
Args:
57+
data_type (FileDataType): The data type of the file.
58+
size (int or None): size of the file in bytes.
59+
nodata (List[Object] or None): Value(s) for no-data.
60+
checksum (str or None): Multihash for the corresponding file,
61+
encoded as hexadecimal (base 16) string with lowercase letters.
62+
"""
63+
self.data_type = data_type
64+
self.size = size
65+
self.nodata = nodata
66+
self.checksum = checksum
67+
68+
@property
69+
def data_type(self):
70+
"""Get or sets the data_type of the file.
71+
72+
Returns:
73+
FileDataType
74+
"""
75+
return self.get_data_type()
76+
77+
@data_type.setter
78+
def data_type(self, v):
79+
self.set_data_type(v)
80+
81+
def get_data_type(self, asset=None):
82+
"""Gets an Item or an Asset data_type.
83+
84+
If an Asset is supplied and the data_type property exists on the Asset,
85+
returns the Asset's value. Otherwise returns the Item's value
86+
87+
Returns:
88+
FileDataType
89+
"""
90+
if asset is not None and 'file:data_type' in asset.properties:
91+
data_type = asset.properties.get('file:data_type')
92+
else:
93+
data_type = self.item.properties.get('file_data_type')
94+
95+
if data_type is not None:
96+
return FileDataType(data_type)
97+
98+
def set_data_type(self, data_type, asset=None):
99+
"""Set an Item or an Asset data_type.
100+
101+
If an Asset is supplied, sets the property on the Asset.
102+
Otherwise sets the Item's value.
103+
"""
104+
if asset is not None:
105+
asset.properties['file:data_type'] = data_type.value
106+
else:
107+
self.item.properties['file:data_type'] = data_type.value
108+
109+
@property
110+
def size(self):
111+
"""Get or sets the size in bytes of the file
112+
113+
Returns:
114+
int or None
115+
"""
116+
return self.get_size()
117+
118+
@size.setter
119+
def size(self, v):
120+
self.set_size(v)
121+
122+
def get_size(self, asset=None):
123+
"""Gets an Item or an Asset file size.
124+
125+
If an Asset is supplied and the Item property exists on the Asset,
126+
returns the Asset's value. Otherwise returns the Item's value
127+
128+
Returns:
129+
float
130+
"""
131+
if asset is None or 'file:size' not in asset.properties:
132+
return self.item.properties.get('file:size')
133+
else:
134+
return asset.properties.get('file:size')
135+
136+
def set_size(self, size, asset=None):
137+
"""Set an Item or an Asset size.
138+
139+
If an Asset is supplied, sets the property on the Asset.
140+
Otherwise sets the Item's value.
141+
"""
142+
if asset is None:
143+
self.item.properties['file:size'] = size
144+
else:
145+
asset.properties['file:size'] = size
146+
147+
@property
148+
def nodata(self):
149+
"""Get or sets the no data values
150+
151+
Returns:
152+
int or None
153+
"""
154+
return self.get_nodata()
155+
156+
@nodata.setter
157+
def nodata(self, v):
158+
self.set_nodata(v)
159+
160+
def get_nodata(self, asset=None):
161+
"""Gets an Item or an Asset nodata values.
162+
163+
If an Asset is supplied and the Item property exists on the Asset,
164+
returns the Asset's value. Otherwise returns the Item's value
165+
166+
Returns:
167+
list[object]
168+
"""
169+
if asset is None or 'file:nodata' not in asset.properties:
170+
return self.item.properties.get('file:nodata')
171+
else:
172+
return asset.properties.get('file:nodata')
173+
174+
def set_nodata(self, nodata, asset=None):
175+
"""Set an Item or an Asset nodata values.
176+
177+
If an Asset is supplied, sets the property on the Asset.
178+
Otherwise sets the Item's value.
179+
"""
180+
if asset is None:
181+
self.item.properties['file:nodata'] = nodata
182+
else:
183+
asset.properties['file:nodata'] = nodata
184+
185+
@property
186+
def checksum(self):
187+
"""Get or sets the checksum
188+
189+
Returns:
190+
str or None
191+
"""
192+
return self.get_checksum()
193+
194+
@nodata.setter
195+
def checksum(self, v):
196+
self.set_checksum(v)
197+
198+
def get_checksum(self, asset=None):
199+
"""Gets an Item or an Asset checksum.
200+
201+
If an Asset is supplied and the Item property exists on the Asset,
202+
returns the Asset's value. Otherwise returns the Item's value
203+
204+
Returns:
205+
list[object]
206+
"""
207+
if asset is None or 'file:checksum' not in asset.properties:
208+
return self.item.properties.get('file:checksum')
209+
else:
210+
return asset.properties.get('file:checksum')
211+
212+
def set_checksum(self, checksum, asset=None):
213+
"""Set an Item or an Asset checksum.
214+
215+
If an Asset is supplied, sets the property on the Asset.
216+
Otherwise sets the Item's value.
217+
"""
218+
if asset is None:
219+
self.item.properties['file:checksum'] = checksum
220+
else:
221+
asset.properties['file:checksum'] = checksum
222+
223+
def __repr__(self):
224+
return '<FileItemExt Item id={}>'.format(self.item.id)
225+
226+
@classmethod
227+
def _object_links(cls):
228+
return []
229+
230+
@classmethod
231+
def from_item(cls, item):
232+
return cls(item)
233+
234+
235+
FILE_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.FILE, [ExtendedObject(Item, FileItemExt)])
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616",
3+
"type": "Feature",
4+
"stac_version": "1.0.0-beta.2",
5+
"stac_extensions": [
6+
"file"
7+
],
8+
"bbox": [
9+
-70.275032,
10+
-64.72924,
11+
-65.087479,
12+
-51.105831
13+
],
14+
"geometry": {
15+
"type": "Polygon",
16+
"coordinates": [
17+
[
18+
[
19+
-67.071648,
20+
-64.72924
21+
],
22+
[
23+
-65.087479,
24+
-56.674374
25+
],
26+
[
27+
-68.033211,
28+
-51.105831
29+
],
30+
[
31+
-70.275032,
32+
-59.805672
33+
],
34+
[
35+
-67.071648,
36+
-64.72924
37+
]
38+
]
39+
]
40+
},
41+
"properties": {
42+
"datetime": "2018-11-03T23:58:55Z"
43+
},
44+
"assets": {
45+
"noises": {
46+
"href": "./annotation/calibration/noise-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
47+
"title": "Calibration Schema",
48+
"type": "text/xml",
49+
"file:checksum": "90e40210a30d1711e81a4b11ef67b28744321659"
50+
},
51+
"calibrations": {
52+
"href": "./annotation/calibration/calibration-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
53+
"title": "Noise Schema",
54+
"type": "text/xml",
55+
"file:checksum": "90e402104fc5351af67db0b8f1746efe421a05e4"
56+
},
57+
"products": {
58+
"href": "./annotation/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml",
59+
"title": "Product Schema",
60+
"type": "text/xml",
61+
"file:checksum": "90e402107a7f2588a85362b9beea2a12d4514d45"
62+
},
63+
"measurement": {
64+
"href": "./measurement/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.tiff",
65+
"title": "Measurements",
66+
"type": "image/tiff",
67+
"file:byte_order": "little-endian",
68+
"file:data_type": "uint16",
69+
"file:size": 209715200,
70+
"file:header_size": 4096,
71+
"file:checksum": "90e40210163700a8a6501eccd00b6d3b44ddaed0"
72+
},
73+
"thumbnail": {
74+
"href": "./preview/quick-look.png",
75+
"title": "Thumbnail",
76+
"type": "image/png",
77+
"file:byte_order": "big-endian",
78+
"file:data_type": "uint8",
79+
"file:size": 146484,
80+
"file:checksum": "90e40210f52acd32b09769d3b1871b420789456c",
81+
"file:nodata": []
82+
}
83+
},
84+
"links": [
85+
{
86+
"rel": "self",
87+
"href": "https://example.com/collections/sentinel-1/items/S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616"
88+
},
89+
{
90+
"rel": "parent",
91+
"href": "https://example.com/collections/sentinel-1",
92+
"file:checksum": "11146d97123fd2c02dec9a1b6d3b13136dbe600cf966"
93+
},
94+
{
95+
"rel": "root",
96+
"href": "https://example.com/collections",
97+
"file:checksum": "1114fa4b9d69fdddc7c1be7bed9440621400b383b43f"
98+
}
99+
]
100+
}

0 commit comments

Comments
 (0)