Skip to content

Commit

Permalink
Merge pull request #201 from stac-utils/rde/fix-doc-build
Browse files Browse the repository at this point in the history
Fix spacenet tutorial, allow for single bbox argument to SpatialExtent, and other fixes.
  • Loading branch information
lossyrob authored Oct 22, 2020
2 parents dc9e860 + 1f472cf commit 1fcfb44
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 33 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,17 @@ PySTAC uses [flake8](http://flake8.pycqa.org/en/latest/) and [yapf](https://gith
To run the flake8 style checks:

```
> flake8 pystac
> flake8 tests
> flake8 pystac tests
```

To format code:

```
> yapf -ipr pystac
> yapf -ipr tests
> yapf -ipr pystac tests
```

Note that you may have to use `yapf3` explicitly depending on your environment.

You can also run the `./scripts/test` script to check flake8 and yapf.

### Documentation
Expand Down
92 changes: 77 additions & 15 deletions docs/tutorials/pystac-spacenet-tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"from botocore.errorfactory import ClientError\n",
"import pystac\n",
"from pystac.extensions import label\n",
"from shapely.geometry import GeometryCollection, Polygon, box, shape"
"from shapely.geometry import GeometryCollection, Polygon, box, shape, mapping"
]
},
{
Expand Down Expand Up @@ -217,7 +217,7 @@
" params['id'] = basename(uri).split('.')[0]\n",
" with rasterio.open(uri) as src:\n",
" params['bbox'] = list(src.bounds)\n",
" params['geometry'] = box(*params['bbox']).__geo_interface__\n",
" params['geometry'] = mapping(box(*params['bbox']))\n",
" params['datetime'] = capture_date\n",
" params['properties'] = {}\n",
" i = pystac.Item(**params)\n",
Expand All @@ -240,24 +240,53 @@
"metadata": {},
"outputs": [],
"source": [
"bounds = GeometryCollection([shape(s.geometry) for s in spacenet.get_all_items()]).bounds\n",
"bounds = [list(GeometryCollection([shape(s.geometry) for s in spacenet.get_all_items()]).bounds)]\n",
"vegas.extent.spatial = pystac.SpatialExtent(bounds)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Currently, this STAC only exists in memory. We need to set all of the paths based on the root directory we want to save off that catalog too, and then save a \"self contained\" catalog, which will have all links be relative and contain no 'self' links. We can do this all in one shot with the `normalize_and_save` method:"
"Currently, this STAC only exists in memory. We need to set all of the paths based on the root directory we want to save off that catalog too, and then save a \"self contained\" catalog, which will have all links be relative and contain no 'self' links. We can do this by using the `normalize` method to set the HREFs of all of our STAC objects. We'll then validate the catalog, and then save:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Catalog id=spacenet>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spacenet.normalize_hrefs('spacenet-stac')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"spacenet.normalize_and_save('spacenet-stac', catalog_type=pystac.CatalogType.SELF_CONTAINED)"
"spacenet.validate_all()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"spacenet.save(catalog_type=pystac.CatalogType.SELF_CONTAINED)"
]
},
{
Expand Down Expand Up @@ -285,7 +314,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -298,7 +327,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -314,7 +343,7 @@
" label_item = pystac.Item(\n",
" id='{}-labels'.format(item.id),\n",
" bbox=item.bbox,\n",
" geometry=box(*item.bbox).__geo_interface__,\n",
" geometry=mapping(box(*item.bbox)),\n",
" datetime=item.datetime,\n",
" properties={},\n",
" stac_extensions=[pystac.Extensions.LABEL]\n",
Expand All @@ -323,7 +352,12 @@
" label_item.ext.label.apply(\n",
" label_description='Building labels for scene {}'.format(item.id),\n",
" label_type=label.LabelType.VECTOR,\n",
" label_properties=['partialBuilding']\n",
" label_properties=['partialBuilding'],\n",
" \n",
" # Label classes is marked as required in 1.0.0-beta.2, so make it up.\n",
" # Once this PR is released, this can be removed:\n",
" # https://github.com/radiantearth/stac-spec/pull/905\n",
" label_classes=[label.LabelClasses.create(classes=['building'], name='partialBuilding')]\n",
" )\n",
" \n",
" label_item.ext.label.add_geojson_labels(href=label_uri)\n",
Expand All @@ -347,7 +381,7 @@
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -356,7 +390,7 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 18,
"metadata": {},
"outputs": [
{
Expand All @@ -383,7 +417,7 @@
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 19,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -428,12 +462,40 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Catalog id=spacenet>"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spacenet_cog.normalize_hrefs('spacenet-cog-stac')"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"spacenet_cog.validate_all()"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"spacenet_cog.normalize_and_save('spacenet-cog-stac', \n",
" catalog_type=pystac.CatalogType.SELF_CONTAINED)"
"spacenet_cog.save(catalog_type=pystac.CatalogType.SELF_CONTAINED)"
]
},
{
Expand Down
14 changes: 14 additions & 0 deletions pystac/collection.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import abc
from datetime import datetime
import dateutil.parser
from dateutil import tz
Expand Down Expand Up @@ -305,6 +306,12 @@ class SpatialExtent:
2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]]
"""
def __init__(self, bboxes):
# A common mistake is to pass in a single bbox instead of a list of bboxes.
# Account for this by transforming the input in that case.
if isinstance(bboxes, abc.Sequence):
if not isinstance(bboxes[0], abc.Sequence):
bboxes = [bboxes]

self.bboxes = bboxes

def to_dict(self):
Expand Down Expand Up @@ -388,6 +395,13 @@ class TemporalExtent:
Datetimes are required to be in UTC.
"""
def __init__(self, intervals):
# A common mistake is to pass in a single interval instead of a
# list of intervals. Account for this by transforming the input
# in that case.
if isinstance(intervals, abc.Sequence):
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 '
Expand Down
2 changes: 1 addition & 1 deletion pystac/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def get_href(self):
else:
href = self.target

if is_absolute_href(href) and self.owner is not None:
if href and is_absolute_href(href) and self.owner is not None:
href = make_relative_href(href, self.owner.get_self_href())
else:
href = self.get_absolute_href()
Expand Down
9 changes: 7 additions & 2 deletions pystac/validation/stac_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ def validate(self, stac_dict, stac_object_type, stac_version, extensions, href=N
STACValidator implementation.
"""
results = []
core_result = self.validate_core(stac_dict, stac_object_type, stac_version, href)

# Pass the dict through JSON serialization and parsing, otherwise
# some valid properties can be marked as invalid (e.g. tuples in
# coordinate sequences for geometries).
json_dict = json.loads(json.dumps(stac_dict))
core_result = self.validate_core(json_dict, stac_object_type, stac_version, href)
if core_result is not None:
results.append(core_result)

for extension_id in extensions:
ext_result = self.validate_extension(stac_dict, stac_object_type, stac_version,
ext_result = self.validate_extension(json_dict, stac_object_type, stac_version,
extension_id, href)
if ext_result is not None:
results.append(ext_result)
Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ipython==7.16.1
jsonschema==3.2.0
pylint==1.9.1
Sphinx==1.8.0
Expand All @@ -6,5 +7,5 @@ sphinxcontrib-fulltoc==1.2.0
sphinxcontrib-napoleon==0.7
flake8==3.8.*
yapf==0.28.*
nbsphinx==0.4.3
nbsphinx==0.7.1
coverage==5.2.*
47 changes: 38 additions & 9 deletions tests/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
from datetime import datetime

import pystac
from pystac.validation import (validate_dict, STACValidationError)
from pystac.validation import validate_dict
from pystac.serialization.identify import STACObjectType
from pystac import (Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType)
from pystac.extensions.eo import Band
from pystac.utils import datetime_to_str
from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX)

TEST_DATETIME = datetime(2020, 3, 14, 16, 32)


class CollectionTest(unittest.TestCase):
def test_spatial_extent_from_coordinates(self):
Expand All @@ -27,14 +29,14 @@ def test_eo_items_are_heritable(self):
item1 = Item(id='test-item-1',
geometry=RANDOM_GEOM,
bbox=RANDOM_BBOX,
datetime=datetime.utcnow(),
datetime=TEST_DATETIME,
properties={'key': 'one'},
stac_extensions=['eo', 'commons'])

item2 = Item(id='test-item-2',
geometry=RANDOM_GEOM,
bbox=RANDOM_BBOX,
datetime=datetime.utcnow(),
datetime=TEST_DATETIME,
properties={'key': 'two'},
stac_extensions=['eo', 'commons'])

Expand Down Expand Up @@ -114,11 +116,6 @@ def test_multiple_extents(self):
cloned_ext = ext.clone()
self.assertDictEqual(cloned_ext.to_dict(), multi_ext_dict['extent'])

multi_ext_dict['extent']['spatial']['bbox'] = multi_ext_dict['extent']['spatial']['bbox'][0]
invalid_col = Collection.from_dict(multi_ext_dict)
with self.assertRaises(STACValidationError):
invalid_col.validate()

def test_extra_fields(self):
catalog = TestCases.test_case_2()
collection = catalog.get_child('1a8c1632-fa91-4a62-b33e-3a87c2ebdf16')
Expand Down Expand Up @@ -147,7 +144,7 @@ def test_update_extents(self):
item1 = Item(id='test-item-1',
geometry=RANDOM_GEOM,
bbox=[-180, -90, 180, 90],
datetime=datetime.utcnow(),
datetime=TEST_DATETIME,
properties={'key': 'one'},
stac_extensions=['eo', 'commons'])

Expand Down Expand Up @@ -179,3 +176,35 @@ def test_update_extents(self):
self.assertEqual(
[[item2.common_metadata.start_datetime, base_extent.temporal.intervals[0][1]]],
collection.extent.temporal.intervals)


class ExtentTest(unittest.TestCase):
def test_spatial_allows_single_bbox(self):
temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]])

# Pass in a single BBOX
spatial_extent = SpatialExtent(bboxes=RANDOM_BBOX)

collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent)

collection = Collection(id='test', description='test desc', extent=collection_extent)

# HREF required by validation
collection.set_self_href('https://example.com/collection.json')

collection.validate()

def test_temporal_allows_single_interval(self):
spatial_extent = SpatialExtent(bboxes=[RANDOM_BBOX])

# Pass in a single interval
temporal_extent = TemporalExtent(intervals=[TEST_DATETIME, None])

collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent)

collection = Collection(id='test', description='test desc', extent=collection_extent)

# HREF required by validation
collection.set_self_href('https://example.com/collection.json')

collection.validate()
24 changes: 24 additions & 0 deletions tests/test_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from datetime import datetime
import unittest

import pystac
from tests.utils import (RANDOM_BBOX, RANDOM_GEOM)

TEST_DATETIME = datetime(2020, 3, 14, 16, 32)


class LinkTest(unittest.TestCase):
def test_link_does_not_fail_if_href_is_none(self):
"""Test to ensure get_href does not fail when the href is None"""
catalog = pystac.Catalog(id='test', description='test desc')
item = pystac.Item(id='test-item',
geometry=RANDOM_GEOM,
bbox=RANDOM_BBOX,
datetime=datetime.utcnow(),
properties={})
catalog.add_item(item)
catalog.set_self_href('/some/href')
catalog.make_all_links_relative()

link = catalog.get_single_link('item')
self.assertIsNone(link.get_href())
Loading

0 comments on commit 1fcfb44

Please sign in to comment.