Skip to content

Commit

Permalink
Merge pull request #254 from stac-utils/develop
Browse files Browse the repository at this point in the history
Merge develop into 0.5 branch for 0.5.4 release
  • Loading branch information
lossyrob authored Jan 15, 2021
2 parents f3c0109 + 1174848 commit 31f5662
Show file tree
Hide file tree
Showing 20 changed files with 1,096 additions and 21 deletions.
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## [v0.5.4]

### Added

- SAT Extension ([#236](https://github.com/stac-utils/pystac/pull/236))
- Add support for the scientific extension. ([#199](https://github.com/stac-utils/pystac/pull/199))

### Fixed

- Fix unexpected behaviour of `generate_subcatalogs` ([#241](https://github.com/stac-utils/pystac/pull/241))
- Get eo bands defined in assets only ([#243](https://github.com/stac-utils/pystac/pull/243))
- Collection TemporalExtent can be open ended ([#247](https://github.com/stac-utils/pystac/pull/247))
- Make asset HREFs relative or absolute based on CatalogType during save ([#251](https://github.com/stac-utils/pystac/pull/251))

### Changed

- Be more strict with CatalogType in `Catalog.save` ([#244](https://github.com/stac-utils/pystac/pull/244))


## [v0.5.3]

### Added
Expand All @@ -13,7 +32,7 @@
- Added a `catalog_type` property to track the CatalogType of read in or previously saved catalogs ([#224](https://github.com/stac-utils/pystac/pull/224))
- Added a tutorial for creating Landsat 8 STACs ([#181](https://github.com/stac-utils/pystac/pull/181))
- Added codespell to CI ([#206](https://github.com/stac-utils/pystac/pull/206))
- Added more teesting to Links ([#211](https://github.com/stac-utils/pystac/pull/211))
- Added more testing to Links ([#211](https://github.com/stac-utils/pystac/pull/211))

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if you'd like to enable the validation feature utilizing the [jsonschema](https:
From source repository:

```bash
> git clone https://github.com/azavea/pystac.git
> git clone https://github.com/stac-utils/pystac.git
> cd pystac
> pip install .
```
Expand Down
13 changes: 13 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,19 @@ SarItemExt
:undoc-members:
:show-inheritance:

SAT Extension
-------------

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

SatItemExt
~~~~~~~~~~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.sar.SatItemExt
:members:
:undoc-members:
:show-inheritance:

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

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Catalog Types

The STAC `best practices document <https://github.com/radiantearth/stac-spec/blob/v1.0.0-beta.2/best-practices.md>`_ lays out different catalog types, and how their links should be formatted. A brief description is below, but check out the document for the official take on these types:

Note that the catalog types do not dictate the asset HREF formats, only link formats. Asset HREFs in any catalog type can be relative or absolute; see the section on :ref:`rel vs abs asset` below.
The catalog types will also dictate the asset HREF formats. Asset HREFs in any catalog type can be relative or absolute may be absolute depending on their location; see the section on :ref:`rel vs abs asset` below.


Self-Contained Catalogs
Expand Down
3 changes: 3 additions & 0 deletions pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class STACError(Exception):
import pystac.extensions.pointcloud
import pystac.extensions.projection
import pystac.extensions.sar
import pystac.extensions.sat
import pystac.extensions.scientific
import pystac.extensions.single_file_stac
import pystac.extensions.timestamps
import pystac.extensions.version
Expand All @@ -44,6 +46,7 @@ class STACError(Exception):
extensions.eo.EO_EXTENSION_DEFINITION, extensions.label.LABEL_EXTENSION_DEFINITION,
extensions.pointcloud.POINTCLOUD_EXTENSION_DEFINITION,
extensions.projection.PROJECTION_EXTENSION_DEFINITION, extensions.sar.SAR_EXTENSION_DEFINITION,
extensions.sat.SAT_EXTENSION_DEFINITION, extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION,
extensions.single_file_stac.SFS_EXTENSION_DEFINITION,
extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION,
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION
Expand Down
30 changes: 24 additions & 6 deletions pystac/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ def fn():

return self

def generate_subcatalogs(self, template, defaults=None, **kwargs):
def generate_subcatalogs(self, template, defaults=None, parent_ids=None, **kwargs):
"""Walks through the catalog and generates subcatalogs
for items based on the template string. See :class:`~pystac.layout.LayoutTemplate`
for details on the construction of template strings. This template string
Expand All @@ -533,29 +533,43 @@ def generate_subcatalogs(self, template, defaults=None, **kwargs):
defaults (dict): Default values for the template variables
that will be used if the property cannot be found on
the item.
parent_ids (List[str]): Optional list of the parent catalogs'
identifiers. If the bottom-most subcatalags already match the
template, no subcatalog is added.
Returns:
[catalog]: List of new catalogs created
"""
result = []
parent_ids = parent_ids or list()
parent_ids.append(self.id)
for child in self.get_children():
result.extend(child.generate_subcatalogs(template, defaults=defaults))
result.extend(
child.generate_subcatalogs(template,
defaults=defaults,
parent_ids=parent_ids.copy()))

layout_template = LayoutTemplate(template, defaults=defaults)
subcat_id_to_cat = {}

items = list(self.get_items())
for item in items:
item_parts = layout_template.get_template_values(item)
id_iter = reversed(parent_ids)
if all(['{}'.format(id) == next(id_iter, None)
for id in reversed(item_parts.values())]):
# Skip items for which the sub-catalog structure already
# matches the template. The list of parent IDs can include more
# elements on the root side, so compare the reversed sequences.
continue
curr_parent = self
for k, v in item_parts.items():
subcat_id = '{}'.format(v)
subcat = subcat_id_to_cat.get(subcat_id)
subcat = curr_parent.get_child(subcat_id)
if subcat is None:
subcat_desc = 'Catalog of items from {} with {} of {}'.format(
curr_parent.id, k, v)
subcat = pystac.Catalog(id=subcat_id, description=subcat_desc)
curr_parent.add_child(subcat)
subcat_id_to_cat[subcat_id] = subcat
result.append(subcat)
curr_parent = subcat
self.remove_item(item.id)
Expand Down Expand Up @@ -594,8 +608,12 @@ def save(self, catalog_type=None):
# Ensure relative vs absolute
if catalog_type == CatalogType.ABSOLUTE_PUBLISHED:
self.make_all_links_absolute()
else:
self.make_all_asset_hrefs_absolute()
elif catalog_type in (CatalogType.SELF_CONTAINED, CatalogType.RELATIVE_PUBLISHED):
self.make_all_links_relative()
self.make_all_asset_hrefs_relative()
else:
raise ValueError(f'catalog_type is not a CatalogType: "{catalog_type}"')

include_self_link = catalog_type in [
CatalogType.ABSOLUTE_PUBLISHED, CatalogType.RELATIVE_PUBLISHED
Expand Down
6 changes: 1 addition & 5 deletions pystac/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import dateutil.parser
from dateutil import tz
from copy import (copy, deepcopy)
from pystac import (STACError, STACObjectType, CatalogType)
from pystac import (STACObjectType, CatalogType)
from pystac.catalog import Catalog
from pystac.link import Link
from pystac.utils import datetime_to_str
Expand Down Expand Up @@ -396,10 +396,6 @@ def __init__(self, intervals):
if not isinstance(intervals[0], abc.Sequence):
intervals = [intervals]

for i in intervals:
if i[0] is None and i[1] is None:
raise STACError('TemporalExtent interval must have either '
'a start or an end time, or both')
self.intervals = intervals

def to_dict(self):
Expand Down
10 changes: 9 additions & 1 deletion pystac/extensions/eo.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def get_bands(self, asset=None):
"""Gets an Item or an Asset bands.
If an Asset is supplied and the bands property exists on the Asset,
returns the Asset's value. Otherwise returns the Item's value
returns the Asset's value. Otherwise returns the Item's value or
all the asset's eo bands
Returns:
List[Band]
Expand All @@ -65,6 +66,13 @@ def get_bands(self, asset=None):
else:
bands = self.item.properties.get('eo:bands')

# get assets with eo:bands even if not in item
if asset is None and bands is None:
bands = []
for (key, value) in self.item.get_assets().items():
if 'eo:bands' in value.properties:
bands.extend(value.properties.get('eo:bands'))

if bands is not None:
bands = [Band(b) for b in bands]

Expand Down
114 changes: 114 additions & 0 deletions pystac/extensions/sat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Implement the Satellite (SAT) Extension.
https://github.com/radiantearth/stac-spec/tree/dev/extensions/sat
"""

import enum
from typing import List, Optional

import pystac
from pystac import Extensions
from pystac import item
from pystac.extensions import base

ORBIT_STATE: str = 'sat:orbit_state'
RELATIVE_ORBIT: str = 'sat:relative_orbit'


class OrbitState(enum.Enum):
ASCENDING: str = 'ascending'
DESCENDING: str = 'descending'
GEOSTATIONARY: str = 'geostationary'


class SatItemExt(base.ItemExtension):
"""SatItemExt extends Item to add sat properties to a STAC Item.
Args:
item (Item): The item to be extended.
Attributes:
item (Item): The item that is being extended.
Note:
Using SatItemExt to directly wrap an item will add the 'sat'
extension ID to the item's stac_extensions.
"""
item: pystac.Item

def __init__(self, an_item: item.Item) -> None:
self.item = an_item

def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Optional[str] = None):
"""Applies ext extension properties to the extended Item.
Must specify at least one of orbit_state or relative_orbit.
Args:
orbit_state (OrbitState): Optional state of the orbit. Either ascending or descending
for polar orbiting satellites, or geostationary for geosynchronous satellites.
relative_orbit (int): Optional non-negative integer of the orbit number at the time
of acquisition.
"""
if orbit_state is None and relative_orbit is None:
raise pystac.STACError('Must provide at least one of: orbit_state or relative_orbit')
if orbit_state:
self.orbit_state = orbit_state
if relative_orbit:
self.relative_orbit = relative_orbit

@classmethod
def from_item(cls, an_item: item.Item):
return cls(an_item)

@classmethod
def _object_links(cls) -> List:
return []

@property
def orbit_state(self) -> Optional[OrbitState]:
"""Get or sets an orbit state of the item.
Returns:
OrbitState or None
"""
if ORBIT_STATE not in self.item.properties:
return
return OrbitState(self.item.properties.get(ORBIT_STATE))

@orbit_state.setter
def orbit_state(self, v: Optional[OrbitState]) -> None:
if v is None:
if self.relative_orbit is None:
raise pystac.STACError('Must set relative_orbit before clearing orbit_state')
if ORBIT_STATE in self.item.properties:
del self.item.properties[ORBIT_STATE]
else:
self.item.properties[ORBIT_STATE] = v.value

@property
def relative_orbit(self) -> int:
"""Get or sets a relative orbit number of the item.
Returns:
int or None
"""
return self.item.properties.get(RELATIVE_ORBIT)

@relative_orbit.setter
def relative_orbit(self, v: int) -> None:
if v is None and self.orbit_state is None:
raise pystac.STACError('Must set orbit_state before clearing relative_orbit')
if v is None:
if RELATIVE_ORBIT in self.item.properties:
del self.item.properties[RELATIVE_ORBIT]
return
if v < 0:
raise pystac.STACError(f'relative_orbit must be >= 0. Found {v}.')

self.item.properties[RELATIVE_ORBIT] = v


SAT_EXTENSION_DEFINITION = base.ExtensionDefinition(Extensions.SAT, [
base.ExtendedObject(pystac.Item, SatItemExt),
])
Loading

0 comments on commit 31f5662

Please sign in to comment.