Skip to content
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

Update Satellite Extension #509

Merged
merged 6 commits into from
Jul 7, 2021
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Timestamps Extension summaries ([#513](https://github.com/stac-utils/pystac/pull/513))
- Define equality and `__repr__` of `RangeSummary` instances based on `to_dict`
representation ([#513](https://github.com/stac-utils/pystac/pull/513))
- Sat Extension summaries ([#509](https://github.com/stac-utils/pystac/pull/509))

### Changed

Expand All @@ -24,6 +25,8 @@
- `Link` constructor classes (e.g. `Link.from_dict`, `Link.canonical`, etc.) now return
the calling class instead of always returning the `Link` class
([#512](https://github.com/stac-utils/pystac/pull/512))
- Sat extension now includes all fields defined in v1.0.0
([#509](https://github.com/stac-utils/pystac/pull/509))

### Removed

Expand Down
39 changes: 39 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,45 @@ RasterExtension
:show-inheritance:
:inherited-members:

Satellite Extension
-------------------

OrbitState
~~~~~~~~~~

.. autoclass:: pystac.extensions.sat.OrbitState
:members:
:show-inheritance:
:undoc-members:

SatExtension
~~~~~~~~~~~~

.. autoclass:: pystac.extensions.sat.SatExtension
:members:
:show-inheritance:

ItemSatExtension
~~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.sat.ItemSatExtension
:members:
:show-inheritance:

AssetSatExtension
~~~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.sat.AssetSatExtension
:members:
:show-inheritance:

SummariesSatExtension
~~~~~~~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.sat.SummariesSatExtension
:members:
:show-inheritance:

Scientific Extension
--------------------

Expand Down
213 changes: 185 additions & 28 deletions pystac/extensions/sat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,34 @@
"""

import enum
from typing import Generic, Optional, Set, TypeVar, cast
from datetime import datetime as Datetime
from pystac.summaries import RangeSummary
from typing import Dict, Any, List, Generic, Iterable, Optional, Set, TypeVar, cast

import pystac
from pystac.extensions.base import (
ExtensionManagementMixin,
PropertiesExtension,
SummariesExtension,
)
from pystac.extensions.hooks import ExtensionHooks
from pystac.utils import map_opt
from pystac.utils import str_to_datetime, datetime_to_str, map_opt

T = TypeVar("T", pystac.Item, pystac.Asset)

SCHEMA_URI = "https://stac-extensions.github.io/sat/v1.0.0/schema.json"

ORBIT_STATE: str = "sat:orbit_state"
RELATIVE_ORBIT: str = "sat:relative_orbit"
PREFIX: str = "sat:"
PLATFORM_INTERNATIONAL_DESIGNATOR_PROP: str = (
PREFIX + "platform_international_designator"
)
ABSOLUTE_ORBIT_PROP: str = PREFIX + "absolute_orbit"
ORBIT_STATE_PROP: str = PREFIX + "orbit_state"
RELATIVE_ORBIT_PROP: str = PREFIX + "relative_orbit"
ANX_DATETIME_PROP: str = PREFIX + "anx_datetime"


class OrbitState(enum.Enum):
class OrbitState(str, enum.Enum):
ASCENDING = "ascending"
DESCENDING = "descending"
GEOSTATIONARY = "geostationary"
Expand All @@ -31,25 +40,31 @@ class OrbitState(enum.Enum):
class SatExtension(
Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item]
):
"""SatItemExt extends Item to add sat properties to a STAC Item.
"""An abstract class that can be used to extend the properties of an
:class:`~pystac.Item` or :class:`~pystac.Asset` with properties from the
:stac-ext:`Satellite Extension <sat>`. This class is generic over the type of
STAC Object to be extended (e.g. :class:`~pystac.Item`,
:class:`~pystac.Collection`).

Args:
item : The item to be extended.
To create a concrete instance of :class:`SatExtension`, use the
:meth:`SatExtension.ext` method. For example:

Attributes:
item : The item that is being extended.
.. code-block:: python

Note:
Using SatItemExt to directly wrap an item will add the 'sat'
extension ID to the item's stac_extensions.
>>> item: pystac.Item = ...
>>> sat_ext = SatExtension.ext(item)
"""

def apply(
self,
orbit_state: Optional[OrbitState] = None,
relative_orbit: Optional[int] = None,
absolute_orbit: Optional[int] = None,
platform_international_designator: Optional[str] = None,
anx_datetime: Optional[Datetime] = None,
) -> None:
"""Applies ext extension properties to the extended Item.
"""Applies ext extension properties to the extended :class:`~pystac.Item` or
class:`~pystac.Asset`.

Must specify at least one of orbit_state or relative_orbit in order
for the sat extension to properties to be valid.
Expand All @@ -62,41 +77,75 @@ def apply(
the time of acquisition.
"""

self.platform_international_designator = platform_international_designator
self.orbit_state = orbit_state
self.absolute_orbit = absolute_orbit
self.relative_orbit = relative_orbit
self.anx_datetime = anx_datetime

@property
def orbit_state(self) -> Optional[OrbitState]:
"""Get or sets an orbit state of the item.
def platform_international_designator(self) -> Optional[str]:
"""Gets or sets the International Designator, also known as COSPAR ID, and
NSSDCA ID."""
return self._get_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, str)

Returns:
OrbitState or None
"""
return map_opt(lambda x: OrbitState(x), self._get_property(ORBIT_STATE, str))
@platform_international_designator.setter
def platform_international_designator(self, v: Optional[str]) -> None:
self._set_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v)

@property
def orbit_state(self) -> Optional[OrbitState]:
"""Get or sets an orbit state of the object."""
return map_opt(
lambda x: OrbitState(x), self._get_property(ORBIT_STATE_PROP, str)
)

@orbit_state.setter
def orbit_state(self, v: Optional[OrbitState]) -> None:
self._set_property(ORBIT_STATE, map_opt(lambda x: x.value, v))
self._set_property(ORBIT_STATE_PROP, map_opt(lambda x: x.value, v))

@property
def relative_orbit(self) -> Optional[int]:
"""Get or sets a relative orbit number of the item.
def absolute_orbit(self) -> Optional[int]:
"""Get or sets a absolute orbit number of the item."""
return self._get_property(ABSOLUTE_ORBIT_PROP, int)

Returns:
int or None
"""
return self._get_property(RELATIVE_ORBIT, int)
@absolute_orbit.setter
def absolute_orbit(self, v: Optional[int]) -> None:
self._set_property(ABSOLUTE_ORBIT_PROP, v)

@property
def relative_orbit(self) -> Optional[int]:
"""Get or sets a relative orbit number of the item."""
return self._get_property(RELATIVE_ORBIT_PROP, int)

@relative_orbit.setter
def relative_orbit(self, v: Optional[int]) -> None:
self._set_property(RELATIVE_ORBIT, v)
self._set_property(RELATIVE_ORBIT_PROP, v)

@property
def anx_datetime(self) -> Optional[Datetime]:
return map_opt(str_to_datetime, self._get_property(ANX_DATETIME_PROP, str))

@anx_datetime.setter
def anx_datetime(self, v: Optional[Datetime]) -> None:
self._set_property(ANX_DATETIME_PROP, map_opt(datetime_to_str, v))

@classmethod
def get_schema_uri(cls) -> str:
return SCHEMA_URI

@classmethod
def ext(cls, obj: T, add_if_missing: bool = False) -> "SatExtension[T]":
"""Extends the given STAC Object with properties from the :stac-ext:`Satellite
Extension <sat>`.

This extension can be applied to instances of :class:`~pystac.Item` or
:class:`~pystac.Asset`.

Raises:

pystac.ExtensionTypeError : If an invalid object type is passed.
"""
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
Expand All @@ -112,8 +161,28 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> "SatExtension[T]":
f"Satellite extension does not apply to type '{type(obj).__name__}'"
)

@staticmethod
def summaries(obj: pystac.Collection) -> "SummariesSatExtension":
"""Returns the extended summaries object for the given collection."""
return SummariesSatExtension(obj)


class ItemSatExtension(SatExtension[pystac.Item]):
"""A concrete implementation of :class:`SatExtension` on an :class:`~pystac.Item`
that extends the properties of the Item to include properties defined in the
:stac-ext:`Satellite Extension <sat>`.

This class should generally not be instantiated directly. Instead, call
:meth:`SatExtension.ext` on an :class:`~pystac.Item` to
extend it.
"""

item: pystac.Item
"""The :class:`~pystac.Item` being extended."""

properties: Dict[str, Any]
"""The :class:`~pystac.Item` properties, including extension properties."""

def __init__(self, item: pystac.Item):
self.item = item
self.properties = item.properties
Expand All @@ -123,6 +192,25 @@ def __repr__(self) -> str:


class AssetSatExtension(SatExtension[pystac.Asset]):
"""A concrete implementation of :class:`SatExtension` on an :class:`~pystac.Asset`
that extends the properties of the Asset to include properties defined in the
:stac-ext:`Satellite Extension <sat>`.

This class should generally not be instantiated directly. Instead, call
:meth:`SatExtension.ext` on an :class:`~pystac.Asset` to
extend it.
"""

asset_href: str
"""The ``href`` value of the :class:`~pystac.Asset` being extended."""

properties: Dict[str, Any]
"""The :class:`~pystac.Asset` fields, including extension properties."""

additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None
"""If present, this will be a list containing 1 dictionary representing the
properties of the owning :class:`~pystac.Item`."""

def __init__(self, asset: pystac.Asset):
self.asset_href = asset.href
self.properties = asset.properties
Expand All @@ -133,6 +221,75 @@ def __repr__(self) -> str:
return "<AssetSatExtension Asset href={}>".format(self.asset_href)


class SummariesSatExtension(SummariesExtension):
"""A concrete implementation of :class:`~SummariesExtension` that extends
the ``summaries`` field of a :class:`~pystac.Collection` to include properties
defined in the :stac-ext:`Satellite Extension <sat>`.
"""

@property
def platform_international_designator(self) -> Optional[List[str]]:
"""Get or sets the summary of
:attr:`SatExtension.platform_international_designator` values for this
Collection.
"""

return self.summaries.get_list(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP)

@platform_international_designator.setter
def platform_international_designator(self, v: Optional[List[str]]) -> None:
self._set_summary(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v)

@property
def orbit_state(self) -> Optional[List[OrbitState]]:
"""Get or sets the summary of :attr:`SatExtension.orbit_state` values
for this Collection.
"""

return self.summaries.get_list(ORBIT_STATE_PROP)

@orbit_state.setter
def orbit_state(self, v: Optional[List[OrbitState]]) -> None:
self._set_summary(ORBIT_STATE_PROP, v)

@property
def absolute_orbit(self) -> Optional[RangeSummary[int]]:
return self.summaries.get_range(ABSOLUTE_ORBIT_PROP)

@absolute_orbit.setter
def absolute_orbit(self, v: Optional[RangeSummary[int]]) -> None:
self._set_summary(ABSOLUTE_ORBIT_PROP, v)

@property
def relative_orbit(self) -> Optional[RangeSummary[int]]:
return self.summaries.get_range(RELATIVE_ORBIT_PROP)

@relative_orbit.setter
def relative_orbit(self, v: Optional[RangeSummary[int]]) -> None:
self._set_summary(RELATIVE_ORBIT_PROP, v)

@property
def anx_datetime(self) -> Optional[RangeSummary[Datetime]]:
return map_opt(
lambda s: RangeSummary(
str_to_datetime(s.minimum), str_to_datetime(s.maximum)
),
self.summaries.get_range(ANX_DATETIME_PROP),
)

@anx_datetime.setter
def anx_datetime(self, v: Optional[RangeSummary[Datetime]]) -> None:
self._set_summary(
ANX_DATETIME_PROP,
map_opt(
lambda s: RangeSummary(
datetime_to_str(s.minimum), datetime_to_str(s.maximum)
),
v,
),
)


class SatExtensionHooks(ExtensionHooks):
schema_uri: str = SCHEMA_URI
prev_extension_ids: Set[str] = set(["sat"])
Expand Down
Loading