Skip to content

Commit 60b3ae6

Browse files
authored
Merge pull request #270 from volaya/file_extension
added support for STAC file extension
2 parents 621bd7b + b9c3cda commit 60b3ae6

File tree

6 files changed

+408
-1
lines changed

6 files changed

+408
-1
lines changed

.codespellignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
filetest

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: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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, data_type=None, size=None, nodata=None, checksum=None):
50+
"""Applies file extension properties to the extended Item.
51+
52+
Args:
53+
data_type (FileDataType): The data type of the file.
54+
size (int or None): size of the file in bytes.
55+
nodata (List[Object] or None): Value(s) for no-data.
56+
checksum (str or None): Multihash for the corresponding file,
57+
encoded as hexadecimal (base 16) string with lowercase letters.
58+
"""
59+
self.data_type = data_type
60+
self.size = size
61+
self.nodata = nodata
62+
self.checksum = checksum
63+
64+
def _set_property(self, key, value, asset):
65+
target = self.item.properties if asset is None else asset.properties
66+
if value is None:
67+
target.pop(key, None)
68+
else:
69+
target[key] = value
70+
71+
@property
72+
def data_type(self):
73+
"""Get or sets the data_type of the file.
74+
75+
Returns:
76+
FileDataType
77+
"""
78+
return self.get_data_type()
79+
80+
@data_type.setter
81+
def data_type(self, v):
82+
self.set_data_type(v)
83+
84+
def get_data_type(self, asset=None):
85+
"""Gets an Item or an Asset data_type.
86+
87+
If an Asset is supplied and the data_type property exists on the Asset,
88+
returns the Asset's value. Otherwise returns the Item's value
89+
90+
Returns:
91+
FileDataType
92+
"""
93+
if asset is not None and 'file:data_type' in asset.properties:
94+
data_type = asset.properties.get('file:data_type')
95+
else:
96+
data_type = self.item.properties.get('file:data_type')
97+
98+
if data_type is not None:
99+
return FileDataType(data_type)
100+
101+
def set_data_type(self, data_type, asset=None):
102+
"""Set an Item or an Asset data_type.
103+
104+
If an Asset is supplied, sets the property on the Asset.
105+
Otherwise sets the Item's value.
106+
"""
107+
self._set_property('file:data_type', data_type.value, asset)
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+
self._set_property('file:size', size, asset)
143+
144+
@property
145+
def nodata(self):
146+
"""Get or sets the no data values
147+
148+
Returns:
149+
int or None
150+
"""
151+
return self.get_nodata()
152+
153+
@nodata.setter
154+
def nodata(self, v):
155+
self.set_nodata(v)
156+
157+
def get_nodata(self, asset=None):
158+
"""Gets an Item or an Asset nodata values.
159+
160+
If an Asset is supplied and the Item property exists on the Asset,
161+
returns the Asset's value. Otherwise returns the Item's value
162+
163+
Returns:
164+
list[object]
165+
"""
166+
if asset is None or 'file:nodata' not in asset.properties:
167+
return self.item.properties.get('file:nodata')
168+
else:
169+
return asset.properties.get('file:nodata')
170+
171+
def set_nodata(self, nodata, asset=None):
172+
"""Set an Item or an Asset nodata values.
173+
174+
If an Asset is supplied, sets the property on the Asset.
175+
Otherwise sets the Item's value.
176+
"""
177+
self._set_property('file:nodata', nodata, asset)
178+
179+
@property
180+
def checksum(self):
181+
"""Get or sets the checksum
182+
183+
Returns:
184+
str or None
185+
"""
186+
return self.get_checksum()
187+
188+
@checksum.setter
189+
def checksum(self, v):
190+
self.set_checksum(v)
191+
192+
def get_checksum(self, asset=None):
193+
"""Gets an Item or an Asset checksum.
194+
195+
If an Asset is supplied and the Item property exists on the Asset,
196+
returns the Asset's value. Otherwise returns the Item's value
197+
198+
Returns:
199+
list[object]
200+
"""
201+
if asset is None or 'file:checksum' not in asset.properties:
202+
return self.item.properties.get('file:checksum')
203+
else:
204+
return asset.properties.get('file:checksum')
205+
206+
def set_checksum(self, checksum, asset=None):
207+
"""Set an Item or an Asset checksum.
208+
209+
If an Asset is supplied, sets the property on the Asset.
210+
Otherwise sets the Item's value.
211+
"""
212+
self._set_property('file:checksum', checksum, asset)
213+
214+
def __repr__(self):
215+
return '<FileItemExt Item id={}>'.format(self.item.id)
216+
217+
@classmethod
218+
def _object_links(cls):
219+
return []
220+
221+
@classmethod
222+
def from_item(cls, item):
223+
return cls(item)
224+
225+
226+
FILE_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.FILE,
227+
[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)