Skip to content

add timestamps extension #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Added support for prerelease versions in version comparisions for the `pystac.serialization.identify` package ([#138](https://github.com/azavea/pystac/pull/138))
- Added validation for PySTAC STACObjects as well as arbitrary STAC JSON ([#139](https://github.com/azavea/pystac/pull/139))
- Added the ability to read HTTP and HTTPS uris by default ([#139](https://github.com/azavea/pystac/pull/139))
- Added support for the timestamps extension([#161](https://github.com/stac-utils/pystac/pull/161))

### Changed

Expand Down
13 changes: 13 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ ProjectionItemExt
:undoc-members:
:show-inheritance:

Timestamps Extension
--------------------

Implements the `Timestamps Extension <https://github.com/radiantearth/stac-spec/tree/v1.0.0-beta.2/extensions/timestamps>`_.

TimestampsItemExt
~~~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.timestamps.TimestampsItemExt
:members:
:undoc-members:
:show-inheritance:

Single File STAC Extension
--------------------------

Expand Down
4 changes: 3 additions & 1 deletion pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ class STACError(Exception):
import pystac.extensions.projection
import pystac.extensions.view
import pystac.extensions.single_file_stac
import pystac.extensions.timestamps

STAC_EXTENSIONS = extensions.base.RegisteredSTACExtensions([
extensions.eo.EO_EXTENSION_DEFINITION, extensions.label.LABEL_EXTENSION_DEFINITION,
extensions.projection.PROJECTION_EXTENSION_DEFINITION,
extensions.view.VIEW_EXTENSION_DEFINITION, extensions.single_file_stac.SFS_EXTENSION_DEFINITION
extensions.view.VIEW_EXTENSION_DEFINITION, extensions.single_file_stac.SFS_EXTENSION_DEFINITION,
extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION
])


Expand Down
193 changes: 193 additions & 0 deletions pystac/extensions/timestamps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import pystac
from pystac import Extensions
from pystac.extensions.base import (ExtendedObject, ExtensionDefinition, ItemExtension)
from pystac.item import Item
from pystac.utils import datetime_to_str, str_to_datetime


class TimestampsItemExt(ItemExtension):
"""TimestampsItemExt is the extension of an Item in that
allows to specify additional timestamps for assets and metadata.

Args:
item (Item): The item to be extended.

Attributes:
item (Item): The Item that is being extended.

Note:
Using TimestampsItemExt to directly wrap an item will add the 'timestamps'
extension ID to the item's stac_extensions.
"""
def __init__(self, item):
if item.stac_extensions is None:
item.stac_extensions = [Extensions.TIMESTAMPS]
elif Extensions.TIMESTAMPS not in item.stac_extensions:
item.stac_extensions.append(Extensions.TIMESTAMPS)

self.item = item

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

@classmethod
def _object_links(cls):
return []

def apply(self, published=None, expires=None, unpublished=None):
"""Applies timestamps extension properties to the extended Item.

Args:
published (datetime or None): Date and time the corresponding data
was published the first time.
expires (datetime or None): Date and time the corresponding data
expires (is not valid any longer).
unpublished (datetime or None): Date and time the corresponding data
was unpublished.
"""
if published is None and expires is None and unpublished is None:
raise pystac.STACError("timestamps extension needs at least one property value.")

self.published = published
self.expires = expires
self.unpublished = unpublished

def _timestamp_getter(self, key, asset=None):
if asset is not None and key in asset.properties:
timestamp_str = asset.properties.get(key)
else:
timestamp_str = self.item.properties.get(key)

timestamp = None
if timestamp_str is not None:
timestamp = str_to_datetime(timestamp_str)

return timestamp

def _timestamp_setter(self, timestamp, key, asset=None):
if timestamp is None:
self.item.properties[key] = timestamp
else:
timestamp_str = datetime_to_str(timestamp)
if asset is not None:
asset.properties[key] = timestamp_str
else:
self.item.properties[key] = timestamp_str

@property
def published(self):
"""Get or sets a datetime objects that represent
the date and time that the corresponding data
was published the first time.

Returns:
datetime
"""
return self.get_published()

@published.setter
def published(self, v):
self.set_published(v)

def get_published(self, asset=None):
"""Get an Item or Asset published datetime

If an Asset is supplied and the published property exists on the Asset,
return the Asset's value. Otherwise return the Item's value. 'Published'
has a different meaning depending on where it is used. If available in
the asset properties, it refers to the timestamps valid for the actual data linked
to the Asset Object. If it comes from the Item properties, it's referencing to
the timestamp valid for the metadata.

Returns:
datetime
"""
return self._timestamp_getter('published', asset)

def set_published(self, published, asset=None):
"""Set an Item or asset published datetime

If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._timestamp_setter(published, 'published', asset)

@property
def expires(self):
"""Get or sets a datetime objects that represent
the date and time the corresponding data
expires (is not valid any longer).

Returns:
datetime
"""
return self.get_expires()

@expires.setter
def expires(self, v):
self.set_expires(v)

def get_expires(self, asset=None):
"""Get an Item or Asset expires datetime

If an Asset is supplied and the expires property exists on the Asset,
return the Asset's value. Otherwise return the Item's value. 'Unpublished'
has a different meaning depending on where it is used. If available in
the asset properties, it refers to the timestamps valid for the actual data linked
to the Asset Object. If it comes from the Item properties, it's referencing to
the timestamp valid for the metadata.

Returns:
datetime
"""
return self._timestamp_getter('expires', asset)

def set_expires(self, expires, asset=None):
"""Set an Item or asset expires datetime

If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._timestamp_setter(expires, 'expires', asset)

@property
def unpublished(self):
"""Get or sets a datetime objects that represent
the Date and time the corresponding data was unpublished.

Returns:
datetime
"""
return self.get_unpublished()

@unpublished.setter
def unpublished(self, v):
self.set_unpublished(v)

def get_unpublished(self, asset=None):
"""Get an Item or Asset unpublished datetime

If an Asset is supplied and the unpublished property exists on the Asset,
return the Asset's value. Otherwise return the Item's value. 'Unpublished'
has a different meaning depending on where it is used. If available in
the asset properties, it refers to the timestamps valid for the actual data linked
to the Asset Object. If it comes from the Item properties, it's referencing to
the timestamp valid for the metadata.

Returns:
datetime
"""
return self._timestamp_getter('unpublished', asset)

def set_unpublished(self, unpublished, asset=None):
"""Set an Item or asset unpublished datetime

If an Asset is supplied, sets the property on the Asset.
Otherwise sets the Item's value.
"""
self._timestamp_setter(unpublished, 'unpublished', asset)


TIMESTAMPS_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.TIMESTAMPS,
[ExtendedObject(Item, TimestampsItemExt)])
91 changes: 91 additions & 0 deletions tests/data-files/timestamps/example-landsat8.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"stac_version": "1.0.0-beta.2",
"stac_extensions": [
"timestamps"
],
"id": "LC08_L1TP_107018_20181001",
"type": "Feature",
"bbox": [
148.13933,
59.51584,
152.52758,
60.63437
],
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
152.52758,
60.63437
],
[
149.1755,
61.19016
],
[
148.13933,
59.51584
],
[
151.33786,
58.97792
],
[
152.52758,
60.63437
]
]
]
},
"properties": {
"datetime": "2018-10-01T01:08:32Z",
"created": "2018-10-02T00:00:03Z",
"updated": "2020-05-20T12:13:02Z",
"published": "2018-10-03T06:45:55Z",
"expires": "2025-01-01T00:00:00Z",
"platform": "landsat-8",
"instruments": ["oli", "tirs"],
"constellation": "landsat"
},
"assets": {
"blue": {
"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_B2.TIF",
"type": "image/tiff; application=geotiff",
"title": "Band 2 (blue)",
"created": "2018-10-02T00:00:00Z",
"published": "2018-11-02T00:00:00Z",
"expires": "2018-12-02T00:00:00Z",
"unpublished": "2019-01-02T00:00:00Z"
},
"green": {
"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_B3.TIF",
"type": "image/tiff; application=geotiff",
"title": "Band 3 (green)",
"created": "2018-10-02T00:00:00Z"
},
"red": {
"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_B4.TIF",
"type": "image/tiff; application=geotiff",
"title": "Band 4 (red)",
"created": "2018-10-02T00:00:00Z"
},
"thumbnail": {
"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_thumb_large.jpg",
"title": "Thumbnail image",
"type": "image/jpeg",
"created": "2018-10-02T00:00:01Z"
},
"index": {
"href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html",
"type": "text/html",
"title": "HTML index page",
"created": "2018-10-02T00:00:02Z",
"updated": "2020-05-20T12:13:01Z"
}
},
"links": [{
"rel": "self",
"href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT"
}]
}
Loading