Source code for pystac.extensions.sat

"""Implements the :stac-ext:`Satellite Extension <sat>`."""

from __future__ import annotations

from collections.abc import Iterable
from datetime import datetime
from typing import (
    Any,
    Generic,
    Literal,
    TypeVar,
    Union,
    cast,
)

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

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

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

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"


[docs]class OrbitState(StringEnum): ASCENDING = "ascending" DESCENDING = "descending" GEOSTATIONARY = "geostationary"
[docs]class SatExtension( Generic[T], PropertiesExtension, ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]], ): """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`). To create a concrete instance of :class:`SatExtension`, use the :meth:`SatExtension.ext` method. For example: .. code-block:: python >>> item: pystac.Item = ... >>> sat_ext = SatExtension.ext(item) """ name: Literal["sat"] = "sat"
[docs] def apply( self, orbit_state: OrbitState | None = None, relative_orbit: int | None = None, absolute_orbit: int | None = None, platform_international_designator: str | None = None, anx_datetime: datetime | None = None, ) -> None: """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. Args: orbit_state : Optional state of the orbit. Either ascending or descending for polar orbiting satellites, or geostationary for geosynchronous satellites. relative_orbit : Optional non-negative integer of the orbit number at 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 platform_international_designator(self) -> str | None: """Gets or sets the International Designator, also known as COSPAR ID, and NSSDCA ID.""" return self._get_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, str) @platform_international_designator.setter def platform_international_designator(self, v: str | None) -> None: self._set_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v) @property def orbit_state(self) -> OrbitState | None: """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: OrbitState | None) -> None: self._set_property(ORBIT_STATE_PROP, map_opt(lambda x: x.value, v)) @property def absolute_orbit(self) -> int | None: """Get or sets a absolute orbit number of the item.""" return self._get_property(ABSOLUTE_ORBIT_PROP, int) @absolute_orbit.setter def absolute_orbit(self, v: int | None) -> None: self._set_property(ABSOLUTE_ORBIT_PROP, v) @property def relative_orbit(self) -> int | None: """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: int | None) -> None: self._set_property(RELATIVE_ORBIT_PROP, v) @property def anx_datetime(self) -> datetime | None: return map_opt(str_to_datetime, self._get_property(ANX_DATETIME_PROP, str)) @anx_datetime.setter def anx_datetime(self, v: datetime | None) -> None: self._set_property(ANX_DATETIME_PROP, map_opt(datetime_to_str, v))
[docs] @classmethod def get_schema_uri(cls) -> str: return SCHEMA_URI
[docs] @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): cls.ensure_has_extension(obj, add_if_missing) return cast(SatExtension[T], ItemSatExtension(obj)) elif isinstance(obj, pystac.Asset): cls.ensure_owner_has_extension(obj, add_if_missing) return cast(SatExtension[T], AssetSatExtension(obj)) elif isinstance(obj, item_assets.AssetDefinition): cls.ensure_owner_has_extension(obj, add_if_missing) return cast(SatExtension[T], ItemAssetsSatExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
[docs] @classmethod def summaries( cls, obj: pystac.Collection, add_if_missing: bool = False ) -> SummariesSatExtension: """Returns the extended summaries object for the given collection.""" cls.ensure_has_extension(obj, add_if_missing) return SummariesSatExtension(obj)
[docs]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 def __repr__(self) -> str: return f"<ItemSatExtension Item id={self.item.id}>"
[docs]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: Iterable[dict[str, Any]] | None = 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.extra_fields if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: return f"<AssetSatExtension Asset href={self.asset_href}>"
[docs]class ItemAssetsSatExtension(SatExtension[item_assets.AssetDefinition]): properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): self.asset_defn = item_asset self.properties = item_asset.properties
[docs]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) -> list[str] | None: """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: list[str] | None) -> None: self._set_summary(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v) @property def orbit_state(self) -> list[OrbitState] | None: """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: list[OrbitState] | None) -> None: self._set_summary(ORBIT_STATE_PROP, v) @property def absolute_orbit(self) -> RangeSummary[int] | None: return self.summaries.get_range(ABSOLUTE_ORBIT_PROP) @absolute_orbit.setter def absolute_orbit(self, v: RangeSummary[int] | None) -> None: self._set_summary(ABSOLUTE_ORBIT_PROP, v) @property def relative_orbit(self) -> RangeSummary[int] | None: return self.summaries.get_range(RELATIVE_ORBIT_PROP) @relative_orbit.setter def relative_orbit(self, v: RangeSummary[int] | None) -> None: self._set_summary(RELATIVE_ORBIT_PROP, v) @property def anx_datetime(self) -> RangeSummary[datetime] | None: 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: RangeSummary[datetime] | None) -> None: self._set_summary( ANX_DATETIME_PROP, map_opt( lambda s: RangeSummary( datetime_to_str(s.minimum), datetime_to_str(s.maximum) ), v, ), )
[docs]class SatExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids = {"sat"} stac_object_types = {pystac.STACObjectType.ITEM}
SAT_EXTENSION_HOOKS: ExtensionHooks = SatExtensionHooks()