Skip to content

Commit a0f79a3

Browse files
author
Jon Duckworth
authored
Merge pull request #509 from duckontheweb/update/sat-extension
Update Satellite Extension
2 parents d78d122 + 9a972d8 commit a0f79a3

File tree

4 files changed

+392
-35
lines changed

4 files changed

+392
-35
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Timestamps Extension summaries ([#513](https://github.com/stac-utils/pystac/pull/513))
1414
- Define equality and `__repr__` of `RangeSummary` instances based on `to_dict`
1515
representation ([#513](https://github.com/stac-utils/pystac/pull/513))
16+
- Sat Extension summaries ([#509](https://github.com/stac-utils/pystac/pull/509))
1617

1718
### Changed
1819

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

3235
### Removed
3336

docs/api.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,45 @@ RasterExtension
499499
:show-inheritance:
500500
:inherited-members:
501501

502+
Satellite Extension
503+
-------------------
504+
505+
OrbitState
506+
~~~~~~~~~~
507+
508+
.. autoclass:: pystac.extensions.sat.OrbitState
509+
:members:
510+
:show-inheritance:
511+
:undoc-members:
512+
513+
SatExtension
514+
~~~~~~~~~~~~
515+
516+
.. autoclass:: pystac.extensions.sat.SatExtension
517+
:members:
518+
:show-inheritance:
519+
520+
ItemSatExtension
521+
~~~~~~~~~~~~~~~~
522+
523+
.. autoclass:: pystac.extensions.sat.ItemSatExtension
524+
:members:
525+
:show-inheritance:
526+
527+
AssetSatExtension
528+
~~~~~~~~~~~~~~~~~
529+
530+
.. autoclass:: pystac.extensions.sat.AssetSatExtension
531+
:members:
532+
:show-inheritance:
533+
534+
SummariesSatExtension
535+
~~~~~~~~~~~~~~~~~~~~~
536+
537+
.. autoclass:: pystac.extensions.sat.SummariesSatExtension
538+
:members:
539+
:show-inheritance:
540+
502541
Scientific Extension
503542
--------------------
504543

pystac/extensions/sat.py

Lines changed: 185 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,34 @@
44
"""
55

66
import enum
7-
from typing import Generic, Optional, Set, TypeVar, cast
7+
from datetime import datetime as Datetime
8+
from pystac.summaries import RangeSummary
9+
from typing import Dict, Any, List, Generic, Iterable, Optional, Set, TypeVar, cast
810

911
import pystac
1012
from pystac.extensions.base import (
1113
ExtensionManagementMixin,
1214
PropertiesExtension,
15+
SummariesExtension,
1316
)
1417
from pystac.extensions.hooks import ExtensionHooks
15-
from pystac.utils import map_opt
18+
from pystac.utils import str_to_datetime, datetime_to_str, map_opt
1619

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

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

21-
ORBIT_STATE: str = "sat:orbit_state"
22-
RELATIVE_ORBIT: str = "sat:relative_orbit"
24+
PREFIX: str = "sat:"
25+
PLATFORM_INTERNATIONAL_DESIGNATOR_PROP: str = (
26+
PREFIX + "platform_international_designator"
27+
)
28+
ABSOLUTE_ORBIT_PROP: str = PREFIX + "absolute_orbit"
29+
ORBIT_STATE_PROP: str = PREFIX + "orbit_state"
30+
RELATIVE_ORBIT_PROP: str = PREFIX + "relative_orbit"
31+
ANX_DATETIME_PROP: str = PREFIX + "anx_datetime"
2332

2433

25-
class OrbitState(enum.Enum):
34+
class OrbitState(str, enum.Enum):
2635
ASCENDING = "ascending"
2736
DESCENDING = "descending"
2837
GEOSTATIONARY = "geostationary"
@@ -31,25 +40,31 @@ class OrbitState(enum.Enum):
3140
class SatExtension(
3241
Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item]
3342
):
34-
"""SatItemExt extends Item to add sat properties to a STAC Item.
43+
"""An abstract class that can be used to extend the properties of an
44+
:class:`~pystac.Item` or :class:`~pystac.Asset` with properties from the
45+
:stac-ext:`Satellite Extension <sat>`. This class is generic over the type of
46+
STAC Object to be extended (e.g. :class:`~pystac.Item`,
47+
:class:`~pystac.Collection`).
3548
36-
Args:
37-
item : The item to be extended.
49+
To create a concrete instance of :class:`SatExtension`, use the
50+
:meth:`SatExtension.ext` method. For example:
3851
39-
Attributes:
40-
item : The item that is being extended.
52+
.. code-block:: python
4153
42-
Note:
43-
Using SatItemExt to directly wrap an item will add the 'sat'
44-
extension ID to the item's stac_extensions.
54+
>>> item: pystac.Item = ...
55+
>>> sat_ext = SatExtension.ext(item)
4556
"""
4657

4758
def apply(
4859
self,
4960
orbit_state: Optional[OrbitState] = None,
5061
relative_orbit: Optional[int] = None,
62+
absolute_orbit: Optional[int] = None,
63+
platform_international_designator: Optional[str] = None,
64+
anx_datetime: Optional[Datetime] = None,
5165
) -> None:
52-
"""Applies ext extension properties to the extended Item.
66+
"""Applies ext extension properties to the extended :class:`~pystac.Item` or
67+
class:`~pystac.Asset`.
5368
5469
Must specify at least one of orbit_state or relative_orbit in order
5570
for the sat extension to properties to be valid.
@@ -62,41 +77,75 @@ def apply(
6277
the time of acquisition.
6378
"""
6479

80+
self.platform_international_designator = platform_international_designator
6581
self.orbit_state = orbit_state
82+
self.absolute_orbit = absolute_orbit
6683
self.relative_orbit = relative_orbit
84+
self.anx_datetime = anx_datetime
6785

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

72-
Returns:
73-
OrbitState or None
74-
"""
75-
return map_opt(lambda x: OrbitState(x), self._get_property(ORBIT_STATE, str))
92+
@platform_international_designator.setter
93+
def platform_international_designator(self, v: Optional[str]) -> None:
94+
self._set_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v)
95+
96+
@property
97+
def orbit_state(self) -> Optional[OrbitState]:
98+
"""Get or sets an orbit state of the object."""
99+
return map_opt(
100+
lambda x: OrbitState(x), self._get_property(ORBIT_STATE_PROP, str)
101+
)
76102

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

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

85-
Returns:
86-
int or None
87-
"""
88-
return self._get_property(RELATIVE_ORBIT, int)
112+
@absolute_orbit.setter
113+
def absolute_orbit(self, v: Optional[int]) -> None:
114+
self._set_property(ABSOLUTE_ORBIT_PROP, v)
115+
116+
@property
117+
def relative_orbit(self) -> Optional[int]:
118+
"""Get or sets a relative orbit number of the item."""
119+
return self._get_property(RELATIVE_ORBIT_PROP, int)
89120

90121
@relative_orbit.setter
91122
def relative_orbit(self, v: Optional[int]) -> None:
92-
self._set_property(RELATIVE_ORBIT, v)
123+
self._set_property(RELATIVE_ORBIT_PROP, v)
124+
125+
@property
126+
def anx_datetime(self) -> Optional[Datetime]:
127+
return map_opt(str_to_datetime, self._get_property(ANX_DATETIME_PROP, str))
128+
129+
@anx_datetime.setter
130+
def anx_datetime(self, v: Optional[Datetime]) -> None:
131+
self._set_property(ANX_DATETIME_PROP, map_opt(datetime_to_str, v))
93132

94133
@classmethod
95134
def get_schema_uri(cls) -> str:
96135
return SCHEMA_URI
97136

98137
@classmethod
99138
def ext(cls, obj: T, add_if_missing: bool = False) -> "SatExtension[T]":
139+
"""Extends the given STAC Object with properties from the :stac-ext:`Satellite
140+
Extension <sat>`.
141+
142+
This extension can be applied to instances of :class:`~pystac.Item` or
143+
:class:`~pystac.Asset`.
144+
145+
Raises:
146+
147+
pystac.ExtensionTypeError : If an invalid object type is passed.
148+
"""
100149
if isinstance(obj, pystac.Item):
101150
if add_if_missing:
102151
cls.add_to(obj)
@@ -112,8 +161,28 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> "SatExtension[T]":
112161
f"Satellite extension does not apply to type '{type(obj).__name__}'"
113162
)
114163

164+
@staticmethod
165+
def summaries(obj: pystac.Collection) -> "SummariesSatExtension":
166+
"""Returns the extended summaries object for the given collection."""
167+
return SummariesSatExtension(obj)
168+
115169

116170
class ItemSatExtension(SatExtension[pystac.Item]):
171+
"""A concrete implementation of :class:`SatExtension` on an :class:`~pystac.Item`
172+
that extends the properties of the Item to include properties defined in the
173+
:stac-ext:`Satellite Extension <sat>`.
174+
175+
This class should generally not be instantiated directly. Instead, call
176+
:meth:`SatExtension.ext` on an :class:`~pystac.Item` to
177+
extend it.
178+
"""
179+
180+
item: pystac.Item
181+
"""The :class:`~pystac.Item` being extended."""
182+
183+
properties: Dict[str, Any]
184+
"""The :class:`~pystac.Item` properties, including extension properties."""
185+
117186
def __init__(self, item: pystac.Item):
118187
self.item = item
119188
self.properties = item.properties
@@ -123,6 +192,25 @@ def __repr__(self) -> str:
123192

124193

125194
class AssetSatExtension(SatExtension[pystac.Asset]):
195+
"""A concrete implementation of :class:`SatExtension` on an :class:`~pystac.Asset`
196+
that extends the properties of the Asset to include properties defined in the
197+
:stac-ext:`Satellite Extension <sat>`.
198+
199+
This class should generally not be instantiated directly. Instead, call
200+
:meth:`SatExtension.ext` on an :class:`~pystac.Asset` to
201+
extend it.
202+
"""
203+
204+
asset_href: str
205+
"""The ``href`` value of the :class:`~pystac.Asset` being extended."""
206+
207+
properties: Dict[str, Any]
208+
"""The :class:`~pystac.Asset` fields, including extension properties."""
209+
210+
additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None
211+
"""If present, this will be a list containing 1 dictionary representing the
212+
properties of the owning :class:`~pystac.Item`."""
213+
126214
def __init__(self, asset: pystac.Asset):
127215
self.asset_href = asset.href
128216
self.properties = asset.extra_fields
@@ -133,6 +221,75 @@ def __repr__(self) -> str:
133221
return "<AssetSatExtension Asset href={}>".format(self.asset_href)
134222

135223

224+
class SummariesSatExtension(SummariesExtension):
225+
"""A concrete implementation of :class:`~SummariesExtension` that extends
226+
the ``summaries`` field of a :class:`~pystac.Collection` to include properties
227+
defined in the :stac-ext:`Satellite Extension <sat>`.
228+
"""
229+
230+
@property
231+
def platform_international_designator(self) -> Optional[List[str]]:
232+
"""Get or sets the summary of
233+
:attr:`SatExtension.platform_international_designator` values for this
234+
Collection.
235+
"""
236+
237+
return self.summaries.get_list(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP)
238+
239+
@platform_international_designator.setter
240+
def platform_international_designator(self, v: Optional[List[str]]) -> None:
241+
self._set_summary(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v)
242+
243+
@property
244+
def orbit_state(self) -> Optional[List[OrbitState]]:
245+
"""Get or sets the summary of :attr:`SatExtension.orbit_state` values
246+
for this Collection.
247+
"""
248+
249+
return self.summaries.get_list(ORBIT_STATE_PROP)
250+
251+
@orbit_state.setter
252+
def orbit_state(self, v: Optional[List[OrbitState]]) -> None:
253+
self._set_summary(ORBIT_STATE_PROP, v)
254+
255+
@property
256+
def absolute_orbit(self) -> Optional[RangeSummary[int]]:
257+
return self.summaries.get_range(ABSOLUTE_ORBIT_PROP)
258+
259+
@absolute_orbit.setter
260+
def absolute_orbit(self, v: Optional[RangeSummary[int]]) -> None:
261+
self._set_summary(ABSOLUTE_ORBIT_PROP, v)
262+
263+
@property
264+
def relative_orbit(self) -> Optional[RangeSummary[int]]:
265+
return self.summaries.get_range(RELATIVE_ORBIT_PROP)
266+
267+
@relative_orbit.setter
268+
def relative_orbit(self, v: Optional[RangeSummary[int]]) -> None:
269+
self._set_summary(RELATIVE_ORBIT_PROP, v)
270+
271+
@property
272+
def anx_datetime(self) -> Optional[RangeSummary[Datetime]]:
273+
return map_opt(
274+
lambda s: RangeSummary(
275+
str_to_datetime(s.minimum), str_to_datetime(s.maximum)
276+
),
277+
self.summaries.get_range(ANX_DATETIME_PROP),
278+
)
279+
280+
@anx_datetime.setter
281+
def anx_datetime(self, v: Optional[RangeSummary[Datetime]]) -> None:
282+
self._set_summary(
283+
ANX_DATETIME_PROP,
284+
map_opt(
285+
lambda s: RangeSummary(
286+
datetime_to_str(s.minimum), datetime_to_str(s.maximum)
287+
),
288+
v,
289+
),
290+
)
291+
292+
136293
class SatExtensionHooks(ExtensionHooks):
137294
schema_uri: str = SCHEMA_URI
138295
prev_extension_ids: Set[str] = set(["sat"])

0 commit comments

Comments
 (0)