Skip to content

Commit d9686c8

Browse files
committed
Merge remote-tracking branch 'origin/develop' into rde/fix/stac_io_docs
2 parents d810c32 + 6c8d64f commit d9686c8

File tree

9 files changed

+1077
-18
lines changed

9 files changed

+1077
-18
lines changed

pystac/eo.py

Lines changed: 260 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,260 @@
1-
# TODO
1+
from copy import deepcopy
2+
3+
from pystac.item import (Item, Asset)
4+
from pystac import STACError
5+
6+
7+
class EOItem(Item):
8+
EO_FIELDS = ['gsd', 'platform', 'instrument', 'bands', 'constellation', 'epsg',
9+
'cloud_cover', 'off_nadir', 'azimuth', 'sun_azimuth', 'sun_elevation']
10+
11+
def __init__(self,
12+
id,
13+
geometry,
14+
bbox,
15+
datetime,
16+
properties,
17+
gsd,
18+
platform,
19+
instrument,
20+
bands,
21+
constellation=None,
22+
epsg=None,
23+
cloud_cover=None,
24+
off_nadir=None,
25+
azimuth=None,
26+
sun_azimuth=None,
27+
sun_elevation=None,
28+
stac_extensions=None,
29+
href=None,
30+
collection=None,
31+
assets={}):
32+
if stac_extensions is None:
33+
stac_extensions = []
34+
if 'eo' not in stac_extensions:
35+
stac_extensions.append('eo')
36+
super().__init__(id, geometry, bbox, datetime,
37+
properties, stac_extensions, href,
38+
collection)
39+
self.gsd = gsd
40+
self.platform = platform
41+
self.instrument = instrument
42+
self.bands = [Band.from_dict(b) for b in bands]
43+
self.constellation = constellation
44+
self.epsg = epsg
45+
self.cloud_cover = cloud_cover
46+
self.off_nadir = off_nadir
47+
self.azimuth = azimuth
48+
self.sun_azimuth = sun_azimuth
49+
self.sun_elevation = sun_elevation
50+
51+
52+
def __repr__(self):
53+
return '<EOItem id={}>'.format(self.id)
54+
55+
@staticmethod
56+
def from_dict(d):
57+
item = Item.from_dict(d)
58+
return EOItem.from_item(item)
59+
60+
@classmethod
61+
def from_item(cls, item):
62+
eo_params = {}
63+
for eof in EOItem.EO_FIELDS:
64+
if eo_key(eof) in item.properties.keys():
65+
eo_params[eof] = item.properties.pop(eo_key(eof))
66+
elif eof in ('gsd', 'platform', 'instrument', 'bands'):
67+
raise STACError(
68+
"Missing required field '{}' in properties".format(eo_key(eof)))
69+
70+
if not any(item.properties):
71+
item.properties = None
72+
73+
e = cls(item.id, item.geometry, item.bbox, item.datetime,
74+
item.properties, stac_extensions=item.stac_extensions,
75+
collection=item.collection, **eo_params)
76+
77+
e.links = item.links
78+
e.assets = item.assets
79+
80+
for k, v in item.assets.items():
81+
if is_eo(v):
82+
e.assets[k] = EOAsset.from_asset(v)
83+
e.assets[k].set_owner(e)
84+
85+
return e
86+
87+
def get_eo_assets(self):
88+
return {k: v for k, v in self.assets.items() if isinstance(v, EOAsset)}
89+
90+
def add_asset(self, key, asset):
91+
if is_eo(asset) and not isinstance(asset, EOAsset):
92+
asset = EOAsset.from_asset(asset)
93+
asset.set_owner(self)
94+
self.assets[key] = asset
95+
return self
96+
97+
@staticmethod
98+
def from_file(uri):
99+
return EOItem.from_item(Item.from_file(uri))
100+
101+
def clone(self):
102+
c = super(EOItem, self).clone()
103+
self.add_eo_fields_to_dict(c.properties)
104+
return EOItem.from_item(c)
105+
106+
def to_dict(self, include_self_link=True):
107+
d = super().to_dict(include_self_link=include_self_link)
108+
if 'properties' not in d.keys():
109+
d['properties'] = {}
110+
self.add_eo_fields_to_dict(d['properties'])
111+
return deepcopy(d)
112+
113+
def add_eo_fields_to_dict(self, d):
114+
for eof in EOItem.EO_FIELDS:
115+
try:
116+
a = getattr(self, eof)
117+
if a is not None:
118+
d[eo_key(eof)] = a
119+
if eof == 'bands':
120+
d['eo:bands'] = [b.to_dict() for b in d['eo:bands']]
121+
except AttributeError:
122+
pass
123+
124+
125+
class EOAsset(Asset):
126+
def __init__(self, href, bands, title=None, media_type=None, properties=None):
127+
super().__init__(href, title, media_type, properties)
128+
self.bands = bands
129+
130+
@staticmethod
131+
def from_dict(d):
132+
asset = Asset.from_dict(d)
133+
return EOAsset.from_asset(asset)
134+
135+
@classmethod
136+
def from_asset(cls, asset):
137+
a = asset.clone()
138+
if not a.properties or 'eo:bands' not in a.properties.keys():
139+
raise STACError('Missing eo:bands property in asset')
140+
bands = a.properties.pop('eo:bands')
141+
properties = None
142+
if any(a.properties):
143+
properties = a.properties
144+
return cls(a.href, bands, a.title, a.media_type, properties)
145+
146+
def to_dict(self):
147+
d = super().to_dict()
148+
d['eo:bands'] = self.bands
149+
150+
return d
151+
152+
def clone(self):
153+
return EOAsset(href=self.href,
154+
title=self.title,
155+
media_type=self.media_type,
156+
bands=self.bands,
157+
properties=self.properties)
158+
159+
def __repr__(self):
160+
return '<EOAsset href={}>'.format(self.href)
161+
162+
def get_band_objs(self):
163+
# Not sure exactly how this method fits in but
164+
# it seems like there should be a way to get the
165+
# Band objects associated with the indices
166+
if not self.item:
167+
raise STACError('Asset is currently not associated with an item')
168+
return [self.item.bands[i] for i in self.bands]
169+
170+
171+
class Band:
172+
def __init__(self,
173+
name=None,
174+
common_name=None,
175+
gsd=None,
176+
center_wavelength=None,
177+
full_width_half_max=None,
178+
description=None,
179+
accuracy=None):
180+
self.name = name
181+
self.common_name = common_name
182+
self.gsd = gsd
183+
self.center_wavelength = center_wavelength
184+
self.full_width_half_max = full_width_half_max
185+
self.description = description
186+
self.accuracy = accuracy
187+
188+
def __repr__(self):
189+
return '<Band name={}>'.format(self.name)
190+
191+
@staticmethod
192+
def from_dict(d):
193+
name = d.get('name', None)
194+
common_name = d.get('common_name', None)
195+
gsd = d.get('gsd', None)
196+
center_wavelength = d.get('center_wavelength', None)
197+
full_width_half_max = d.get('full_width_half_max', None)
198+
description = d.get('description', None)
199+
accuracy = d.get('accuracy', None)
200+
201+
return Band(name, common_name, gsd, center_wavelength,
202+
full_width_half_max, description, accuracy)
203+
204+
def to_dict(self):
205+
d = {}
206+
if self.name:
207+
d['name'] = self.name
208+
if self.common_name:
209+
d['common_name'] = self.common_name
210+
if self.gsd:
211+
d['gsd'] = self.gsd
212+
if self.center_wavelength:
213+
d['center_wavelength'] = self.center_wavelength
214+
if self.full_width_half_max:
215+
d['full_width_half_max'] = self.full_width_half_max
216+
if self.description:
217+
d['description'] = self.description
218+
if self.accuracy:
219+
d['accuracy'] = self.accuracy
220+
return deepcopy(d)
221+
222+
223+
def eo_key(key):
224+
return 'eo:{}'.format(key)
225+
226+
227+
def band_range(common_name):
228+
name_to_range = {
229+
'coastal': (0.40, 0.45),
230+
'blue': (0.45, 0.50),
231+
'green': (0.50, 0.60),
232+
'red': (0.60, 0.70),
233+
'yellow': (0.58, 0.62),
234+
'pan': (0.50, 0.70),
235+
'rededge': (0.70, 0.75),
236+
'nir': (0.75, 1.00),
237+
'nir08': (0.75, 0.90),
238+
'nir09': (0.85, 1.05),
239+
'cirrus': (1.35, 1.40),
240+
'swir16': (1.55, 1.75),
241+
'swir22': (2.10, 2.30),
242+
'lwir': (10.5, 12.5),
243+
'lwir11': (10.5, 11.5),
244+
'lwir12': (11.5, 12.5)
245+
}
246+
return name_to_range.get(common_name, common_name)
247+
248+
249+
def band_desc(common_name):
250+
r = band_range(common_name)
251+
if isinstance(r, str):
252+
return "Common name: {}".format(common_name)
253+
return "Common name: {}, Range: {} to {}".format(common_name, r[0], r[1])
254+
255+
256+
def is_eo(obj):
257+
if obj.properties:
258+
if obj.properties.get('eo:bands', None):
259+
return True
260+
return False

pystac/item.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import json
12
import os
3+
from copy import copy, deepcopy
24

3-
from copy import (copy, deepcopy)
45
import dateutil.parser
5-
import json
66

7-
from pystac import STAC_VERSION
8-
from pystac.stac_object import STACObject
7+
from pystac import (STAC_VERSION, STACError)
98
from pystac.io import STAC_IO
10-
from pystac.link import (Link, LinkType)
11-
from pystac.utils import (make_relative_href, make_absolute_href, is_absolute_href)
9+
from pystac.link import Link, LinkType
10+
from pystac.stac_object import STACObject
11+
from pystac.utils import (is_absolute_href, make_absolute_href,
12+
make_relative_href)
13+
1214

1315
class Item(STACObject):
1416
def __init__(self,
@@ -72,9 +74,11 @@ def to_dict(self, include_self_link=True):
7274
if not include_self_link:
7375
links = filter(lambda x: x.rel != 'self', links)
7476

75-
assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items()))
77+
assets = dict(
78+
map(lambda x: (x[0], x[1].to_dict()), self.assets.items()))
7679

77-
self.properties['datetime'] = '{}Z'.format(self.datetime.replace(microsecond=0, tzinfo=None))
80+
self.properties['datetime'] = '{}Z'.format(
81+
self.datetime.replace(microsecond=0, tzinfo=None))
7882

7983
d = {
8084
'type': 'Feature',
@@ -89,7 +93,7 @@ def to_dict(self, include_self_link=True):
8993

9094
if self.stac_extensions is not None:
9195
d['stac_extensions'] = self.stac_extensions
92-
96+
9397
if self.collection:
9498
d['collection'] = self.collection
9599

@@ -127,7 +131,8 @@ def normalize_hrefs(self, root_href):
127131
asset.href = new_relative_href
128132

129133
def save(self, include_self_link=True):
130-
STAC_IO.save_json(self.get_self_href(), self.to_dict(include_self_link))
134+
STAC_IO.save_json(self.get_self_href(),
135+
self.to_dict(include_self_link))
131136

132137
@staticmethod
133138
def from_dict(d):
@@ -142,7 +147,8 @@ def from_dict(d):
142147

143148
datetime = properties.get('datetime')
144149
if datetime is None:
145-
raise STACError('Item dict is missing a "datetime" property in the "properties" field')
150+
raise STACError(
151+
'Item dict is missing a "datetime" property in the "properties" field')
146152
datetime = dateutil.parser.parse(datetime)
147153

148154
item = Item(id=id,
@@ -168,6 +174,7 @@ def from_file(uri):
168174
d = json.loads(STAC_IO.read_text(uri))
169175
return Item.from_dict(d)
170176

177+
171178
class Asset:
172179
class MEDIA_TYPE:
173180
TIFF = 'image/tiff'
@@ -181,7 +188,7 @@ class MEDIA_TYPE:
181188
TEXT = 'text/plain'
182189
GEOJSON = 'application/geo+json'
183190
GEOPACKAGE = 'application/geopackage+sqlite3'
184-
HDF5 = 'application/x-hdf5' # Hierarchical Data Format version 5
191+
HDF5 = 'application/x-hdf5' # Hierarchical Data Format version 5
185192
HDF = 'application/x-hdf' # Hierarchical Data Format versions 4 and earlier.
186193

187194
def __init__(self, href, title=None, media_type=None, properties=None):
@@ -228,7 +235,8 @@ def to_dict(self):
228235
def clone(self):
229236
return Asset(href=self.href,
230237
title=self.title,
231-
media_type=self.media_type)
238+
media_type=self.media_type,
239+
properties=self.properties)
232240

233241
def __repr__(self):
234242
return '<Asset href={}>'.format(self.href)
@@ -246,4 +254,4 @@ def from_dict(d):
246254
return Asset(href=href,
247255
media_type=media_type,
248256
title=title,
249-
properties=properties)
257+
properties=properties)

tests/data-files/eo/asset.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt",
3+
"title": "Angle coefficients file",
4+
"type": "text/plain"
5+
}

tests/data-files/eo/eo-asset-2.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF",
3+
"type": "image/tiff; application=geotiff",
4+
"eo:bands": [
5+
0
6+
],
7+
"title": "Band 1 (coastal)",
8+
"extra property": "extra"
9+
}

tests/data-files/eo/eo-asset.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF",
3+
"type": "image/tiff; application=geotiff",
4+
"eo:bands": [
5+
0
6+
],
7+
"title": "Band 1 (coastal)"
8+
}

0 commit comments

Comments
 (0)