From d71c303079f0d7ef834371d9a778ff295f5142d1 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 11 Mar 2021 10:18:09 -0500 Subject: [PATCH 01/51] Add test files for 1.0.0-RC1 --- tests/data-files/examples/1.0.0-RC1/README.md | 89 ++++++ .../examples/1.0.0-RC1/catalog.json | 30 ++ .../1.0.0-RC1/collection-only/collection.json | 228 ++++++++++++++ .../examples/1.0.0-RC1/collection.json | 93 ++++++ .../1.0.0-RC1/collectionless-item.json | 147 +++++++++ .../examples/1.0.0-RC1/core-item.json | 117 ++++++++ .../examples/1.0.0-RC1/extended-item.json | 190 ++++++++++++ .../extensions-collection/collection.json | 66 +++++ .../proj-example/proj-example.json | 278 ++++++++++++++++++ .../examples/1.0.0-RC1/simple-item.json | 74 +++++ tests/data-files/examples/example-info.csv | 146 +-------- 11 files changed, 1320 insertions(+), 138 deletions(-) create mode 100644 tests/data-files/examples/1.0.0-RC1/README.md create mode 100644 tests/data-files/examples/1.0.0-RC1/catalog.json create mode 100644 tests/data-files/examples/1.0.0-RC1/collection-only/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC1/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC1/collectionless-item.json create mode 100644 tests/data-files/examples/1.0.0-RC1/core-item.json create mode 100644 tests/data-files/examples/1.0.0-RC1/extended-item.json create mode 100644 tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json create mode 100644 tests/data-files/examples/1.0.0-RC1/simple-item.json diff --git a/tests/data-files/examples/1.0.0-RC1/README.md b/tests/data-files/examples/1.0.0-RC1/README.md new file mode 100644 index 000000000..85b5f5a1d --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/README.md @@ -0,0 +1,89 @@ +# STAC Examples + +This directory contains various examples for all parts of the STAC specification. It is structured to be two valid STACs, meaning both [catalog.json](catalog.json) and [collection.json](collection.json) should successfully load in various tools. They do not follow *all* the [best practices](../best-practices.md) for STAC, mostly +due to the fact that they contrive examples to show the spec and we are hosting in GitHub. But we note below where they differ from an ideal catalog. + +The various fields are mostly fictional, to be able to demonstrate the various aspects of the spec as tersely as possible. To get a sense +of real world STAC implementations we recommend exploring the [stac-examples](http://github.com/stac-utils/stac-examples) repo, which +gathers in one place copies of STAC [Items](../item-spec/item-spec.md) and [Collection](../collection-spec/collection-spec.md) +from a number of different production catalogs that all follow good STAC practices. And you should also explore the various catalogs +listed on [STAC Index](http://stacindex.org), to see full catalogs in production. + +## Organization + +This directory contains two STAC implementations, both valid, but simplified a bit to be illustrative of the key concepts, so +they do not quite follow all the best practices. + +### Simple Collection + +This STAC implementation consists of three files, all contained at the root of the examples directory + +**[collection.json](collection.json)** is a minimal 'simple collection', that links to three items. + +**[simple-item.json](simple-item.json)** is the most minimal possible compliant Item record. Most all data will +include additional fields, as STAC is designed to be a minimal common subset. But it is useful for showing exactly what is +required. + +**[core-item.json](core-item.json)** is a more realistic example, for a hypothetical analytic image +acquisition from a satellite company called 'Remote Data'. It includes additional fields covering the [common +metadata](../item-spec/common-metadata.md). It also links to a variety of assets that is typical for +satellite imagery, as most providers include a number of complementary files. + +**[extended-item.json](extended-item.json)** is arguably an even more realistic example, as it includes a number of the +[extensions](../extensions/) that are commonly used, to demonstrate how implementations tend to start with the core, and add in +a number of the core extensions. + +**[collectionless-item.json](collectionless-item.json)** demonstrates the common metadata that is only used when an Item does not have +a collection. It is recommended to organize items in collections, but we wanted to show how this works. This is not technically in the +'simple collection' of this section, but it follows the same pattern, so is included here. + +### Nested Catalog + +This STAC implementation shows a common pattern, starting with a catalog that links to a number of distinct collections, which may +link down to a number of items. + +**[catalog.json](catalog.json)** is a minimal catalog implementation, linking to two other collections. + +**[collection-only/collection.json](collection-only/collection.json)** is a collection that does not link to any items. This +demonstrates how is is possible to make use of STAC Collections without needing items, to serve as nice summarizing metadata for +tools that work with full layers / collections. This example collection is based on real Sentinel-2 values, so is not quite fictional, +but should be taken as just an example. + +**[extensions-collection/collection.json](extensions-collection/collection.json)** contains a small number of items, that demonstrate +more functionality available in STAC [extensions](../extensions/). These are linked to directly from the individual extensions. These +items follow the recommendations for [Catalog Layout Best Practices](../best-practices.md#catalog-layout). + +## In Depth + +As mentioned above, the files in this examples directory form valid STAC implementations. They are all based on a +fictional remote sensing company called 'Remote Data', with a URL at remotedata.io. This domain has not been set up, so those links +will not work, but any valid data provider should provide valid links to their homepage. + +The examples use the `rd:` prefix to show how providers can use custom fields when there are not set fields. In the examples these +do not link to a schema which is completely valid, but it is recommended that providers do write a JSON schema that can validate +their custom fields (we will work to add an example schema for the `rd:` fields in the future). + +### Catalog Type + +One of the most important STAC Best Practices is to [use links consistently](../best-practices.md#use-of-links), following one of the +described 'catalog types'. The catalogs described here are [Relative Published Catalogs](../best-practices.md#relative-published-catalog), +that use absolute URL's to refer to their assets (so would be an example of a [Self-contained Metadata +Only](../best-practices.md#self-contained-metadata-only) catalog that is published). + +### Differences with STAC Best Practices + +One of the most important documents in this repository is the one about [best practices](../best-practices.md). It describes a number +of practical recommendations gained by people actually implementing STAC. The core spec is designed to be as flexible as possible, so +that it is not too rigid and unable to handle unanticipated needs. But we recommend following as many of the best practices as is +feasible, as it will help ensure various STAC tools work much better. The examples in this folder don't align with all the best +practices, mostly because they are meant to demonstrate things as tersely as possible, and also because they live directly inside +a github repository. As many people will look at these examples and take them as 'how things should be' we felt its important to +highlight where things here differ from the actual best practices. + +#### Catalog Layout + +Another important recommendations concerns the [layout of STAC catalogs](../best-practices.md#catalog-layout). This is important +for tools to be able to expect a certain layout, and most tools will follow the described layout. The simple collection that consists +of the collection.json and its 3 linked items violates this. This is done to be able to show item examples directly in the root of +the 'examples' folder, so people don't have to dig deep into folders to get a quick example. But a proper catalog layout would +put the items in sub-directories, along with their assets. diff --git a/tests/data-files/examples/1.0.0-RC1/catalog.json b/tests/data-files/examples/1.0.0-RC1/catalog.json new file mode 100644 index 000000000..086a734e0 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/catalog.json @@ -0,0 +1,30 @@ +{ + "id": "examples", + "type": "Catalog", + "stac_version": "1.0.0-beta.2", + "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.", + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./extensions-collection/collection.json", + "type": "application/json", + "title": "Collection Demonstrating STAC Extensions" + }, + { + "rel": "child", + "href": "./collection-only/collection.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0-RC.1/examples/catalog.json", + "type": "application/json" + } + ] +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/collection-only/collection.json b/tests/data-files/examples/1.0.0-RC1/collection-only/collection.json new file mode 100644 index 000000000..2c83d7df8 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/collection-only/collection.json @@ -0,0 +1,228 @@ +{ + "type": "Collection", + "stac_version": "1.0.0-beta.2", + "stac_extensions": [], + "id": "sentinel-2", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-1C", + "description": "Sentinel-2 is a wide-swath, high-resolution, multi-spectral\nimaging mission supporting Copernicus Land Monitoring studies,\nincluding the monitoring of vegetation, soil and water cover,\nas well as observation of inland waterways and coastal areas.\n\nThe Sentinel-2 data contain 13 UINT16 spectral bands representing\nTOA reflectance scaled by 10000. See the [Sentinel-2 User Handbook](https://sentinel.esa.int/documents/247904/685211/Sentinel-2_User_Handbook)\nfor details. In addition, three QA bands are present where one\n(QA60) is a bitmask band with cloud mask information. For more\ndetails, [see the full explanation of how cloud masks are computed.](https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-1c/cloud-masks)\n\nEach Sentinel-2 product (zip archive) may contain multiple\ngranules. Each granule becomes a separate Earth Engine asset.\nEE asset ids for Sentinel-2 assets have the following format:\nCOPERNICUS/S2/20151128T002653_20151128T102149_T56MNN. Here the\nfirst numeric part represents the sensing date and time, the\nsecond numeric part represents the product generation date and\ntime, and the final 6-character string is a unique granule identifier\nindicating its UTM grid reference (see [MGRS](https://en.wikipedia.org/wiki/Military_Grid_Reference_System)).\n\nFor more details on Sentinel-2 radiometric resoltuon, [see this page](https://earth.esa.int/web/sentinel/user-guides/sentinel-2-msi/resolutions/radiometric).\n", + "license": "proprietary", + "keywords": [ + "copernicus", + "esa", + "eu", + "msi", + "radiance", + "sentinel" + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2015-06-23T00:00:00Z", + null + ] + ] + } + }, + "assets": { + "metadata_iso_19139": { + "roles": [ + "metadata", + "iso-19139" + ], + "href": "https://storage.googleapis.com/open-cogs/stac-examples/sentinel-2-iso-19139.xml", + "title": "ISO 19139 metadata", + "type": "application/vnd.iso.19139+xml" + } + }, + "summaries": { + "datetime": { + "minimum": "2015-06-23T00:00:00Z", + "maximum": "2019-07-10T13:44:56Z" + }, + "platform": [ + "sentinel-2a", + "sentinel-2b" + ], + "constellation": [ + "sentinel-2" + ], + "instruments": [ + "msi" + ], + "view:off_nadir": { + "minimum": 0, + "maximum": 100 + }, + "view:sun_elevation": { + "minimum": 6.78, + "maximum": 89.9 + }, + "sci:citation": [ + "Copernicus Sentinel data [Year]" + ], + "gsd": [ + 10, + 30, + 60 + ], + "proj:epsg": [ + 32601, + 32602, + 32603, + 32604, + 32605, + 32606, + 32607, + 32608, + 32609, + 32610, + 32611, + 32612, + 32613, + 32614, + 32615, + 32616, + 32617, + 32618, + 32619, + 32620, + 32621, + 32622, + 32623, + 32624, + 32625, + 32626, + 32627, + 32628, + 32629, + 32630, + 32631, + 32632, + 32633, + 32634, + 32635, + 32636, + 32637, + 32638, + 32639, + 32640, + 32641, + 32642, + 32643, + 32644, + 32645, + 32646, + 32647, + 32648, + 32649, + 32650, + 32651, + 32652, + 32653, + 32654, + 32655, + 32656, + 32657, + 32658, + 32659, + 32660 + ], + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 4.439 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 4.966 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 5.6 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 6.645 + }, + { + "name": "B5", + "center_wavelength": 7.039 + }, + { + "name": "B6", + "center_wavelength": 7.402 + }, + { + "name": "B7", + "center_wavelength": 7.825 + }, + { + "name": "B8", + "common_name": "nir", + "center_wavelength": 8.351 + }, + { + "name": "B8A", + "center_wavelength": 8.648 + }, + { + "name": "B9", + "center_wavelength": 9.45 + }, + { + "name": "B10", + "center_wavelength": 1.3735 + }, + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137 + }, + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.2024 + } + ] + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json" + }, + { + "rel": "root", + "href": "../catalog.json" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ] +} diff --git a/tests/data-files/examples/1.0.0-RC1/collection.json b/tests/data-files/examples/1.0.0-RC1/collection.json new file mode 100644 index 000000000..45356b103 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/collection.json @@ -0,0 +1,93 @@ +{ + "id": "simple-collection", + "type": "Collection", + "stac_version": "1.0.0-beta.2", + "description": "A simple collection demonstrating core catalog fields with links to a couple of items", + "title": "Simple Example Collection", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.911, + 1.343, + 172.955, + 1.3691 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T09:06:43.312000Z", + "2020-12-14T18:02:31.437000Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "summaries": { + "platform": [ + "cool_sat2", + "cool_sat1" + ], + "constellation": [ + "ion" + ], + "instruments": [ + "cool_sensor_v1" + ], + "gsd": { + "minimum": 0.512, + "maximum": 0.7 + }, + "view:off_nadir": { + "minimum": 0, + "maximum": 15 + }, + "view:sun_elevation": { + "minimum": 6.78, + "maximum": 40 + } + }, + "links": [ + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./simple-item.json", + "type": "application/geo+json", + "title": "Simple Item" + }, + { + "rel": "item", + "href": "./core-item.json", + "type": "application/geo+json", + "title": "Core Item" + }, + { + "rel": "item", + "href": "./extended-item.json", + "type": "application/geo+json", + "title": "Extended Item" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0-RC.1/examples/collection.json", + "type": "application/json" + } + ] +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/collectionless-item.json b/tests/data-files/examples/1.0.0-RC1/collectionless-item.json new file mode 100644 index 000000000..8b2e3aa09 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/collectionless-item.json @@ -0,0 +1,147 @@ +{ + "stac_version": "1.0.0-beta.2", + "stac_extensions": [ + "eo", + "view" + ], + "type": "Feature", + "id": "CS3-20160503_132131_08", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.2880486, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "title": "Full Item", + "description": "A sample STAC Item demonstrates an Item that does not have a collection, which is not recommended, but allowed by the spec.", + "datetime": null, + "start_datetime": "2016-05-03T13:22:30Z", + "end_datetime": "2016-05-03T13:27:30Z", + "created": "2016-05-04T00:00:01Z", + "updated": "2017-01-01T00:30:55Z", + "license": "various", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.it" + } + ], + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "view:sun_elevation": 33.4, + "gsd": 0.512, + "cs:type": "scene", + "cs:anomalous_pixels": 0.14, + "cs:earth_sun_distance": 1.014156, + "cs:sat_id": "CS3", + "cs:product_level": "LV1B" + }, + "collection": "CS3", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html" + }, + { + "rel": "license", + "type": "text/html", + "href": "http://remotedata.io/license.html" + } + ], + "assets": { + "analytic": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", + "title": "4-Band Analytic", + "eo:bands": [ + { + "name": "band1" + }, + { + "name": "band1" + }, + { + "name": "band2" + }, + { + "name": "band3" + } + ] + }, + "thumbnail": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "udm": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/UDM.tif", + "title": "Unusable Data Mask" + }, + "json-metadata": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/S3-20160503_132130_04.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/core-item.json b/tests/data-files/examples/1.0.0-RC1/core-item.json new file mode 100644 index 000000000..3004df37d --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/core-item.json @@ -0,0 +1,117 @@ +{ + "stac_version": "1.0.0-beta.2", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/extended-item.json b/tests/data-files/examples/1.0.0-RC1/extended-item.json new file mode 100644 index 000000000..a84586008 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/extended-item.json @@ -0,0 +1,190 @@ +{ + "stac_version": "1.0.0-beta.2", + "stac_extensions": [ + "eo", + "projection", + "scientific", + "view" + ], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Extended Item", + "description": "A sample STAC Item that includes a variety of examples from the stable extensions", + "datetime": "2020-12-11T22:38:32.125Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "gsd": 0.66, + "eo:cloud_cover": 1.2, + "proj:epsg": 32659, + "proj:shape": [ + 5558, + 9559 + ], + "proj:transform": [ + 0.5, + 0, + 712710, + 0, + -0.5, + 151406, + 0, + 0, + 1 + ], + "view:sun_elevation": 54.9, + "view:off_nadir": 3.8, + "view:sun_azimuth": 135.7, + "rd:type": "scene", + "rd:anomalous_pixels": 0.14, + "rd:earth_sun_distance": 1.014156, + "rd:sat_id": "cool_sat2", + "rd:product_level": "LV3A", + "sci:doi": "10.5061/dryad.s2v81.2/27.2" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ], + "eo:bands": [ + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band4", + "common_name": "nir", + "center_wavelength": 800, + "full_width_half_max": 152 + } + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ], + "eo:bands": [ + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + } + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json b/tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json new file mode 100644 index 000000000..dabbbeda3 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json @@ -0,0 +1,66 @@ +{ + "id": "extensions-collection", + "type": "Collection", + "stac_version": "1.0.0-beta.2", + "description": "A heterogenous collection containing deeper examples of various extensions", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./proj-example/proj-example.json", + "title": "Proj extension example" + }, + { + "rel": "license", + "href": "https://remotedata.io/license.html", + "title": "Remote Data License Terms" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [], + "title": "Collection of Extension Items", + "keywords": [ + "examples", + "sar", + "projection" + ], + "providers": [ + { + "name": "Remote Data, Inc.", + "roles": [ + "producer", + "licensor" + ], + "url": "https://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2009-05-20T02:40:01.042784Z", + "2018-11-03T23:59:55.112875Z" + ] + ] + } + }, + "license": "PDDL-1.0" +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json b/tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json new file mode 100644 index 000000000..c3eca2040 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json @@ -0,0 +1,278 @@ +{ + "type": "Feature", + "stac_version": "1.0.0-beta.2", + "id": "proj-example", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "proj:epsg": 32614, + "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "proj:projjson": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 14N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "conversion": { + "name": "UTM zone 14N", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": -99, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 0.9996, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 500000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "area": "World - N hemisphere - 102°W to 96°W - by country", + "bbox": { + "south_latitude": 0, + "west_longitude": -102, + "north_latitude": 84, + "east_longitude": -96 + }, + "id": { + "authority": "EPSG", + "code": 32614 + } + }, + "proj:geometry": { + "coordinates": [ + [ + [ + 169200, + 3712800 + ], + [ + 403200, + 3712800 + ], + [ + 403200, + 3951000 + ], + [ + 169200, + 3951000 + ], + [ + 169200, + 3712800 + ] + ] + ], + "type": "Polygon" + }, + "proj:bbox": [ + 169200, + 3712800, + 403200, + 3951000 + ], + "proj:centroid": { + "lat": 34.595302781575604, + "lon": -101.34448382627504 + }, + "proj:shape": [ + 8391, + 8311 + ], + "proj:transform": [ + 30, + 0, + 224985, + 0, + -30, + 6790215, + 0, + 0, + 1 + ] + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 + ] + ] + ] + }, + "links": [ + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "B1": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + } + ] + }, + "B8": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 8 (panchromatic)", + "eo:bands": [ + { + "name": "B8", + "common_name": "panchromatic", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + } + ], + "proj:shape": [ + 16781, + 16621 + ], + "proj:transform": [ + 15, + 0, + 224992.5, + 0, + -15, + 6790207.5, + 0, + 0, + 1 + ] + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "eo", + "projection" + ], + "collection": "landsat-8-l1" +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC1/simple-item.json b/tests/data-files/examples/1.0.0-RC1/simple-item.json new file mode 100644 index 000000000..38921081d --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC1/simple-item.json @@ -0,0 +1,74 @@ +{ + "stac_version": "1.0.0-beta.2", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "datetime": "2020-12-11T22:38:32.125000Z", + "collection": "simple-collection" + }, + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + } + ], + "assets": { + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/jpeg", + "roles": [ + "thumbnail" + ] + } + } +} \ No newline at end of file diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index e6de13398..6c26c82e5 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -1,138 +1,8 @@ -"0.4.1/extensions/examples/landsat8-merged.json","ITEM","0.4.1","eo","" -"0.5.2/extensions/examples/landsat8-merged.json","ITEM","0.5.2","eo","" -"0.7.0/extensions/sar/examples/sentinel1.json","ITEM","0.7.0","sar|datetime-range","" -"0.8.1/catalog-spec/examples/catalog.json","CATALOG","0.8.1","","" -"0.8.1/catalog-spec/examples/summaries-s2.json","CATALOG","0.8.1","","" -"0.8.1/collection-spec/examples/landsat-collection.json","COLLECTION","0.8.1","","" -"0.8.1/collection-spec/examples/landsat-item.json","ITEM","0.8.1","eo","" -"0.8.1/collection-spec/examples/sentinel2.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/asset/examples/example-landsat8.json","COLLECTION","0.8.1","asset","" -"0.8.1/extensions/checksum/examples/example-sentinel1.json","ITEM","0.8.1","checksum","" -"0.8.1/extensions/datacube/examples/example.json","ITEM","0.8.1","datacube","" -"0.8.1/extensions/datetime-range/examples/example-video.json","ITEM","0.8.1","datetime-range","" -"0.8.1/extensions/eo/examples/example-landsat8.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.8.1/extensions/label/examples/multidataset/catalog.json","CATALOG","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.8.1","","" -"0.8.1/extensions/pointcloud/examples/example-autzen.json","ITEM","0.8.1","pointcloud","" -"0.8.1/extensions/sar/examples/envisat.json","ITEM","0.8.1","sar|datetime-range","" -"0.8.1/extensions/sar/examples/sentinel1.json","ITEM","0.8.1","checksum|sar|datetime-range","" -"0.8.1/extensions/scientific/examples/collection.json","COLLECTION","0.8.1","scientific","" -"0.8.1/extensions/scientific/examples/item.json","ITEM","0.8.1","datetime-range|checksum|scientific","" -"0.8.1/item-spec/examples/digitalglobe-sample.json","ITEM","0.8.1","eo","https://example.digitalglobe.com/stac/1.0/schema.json" -"0.8.1/item-spec/examples/landsat8-sample.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.8.1/item-spec/examples/planet-sample.json","ITEM","0.8.1","eo","https://example.planet.com/stac/1.0/schema.json" -"0.8.1/item-spec/examples/sample-full.json","ITEM","0.8.1","eo","https://example.com/cs-extension/1.0/schema.json" -"0.8.1/item-spec/examples/sample.json","ITEM","0.8.1","","" -"0.8.1/item-spec/examples/sentinel2-sample.json","ITEM","0.8.1","eo","" -"0.9.0/catalog-spec/examples/catalog.json","CATALOG","0.9.0","","" -"0.9.0/collection-spec/examples/landsat-collection.json","COLLECTION","0.9.0","commons|view|eo","" -"0.9.0/collection-spec/examples/landsat-item.json","ITEM","0.9.0","commons|eo|view","https://example.com/stac/landsat-extension/1.0/schema.json","INVALID" -"0.9.0/collection-spec/examples/sentinel2.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/asset/examples/example-landsat8.json","COLLECTION","0.9.0","asset|commons","" -"0.9.0/extensions/checksum/examples/sentinel1.json","ITEM","0.9.0","checksum","" -"0.9.0/extensions/commons/examples/landsat-collection.json","COLLECTION","0.9.0","commons","" -"0.9.0/extensions/commons/examples/landsat-item.json","ITEM","0.9.0","commons|eo|sat","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/extensions/datacube/examples/example-collection.json","COLLECTION","0.9.0","datacube","" -"0.9.0/extensions/datacube/examples/example-item.json","ITEM","0.9.0","datacube","" -"0.9.0/extensions/eo/examples/example-landsat8.json","ITEM","0.9.0","eo|view|commons","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/extensions/label/examples/multidataset/catalog.json","CATALOG","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.9.0","label|version","" -"0.9.0/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.9.0","","" -"0.9.0/extensions/pointcloud/examples/example-autzen.json","ITEM","0.9.0","pointcloud","" -"0.9.0/extensions/projection/examples/example-landsat8.json","ITEM","0.9.0","proj|commons","" -"0.9.0/extensions/sar/examples/envisat.json","ITEM","0.9.0","sat|sar","" -"0.9.0/extensions/sar/examples/sentinel1.json","ITEM","0.9.0","checksum|sar|sat","" -"0.9.0/extensions/sat/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" -"0.9.0/extensions/scientific/examples/collection.json","COLLECTION","0.9.0","scientific","" -"0.9.0/extensions/scientific/examples/item.json","ITEM","0.9.0","scientific|checksum","" -"0.9.0/extensions/version/examples/collection.json","COLLECTION","0.9.0","version","" -"0.9.0/extensions/version/examples/item.json","ITEM","0.9.0","version","","INVALID" -"0.9.0/extensions/view/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" -"0.9.0/item-spec/examples/datetimerange.json","ITEM","0.9.0","","" -"0.9.0/item-spec/examples/digitalglobe-sample.json","ITEM","0.9.0","eo|proj|view","https://example.digitalglobe.com/stac/1.0/schema.json" -"0.9.0/item-spec/examples/landsat8-sample.json","ITEM","0.9.0","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/item-spec/examples/planet-sample.json","ITEM","0.9.0","eo|view","https://example.planet.com/stac/1.0/schema.json" -"0.9.0/item-spec/examples/sample-full.json","ITEM","0.9.0","eo|view","https://example.com/cs-extension/1.0/schema.json" -"0.9.0/item-spec/examples/sample.json","ITEM","0.9.0","","" -"0.9.0/item-spec/examples/sentinel2-sample.json","ITEM","0.9.0","eo|view|proj|commons","" -"1.0.0-beta.2/catalog-spec/examples/catalog-items.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/catalog-spec/examples/catalog.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/collection-spec/examples/landsat-collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/collection-spec/examples/sentinel2.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/checksum/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum","" -"1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json","COLLECTION","1.0.0-beta.2","collection-assets","https://github.com/NCAR/esm-collection-spec/tree/v0.2.0/schema.json" -"1.0.0-beta.2/extensions/datacube/examples/example-collection.json","COLLECTION","1.0.0-beta.2","datacube","" -"1.0.0-beta.2/extensions/datacube/examples/example-item.json","ITEM","1.0.0-beta.2","datacube","" -"1.0.0-beta.2/extensions/eo/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json","COLLECTION","1.0.0-beta.2","item-assets","" -"1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json","ITEM","1.0.0-beta.2","pointcloud","" -"1.0.0-beta.2/extensions/projection/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|projection","" -"1.0.0-beta.2/extensions/sar/examples/envisat.json","ITEM","1.0.0-beta.2","sat|sar","" -"1.0.0-beta.2/extensions/sar/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum|sar|sat","" -"1.0.0-beta.2/extensions/sat/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" -"1.0.0-beta.2/extensions/scientific/examples/collection.json","COLLECTION","1.0.0-beta.2","scientific","" -"1.0.0-beta.2/extensions/scientific/examples/item.json","ITEM","1.0.0-beta.2","scientific|checksum","" -"1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json","ITEM","1.0.0-beta.2","datacube|eo|tiled-assets","" -"1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json","ITEM","1.0.0-beta.2","eo|tiled-assets","" -"1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json","ITEM","1.0.0-beta.2","timestamps","" -"1.0.0-beta.2/extensions/version/examples/collection.json","COLLECTION","1.0.0-beta.2","version","" -"1.0.0-beta.2/extensions/version/examples/item.json","ITEM","1.0.0-beta.2","version","" -"1.0.0-beta.2/extensions/view/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" -"1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json","ITEM","1.0.0-beta.2","projection|view","https://example.com/stac/cbers-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/datetimerange.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json","ITEM","1.0.0-beta.2","eo|projection|view","https://example.digitalglobe.com/stac/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/landsat8-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/planet-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.planet.com/stac/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/sample-full.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/cs-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/sample.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/item-spec/examples/sentinel2-sample.json","ITEM","1.0.0-beta.2","view|projection","" -"gee-0.6.2/CIESIN_GPWv411_GPW_National_Identifier_Grid.json","COLLECTION","0.6.2","scientific","" -"gee-0.6.2/LANDSAT_LT05_C01_T1_ANNUAL_NDWI.json","COLLECTION","0.6.2","","" -"gee-0.6.2/catalog.json","CATALOG","0.6.2","","" -"iserv-0.6.1/2013/03/27/IP0201303271418280967S05834W.json","ITEM","0.6.1","eo","" -"iserv-0.6.1/2013/03/27/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/2013/03/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/2013/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/catalog.json","COLLECTION","0.6.1","","" -"landsat-0.6.0/010/117/2015-01-02/LC80101172015002LGN00.json","ITEM","0.6.0","eo","" -"landsat-0.6.0/010/117/catalog.json","CATALOG","0.6.0","","" -"landsat-0.6.0/010/catalog.json","COLLECTION","0.6.0","","" -"landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json","ITEM","0.6.0","eo","" -"landsat-0.6.0/156/029/catalog.json","CATALOG","0.6.0","","" -"landsat-0.6.0/156/catalog.json","COLLECTION","0.6.0","","" -"landsat-0.6.0/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/2017-10-13/S2B_9VXK_20171013_0.json","ITEM","0.6.0","eo","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","","" -"hand-0.9.0/collection.json","COLLECTION","0.9.0","","" -"hand-0.8.1/collection.json","COLLECTION","0.8.1","","" \ No newline at end of file +"1.0.0-RC1/catalog.json","CATALOG","1.0.0-beta.2","","" +"1.0.0-RC1/collection-only/collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-RC1/collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-RC1/collectionless-item.json","COLLECTION","1.0.0-beta.2","eo|view","" +"1.0.0-RC1/core-item.json","ITEM","1.0.0-beta.2","","" +"1.0.0-RC1/extended-item.json","ITEM","1.0.0-beta.2","eo|projection|scientific|view","" +"1.0.0-RC1/extensions-collection/collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-RC1/extensions-collection/proj-example/proj-example.json","COLLECTION","1.0.0-beta.2","eo|projection","" \ No newline at end of file From d531cc23d5c4477d7b35efa1c21f0670aff5676e Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Wed, 21 Apr 2021 22:32:37 -0400 Subject: [PATCH 02/51] Add type annotations to everything --- pystac/__init__.py | 55 +- pystac/cache.py | 70 +- pystac/catalog.py | 170 ++- pystac/collection.py | 643 ++++----- pystac/extensions/base.py | 126 +- pystac/extensions/eo.py | 323 ++--- pystac/extensions/file.py | 70 +- pystac/extensions/label.py | 806 +++++------ pystac/extensions/pointcloud.py | 731 +++++----- pystac/extensions/projection.py | 101 +- pystac/extensions/sar.py | 91 +- pystac/extensions/sat.py | 19 +- pystac/extensions/scientific.py | 94 +- pystac/extensions/single_file_stac.py | 45 +- pystac/extensions/timestamps.py | 61 +- pystac/extensions/version.py | 52 +- pystac/extensions/view.py | 58 +- pystac/item.py | 1504 +++++++++++---------- pystac/layout.py | 91 +- pystac/link.py | 58 +- pystac/serialization/__init__.py | 10 +- pystac/serialization/common_properties.py | 43 +- pystac/serialization/identify.py | 175 +-- pystac/serialization/migrate.py | 123 +- pystac/stac_io.py | 27 +- pystac/stac_object.py | 246 ++-- pystac/utils.py | 39 +- pystac/validation/__init__.py | 44 +- pystac/validation/schema_uri_map.py | 26 +- pystac/validation/stac_validator.py | 63 +- pystac/version.py | 11 +- 31 files changed, 3160 insertions(+), 2815 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index 529cdab4e..7dc8b13fb 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -13,15 +13,17 @@ class STACError(Exception): pass -from pystac.version import (__version__, get_stac_version, set_stac_version) +from typing import Any, Dict, Optional +from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore from pystac.stac_io import STAC_IO -from pystac.extensions import Extensions -from pystac.stac_object import (STACObject, STACObjectType) -from pystac.media_type import MediaType -from pystac.link import (Link, HIERARCHICAL_LINKS) -from pystac.catalog import (Catalog, CatalogType) -from pystac.collection import (Collection, Extent, SpatialExtent, TemporalExtent, Provider) -from pystac.item import (Item, Asset, CommonMetadata) +from pystac.extensions import Extensions # type:ignore +from pystac.stac_object import (STACObject, STACObjectType) # type:ignore +from pystac.media_type import MediaType # type:ignore +from pystac.link import (Link, HIERARCHICAL_LINKS) # type:ignore +from pystac.catalog import (Catalog, CatalogType) # type:ignore +from pystac.collection import (Collection, Extent, SpatialExtent, TemporalExtent, # type:ignore + Provider) # type:ignore +from pystac.item import (Item, Asset, CommonMetadata) # type:ignore from pystac.serialization import stac_object_from_dict @@ -29,7 +31,7 @@ class STACError(Exception): STAC_IO.stac_object_from_dict = stac_object_from_dict -from pystac import extensions +import pystac.extensions.base import pystac.extensions.eo import pystac.extensions.label import pystac.extensions.pointcloud @@ -43,19 +45,24 @@ class STACError(Exception): import pystac.extensions.view import pystac.extensions.file -STAC_EXTENSIONS = extensions.base.RegisteredSTACExtensions([ - 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, - extensions.file.FILE_EXTENSION_DEFINITION -]) - - -def read_file(href): +STAC_EXTENSIONS: pystac.extensions.base.RegisteredSTACExtensions = pystac.extensions.base.RegisteredSTACExtensions( + [ + pystac.extensions.eo.EO_EXTENSION_DEFINITION, + pystac.extensions.label.LABEL_EXTENSION_DEFINITION, + pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_DEFINITION, + pystac.extensions.projection.PROJECTION_EXTENSION_DEFINITION, + pystac.extensions.sar.SAR_EXTENSION_DEFINITION, + pystac.extensions.sat.SAT_EXTENSION_DEFINITION, + pystac.extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION, + pystac.extensions.single_file_stac.SFS_EXTENSION_DEFINITION, + pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION, + pystac.extensions.version.VERSION_EXTENSION_DEFINITION, + pystac.extensions.view.VIEW_EXTENSION_DEFINITION, + pystac.extensions.file.FILE_EXTENSION_DEFINITION + ]) + + +def read_file(href: str) -> STACObject: """Reads a STAC object from a file. This method will return either a Catalog, a Collection, or an Item based on what the @@ -73,7 +80,7 @@ def read_file(href): return STACObject.from_file(href) -def write_file(obj, include_self_link=True, dest_href=None): +def write_file(obj: STACObject, include_self_link: bool = True, dest_href: Optional[str] = None): """Writes a STACObject to a file. This will write only the Catalog, Collection or Item ``obj``. It will not attempt @@ -96,7 +103,7 @@ def write_file(obj, include_self_link=True, dest_href=None): obj.save_object(include_self_link=include_self_link, dest_href=dest_href) -def read_dict(d, href=None, root=None): +def read_dict(d: Dict[str, Any], href: Optional[str] = None, root: Optional[Catalog] = None): """Reads a STAC object from a dict representing the serialized JSON version of the STAC object. diff --git a/pystac/cache.py b/pystac/cache.py index 7ffc98d36..fabe4b18d 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -1,10 +1,13 @@ from collections import ChainMap from copy import copy +from pystac.collection import Collection +from typing import Any, Dict, List, Optional, Tuple, Union, cast +from pystac.stac_object import STACObject import pystac -def get_cache_key(stac_object): +def get_cache_key(stac_object: STACObject) -> Tuple[str, bool]: """Produce a cache key for the given STAC object. If a self href is set, use that as the cache key. @@ -20,7 +23,7 @@ def get_cache_key(stac_object): if href is not None: return (href, True) else: - ids = [] + ids: List[str] = [] obj = stac_object while obj is not None: ids.append(obj.id) @@ -52,14 +55,17 @@ class ResolvedObjectCache: their cached object. ids_to_collections (Dict[str, Collection]): Map of collection IDs to collections. """ - def __init__(self, id_keys_to_objects=None, hrefs_to_objects=None, ids_to_collections=None): + def __init__(self, + id_keys_to_objects: Optional[Dict[str, STACObject]] = None, + hrefs_to_objects: Optional[Dict[str, STACObject]] = None, + ids_to_collections: Dict[str, Collection] = None): self.id_keys_to_objects = id_keys_to_objects or {} self.hrefs_to_objects = hrefs_to_objects or {} self.ids_to_collections = ids_to_collections or {} self._collection_cache = None - def get_or_cache(self, obj): + def get_or_cache(self, obj: STACObject) -> STACObject: """Gets the STACObject that is the cached version of the given STACObject; or, if none exists, sets the cached object to the given object. @@ -85,7 +91,7 @@ def get_or_cache(self, obj): self.cache(obj) return obj - def get(self, obj): + def get(self, obj: STACObject) -> Optional[STACObject]: """Get the cached object that has the same cache key as the given object. Args: @@ -101,7 +107,7 @@ def get(self, obj): else: return self.id_keys_to_objects.get(key) - def get_by_href(self, href): + def get_by_href(self, href: str) -> Optional[STACObject]: """Gets the cached object at href. Args: @@ -112,7 +118,7 @@ def get_by_href(self, href): """ return self.hrefs_to_objects.get(href) - def get_collection_by_id(self, id): + def get_collection_by_id(self, id: str) -> Optional[Collection]: """Retrieved a cached Collection by its ID. Args: @@ -124,7 +130,7 @@ def get_collection_by_id(self, id): """ return self.ids_to_collections.get(id) - def cache(self, obj): + def cache(self, obj: STACObject) -> None: """Set the given object into the cache. Args: @@ -136,10 +142,10 @@ def cache(self, obj): else: self.id_keys_to_objects[key] = obj - if obj.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION: + if isinstance(obj, Collection): self.ids_to_collections[obj.id] = obj - def remove(self, obj): + def remove(self, obj: STACObject) -> None: """Removes any cached object that matches the given object's cache key. Args: @@ -155,21 +161,21 @@ def remove(self, obj): if obj.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION: self.id_keys_to_objects.pop(obj.id, None) - def __contains__(self, obj): + def __contains__(self, obj: STACObject) -> bool: key, is_href = get_cache_key(obj) return key in self.hrefs_to_objects if is_href else key in self.id_keys_to_objects - def contains_collection_id(self, collection_id): + def contains_collection_id(self, collection_id: str) -> bool: """Returns True if there is a collection with given collection ID is cached.""" return collection_id in self.ids_to_collections - def as_collection_cache(self): + def as_collection_cache(self) -> "CollectionCache": if self._collection_cache is None: self._collection_cache = ResolvedObjectCollectionCache(self) return self._collection_cache @staticmethod - def merge(first, second): + def merge(first: "ResolvedObjectCache", second: "ResolvedObjectCache") -> "ResolvedObjectCache": """Merges two ResolvedObjectCache. The merged cache will give preference to the first argument; that is, if there @@ -206,55 +212,65 @@ class CollectionCache: The CollectionCache will contain collections as either as dicts or PySTAC Collections, and will set Collection JSON that it reads in order to merge in common properties. """ - def __init__(self, cached_ids=None, cached_hrefs=None): + def __init__(self, + cached_ids: Dict[str, Union[Collection, Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union[Collection, Dict[str, Any]]] = None): self.cached_ids = cached_ids or {} self.cached_hrefs = cached_hrefs or {} - def get_by_id(self, collection_id): + def get_by_id(self, collection_id: str) -> Optional[Union[Collection, Dict[str, Any]]]: return self.cached_ids.get(collection_id) - def get_by_href(self, href): + def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: return self.cached_hrefs.get(href) - def contains_id(self, collection_id): + def contains_id(self, collection_id: str) -> bool: return collection_id in self.cached_ids - def cache(self, collection, href=None): + def cache(self, collection: Union[Collection, Dict[str, Any]], href: Optional[str] = None) -> None: """Caches a collection JSON.""" - self.cached_ids[collection['id']] = collection + if isinstance(collection, Collection): + self.cached_ids[collection.id] = collection + else: + self.cached_ids[collection['id']] = collection if href is not None: self.cached_hrefs[href] = collection class ResolvedObjectCollectionCache(CollectionCache): - def __init__(self, resolved_object_cache, cached_ids=None, cached_hrefs=None): + def __init__(self, + resolved_object_cache: ResolvedObjectCache, + cached_ids: Dict[str, Union[Collection, Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union[Collection, Dict[str, Any]]] = None): super().__init__(cached_ids, cached_hrefs) self.resolved_object_cache = resolved_object_cache - def get_by_id(self, collection_id): + def get_by_id(self, collection_id: str) -> Optional[Union[Collection, Dict[str, Any]]]: result = self.resolved_object_cache.get_collection_by_id(collection_id) if result is None: return super().get_by_id(collection_id) else: return result - def get_by_href(self, href): + def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: result = self.resolved_object_cache.get_by_href(href) if result is None: return super().get_by_href(href) else: - return result + return cast(Collection, result) - def contains_id(self, collection_id): + def contains_id(self, collection_id: str) -> bool: return (self.resolved_object_cache.contains_collection_id(collection_id) or super().contains_id(collection_id)) - def cache(self, collection, href=None): + def cache(self, collection: Dict[str, Any], href: Optional[str] = None) -> None: super().cache(collection, href) @staticmethod - def merge(resolved_object_cache, first, second): + def merge(resolved_object_cache: ResolvedObjectCache, + first: Optional["ResolvedObjectCollectionCache"], + second: Optional["ResolvedObjectCollectionCache"]) -> "ResolvedObjectCollectionCache": first_cached_ids = {} if first is not None: first_cached_ids = copy(first.cached_ids) diff --git a/pystac/catalog.py b/pystac/catalog.py index ce32c2df8..89bbe4f4b 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -1,11 +1,13 @@ import os from copy import deepcopy from enum import Enum +from pystac.item import Asset, Item +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast import pystac from pystac import STACError from pystac.stac_object import STACObject -from pystac.layout import (BestPracticesLayoutStrategy, LayoutTemplate) +from pystac.layout import (BestPracticesLayoutStrategy, HrefLayoutStrategy, LayoutTemplate) from pystac.link import Link from pystac.cache import ResolvedObjectCache from pystac.utils import (is_absolute_href, make_absolute_href) @@ -43,7 +45,7 @@ def __str__(self): """ # noqa E501 @classmethod - def determine_type(cls, stac_json): + def determine_type(cls, stac_json: Dict[str, Any]) -> Optional["CatalogType"]: """Determines the catalog type based on a STAC JSON dict. Only applies to Catalogs or Collections @@ -52,7 +54,7 @@ def determine_type(cls, stac_json): stac_json (dict): The STAC JSON dict to determine the catalog type Returns: - str or None: The catalog type of the catalog or collection. + Optional[CatalogType]: The catalog type of the catalog or collection. Will return None if it cannot be determined. """ self_link = None @@ -103,22 +105,22 @@ class Catalog(STACObject): of the Catalog. links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this Catalog. - catalog_type (str or None): The catalog type, or None if not known. + catalog_type (str): The catalog type. Defaults to ABSOLUTE_PUBLISHED """ STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG DEFAULT_FILE_NAME = "catalog.json" - """Default file name that will be given to this STAC object in a cononical format.""" + """Default file name that will be given to this STAC object in a canonical format.""" def __init__(self, - id, - description, - title=None, - stac_extensions=None, - extra_fields=None, - href=None, - catalog_type=CatalogType.ABSOLUTE_PUBLISHED): - super().__init__(stac_extensions) + id: str, + description: str, + title: Optional[str] = None, + stac_extensions: Optional[List[str]] = None, + extra_fields: Optional[Dict[str, Any]] = None, + href: Optional[str] = None, + catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED): + super().__init__(stac_extensions or []) self.id = id self.description = description @@ -139,19 +141,22 @@ def __init__(self, self._resolved_objects.cache(self) - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.id) - def set_root(self, root): + def set_root(self, root: Optional["Catalog"]) -> None: STACObject.set_root(self, root) if root is not None: root._resolved_objects = ResolvedObjectCache.merge(root._resolved_objects, self._resolved_objects) - def is_relative(self): + def is_relative(self) -> bool: return self.catalog_type in [CatalogType.RELATIVE_PUBLISHED, CatalogType.SELF_CONTAINED] - def add_child(self, child, title=None, strategy=None): + def add_child(self, + child: "Catalog", + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None): """Adds a link to a child :class:`~pystac.Catalog` or :class:`~pystac.Collection`. This method will set the child's parent to this object, and its root to this Catalog's root. @@ -159,6 +164,8 @@ def add_child(self, child, title=None, strategy=None): Args: child (Catalog or Collection): The child to add. title (str): Optional title to give to the :class:`~pystac.Link` + strategy (HrefLayoutStrategy): The layout strategy to use for setting the self + href of the child. """ # Prevent typo confusion @@ -179,7 +186,7 @@ def add_child(self, child, title=None, strategy=None): self.add_link(Link.child(child, title=title)) - def add_children(self, children): + def add_children(self, children: Iterable["Catalog"]) -> None: """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection`s. This method will set each child's parent to this object, and their root to this Catalog's root. @@ -190,7 +197,10 @@ def add_children(self, children): for child in children: self.add_child(child) - def add_item(self, item, title=None, strategy=None): + def add_item(self, + item: Item, + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None) -> None: """Adds a link to an :class:`~pystac.Item`. This method will set the item's parent to this object, and its root to this Catalog's root. @@ -218,7 +228,7 @@ def add_item(self, item, title=None, strategy=None): self.add_link(Link.item(item, title=title)) - def add_items(self, items): + def add_items(self, items: Iterable[Item]) -> None: """Adds links to multiple :class:`~pystac.Item` s. This method will set each item's parent to this object, and their root to this Catalog's root. @@ -229,7 +239,7 @@ def add_items(self, items): for item in items: self.add_item(item) - def get_child(self, id, recursive=False): + def get_child(self, id: str, recursive: bool = False) -> Optional["Catalog"]: """Gets the child of this catalog with the given ID, if it exists. Args: @@ -249,14 +259,14 @@ def get_child(self, id, recursive=False): return child return None - def get_children(self): + def get_children(self) -> Iterable["Catalog"]: """Return all children of this catalog. Return: - Generator[Catalog or Collection]: Generator of children who's parent + Iterable[Catalog]: Generator of children who's parent is this catalog. """ - return self.get_stac_objects('child') + return map(lambda x: cast(Catalog, x), self.get_stac_objects('child')) def get_child_links(self): """Return all child links of this catalog. @@ -266,7 +276,7 @@ def get_child_links(self): """ return self.get_links('child') - def clear_children(self): + def clear_children(self) -> "Catalog": """Removes all children from this catalog. Return: @@ -277,28 +287,28 @@ def clear_children(self): self.remove_child(child_id) return self - def remove_child(self, child_id): + def remove_child(self, child_id: str) -> None: """Removes an child from this catalog. Args: child_id (str): The ID of the child to remove. """ - new_links = [] + new_links: List[Link] = [] root = self.get_root() for link in self.links: if link.rel != 'child': new_links.append(link) else: link.resolve_stac_object(root=root) - if link.target.id != child_id: + child = cast("Catalog", link.target) + if child.id != child_id: new_links.append(link) else: - child = link.target child.set_parent(None) child.set_root(None) self.links = new_links - def get_item(self, id, recursive=False): + def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: """Returns an item with a given ID. Args: @@ -312,21 +322,21 @@ def get_item(self, id, recursive=False): if not recursive: return next((i for i in self.get_items() if i.id == id), None) else: - for root, children, items in self.walk(): + for root, _, _ in self.walk(): item = root.get_item(id, recursive=False) if item is not None: return item return None - def get_items(self): + def get_items(self) -> Iterable[Item]: """Return all items of this catalog. Return: - Generator[Item]: Generator of items who's parent is this catalog. + Iterable[Item]: Generator of items who's parent is this catalog. """ - return self.get_stac_objects('item') + return map(lambda x: cast(Item, x), self.get_stac_objects('item')) - def clear_items(self): + def clear_items(self) -> "Catalog": """Removes all items from this catalog. Return: @@ -334,35 +344,35 @@ def clear_items(self): """ for link in self.get_item_links(): if link.is_resolved(): - item = link.target + item = cast(Item, link.target) item.set_parent(None) item.set_root(None) self.links = [link for link in self.links if link.rel != 'item'] return self - def remove_item(self, item_id): + def remove_item(self, item_id: str) -> None: """Removes an item from this catalog. Args: item_id (str): The ID of the item to remove. """ - new_links = [] + new_links: List[Link] = [] root = self.get_root() for link in self.links: if link.rel != 'item': new_links.append(link) else: link.resolve_stac_object(root=root) - if link.target.id != item_id: + item = cast(Item, link.target) + if item.id != item_id: new_links.append(link) else: - item = link.target item.set_parent(None) item.set_root(None) self.links = new_links - def get_all_items(self): + def get_all_items(self) -> Iterable[Item]: """Get all items from this catalog and all subcatalogs. Will traverse any subcatalogs recursively. @@ -375,7 +385,7 @@ def get_all_items(self): for child in self.get_children(): yield from child.get_all_items() - def get_item_links(self): + def get_item_links(self) -> List[Link]: """Return all item links of this catalog. Return: @@ -383,12 +393,12 @@ def get_item_links(self): """ return self.get_links('item') - def to_dict(self, include_self_link=True): + def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: links = self.links if not include_self_link: links = filter(lambda l: l.rel != 'self', links) - d = { + d: Dict[str, Any] = { 'id': self.id, 'stac_version': pystac.get_stac_version(), 'description': self.description, @@ -406,7 +416,7 @@ def to_dict(self, include_self_link=True): return d - def clone(self): + def clone(self) -> "Catalog": clone = Catalog(id=self.id, description=self.description, title=self.title, @@ -444,7 +454,10 @@ def make_all_asset_hrefs_absolute(self): for item in items: item.make_asset_hrefs_absolute() - def normalize_and_save(self, root_href, catalog_type=None, strategy=None): + def normalize_and_save(self, + root_href: str, + catalog_type: Optional[CatalogType] = None, + strategy: Optional[HrefLayoutStrategy] = None): """Normalizes link HREFs to the given root_href, and saves the catalog. This is a convenience method that simply calls :func:`Catalog.normalize_hrefs @@ -463,7 +476,7 @@ def normalize_and_save(self, root_href, catalog_type=None, strategy=None): self.normalize_hrefs(root_href, strategy=strategy) self.save(catalog_type) - def normalize_hrefs(self, root_href, strategy=None): + def normalize_hrefs(self, root_href: str, strategy: Optional[HrefLayoutStrategy] = None): """Normalize HREFs will regenerate all link HREFs based on an absolute root_href and the canonical catalog layout as specified in the STAC specification's best practices. @@ -485,7 +498,7 @@ def normalize_hrefs(self, root_href, strategy=None): if not is_absolute_href(root_href): root_href = make_absolute_href(root_href, os.getcwd(), start_is_dir=True) - def process_item(item, _root_href): + def process_item(item: Item, _root_href: str) -> Callable[[], None]: item.resolve_links() new_self_href = strategy.get_href(item, _root_href) @@ -495,8 +508,9 @@ def fn(): return fn - def process_catalog(cat, _root_href, is_root): - setter_funcs = [] + def process_catalog(cat: Catalog, _root_href: str, + is_root: bool) -> List[Callable[[], None]]: + setter_funcs: List[Callable[[], None]] = [] cat.resolve_links() @@ -526,7 +540,11 @@ def fn(): return self - def generate_subcatalogs(self, template, defaults=None, parent_ids=None, **kwargs): + def generate_subcatalogs(self, + template: str, + defaults: Optional[Dict[str, Any]] = None, + parent_ids: List[str] = None, + **kwargs: Any) -> List["Catalog"]: """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 @@ -540,13 +558,13 @@ def generate_subcatalogs(self, template, defaults=None, parent_ids=None, **kwarg 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 + identifiers. If the bottom-most subcatalogs already match the template, no subcatalog is added. Returns: [catalog]: List of new catalogs created """ - result = [] + result: List[Catalog] = [] parent_ids = parent_ids or list() parent_ids.append(self.id) for child in self.get_children(): @@ -557,11 +575,11 @@ def generate_subcatalogs(self, template, defaults=None, parent_ids=None, **kwarg layout_template = LayoutTemplate(template, defaults=defaults) - keep_item_links = [] + keep_item_links: List[Link] = [] item_links = [lk for lk in self.links if lk.rel == 'item'] for link in item_links: link.resolve_stac_object(root=self.get_root()) - item = link.target + item = cast(Item, link.target) item_parts = layout_template.get_template_values(item) id_iter = reversed(parent_ids) if all(['{}'.format(id) == next(id_iter, None) @@ -595,7 +613,7 @@ def generate_subcatalogs(self, template, defaults=None, parent_ids=None, **kwarg return result - def save(self, catalog_type=None): + def save(self, catalog_type: CatalogType = None) -> None: """Save this catalog and all it's children/item to files determined by the object's self link HREF. @@ -625,11 +643,11 @@ def save(self, catalog_type=None): for child_link in self.get_child_links(): if child_link.is_resolved(): - child_link.target.save() + cast(Catalog, child_link.target).save() for item_link in self.get_item_links(): if item_link.is_resolved(): - item_link.target.save_object(include_self_link=items_include_self_link) + cast(Item, item_link.target).save_object(include_self_link=items_include_self_link) include_self_link = False # include a self link if this is the root catalog or if ABSOLUTE_PUBLISHED catalog @@ -642,7 +660,7 @@ def save(self, catalog_type=None): self.catalog_type = catalog_type - def walk(self): + def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable[Item]]]: """Walks through children and items of catalogs. For each catalog in the STAC's tree rooted at this catalog (including this catalog @@ -664,7 +682,7 @@ def walk(self): for child in self.get_children(): yield from child.walk() - def validate_all(self): + def validate_all(self) -> None: """Validates each catalog, collection contained within this catalog. Walks through the children and items of the catalog and validates each @@ -680,10 +698,10 @@ def validate_all(self): for item in self.get_items(): item.validate() - def _object_links(self): + def _object_links(self) -> List[str]: return ['child', 'item'] + (pystac.STAC_EXTENSIONS.get_extended_object_links(self)) - def map_items(self, item_mapper): + def map_items(self, item_mapper: Callable[[Item], Union[Item, List[Item]]]): """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -697,19 +715,19 @@ def map_items(self, item_mapper): to the item_mapper function. """ - new_cat = self.full_copy() + new_cat = cast(Catalog, self.full_copy()) - def process_catalog(catalog): + def process_catalog(catalog: Catalog): for child in catalog.get_children(): process_catalog(child) - item_links = [] + item_links: List[Link] = [] for item_link in catalog.get_item_links(): item_link.resolve_stac_object(root=self.get_root()) - mapped = item_mapper(item_link.target) + mapped = item_mapper(cast(Item, item_link.target)) if mapped is None: raise Exception('item_mapper cannot return None.') - if type(mapped) is not list: + if isinstance(mapped, Item): item_link.target = mapped item_links.append(item_link) else: @@ -723,7 +741,8 @@ def process_catalog(catalog): process_catalog(new_cat) return new_cat - def map_assets(self, asset_mapper): + def map_assets(self, asset_mapper: Callable[[str, Asset], Union[Asset, Tuple[str, Asset], + Dict[str, Asset]]]): """Creates a copy of a catalog, with each Asset for each Item passed through the asset_mapper function. @@ -737,7 +756,7 @@ def map_assets(self, asset_mapper): Catalog: A full copy of this catalog, with assets manipulated according to the asset_mapper function. """ - def apply_asset_mapper(tup): + def apply_asset_mapper(tup: Tuple[str, Asset]): k, v = tup result = asset_mapper(k, v) if result is None: @@ -749,10 +768,10 @@ def apply_asset_mapper(tup): else: assets = list(result.items()) if len(assets) < 1: - raise Exception('asset_mapper must return a non-empy list') + raise Exception('asset_mapper must return a non-empty list') return assets - def item_mapper(item): + def item_mapper(item: Item): new_assets = [ x for result in map(apply_asset_mapper, item.assets.items()) for x in result ] @@ -761,7 +780,7 @@ def item_mapper(item): return self.map_items(item_mapper) - def describe(self, include_hrefs=False, _indent=0): + def describe(self, include_hrefs: bool = False, _indent: int = 0): """Prints out information about this Catalog and all contained STACObjects. @@ -782,7 +801,10 @@ def describe(self, include_hrefs=False, _indent=0): print(s) @classmethod - def from_dict(cls, d, href=None, root=None): + def from_dict(cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog"] = None) -> "Catalog": catalog_type = CatalogType.determine_type(d) d = deepcopy(d) @@ -801,7 +823,7 @@ def from_dict(cls, d, href=None, root=None): stac_extensions=stac_extensions, extra_fields=d, href=href, - catalog_type=catalog_type) + catalog_type=catalog_type or CatalogType.ABSOLUTE_PUBLISHED) for link in links: if link['rel'] == 'root': diff --git a/pystac/collection.py b/pystac/collection.py index 668658a15..e9cb73b44 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,5 +1,6 @@ -from collections import abc -from datetime import datetime +from datetime import datetime as Datetime +from pystac.item import Item +from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast import dateutil.parser from dateutil import tz from copy import (copy, deepcopy) @@ -9,281 +10,6 @@ from pystac.utils import datetime_to_str -class Collection(Catalog): - """A Collection extends the Catalog spec with additional metadata that helps - enable discovery. - - Args: - id (str): Identifier for the collection. Must be unique within the STAC. - description (str): Detailed multi-line description to fully explain the collection. - `CommonMark 0.28 syntax `_ MAY be used for rich text - representation. - extent (Extent): Spatial and temporal extents that describe the bounds of - all items contained within this Collection. - title (str or None): Optional short descriptive one-line title for the collection. - stac_extensions (List[str]): Optional list of extensions the Collection implements. - href (str or None): Optional HREF for this collection, which be set as the collection's - self link's HREF. - catalog_type (str or None): Optional catalog type for this catalog. Must - be one of the values in :class`~pystac.CatalogType`. - license (str): Collection's license(s) as a `SPDX License identifier - `_, `various`, or `proprietary`. If collection includes - data with multiple different licenses, use `various` and add a link for each. - Defaults to 'proprietary'. - keywords (List[str]): Optional list of keywords describing the collection. - providers (List[Provider]): Optional list of providers of this Collection. - properties (dict): Optional dict of common fields across referenced items. - summaries (dict): An optional map of property summaries, - either a set of values or statistics such as a range. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Collection. - - Attributes: - id (str): Identifier for the collection. - description (str): Detailed multi-line description to fully explain the collection. - extent (Extent): Spatial and temporal extents that describe the bounds of - all items contained within this Collection. - title (str or None): Optional short descriptive one-line title for the collection. - stac_extensions (List[str]): Optional list of extensions the Collection implements. - keywords (List[str] or None): Optional list of keywords describing the collection. - providers (List[Provider] or None): Optional list of providers of this Collection. - properties (dict or None): Optional dict of common fields across referenced items. - summaries (dict or None): An optional map of property summaries, - either a set of values or statistics such as a range. - links (List[Link]): A list of :class:`~pystac.Link` objects representing - all links associated with this Collection. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Catalog. - """ - - STAC_OBJECT_TYPE = STACObjectType.COLLECTION - - DEFAULT_FILE_NAME = "collection.json" - """Default file name that will be given to this STAC object in a cononical format.""" - def __init__(self, - id, - description, - extent, - title=None, - stac_extensions=None, - href=None, - extra_fields=None, - catalog_type=None, - license='proprietary', - keywords=None, - providers=None, - properties=None, - summaries=None): - super(Collection, self).__init__(id, description, title, stac_extensions, extra_fields, - href, catalog_type) - self.extent = extent - self.license = license - - self.stac_extensions = stac_extensions - self.keywords = keywords - self.providers = providers - self.properties = properties - self.summaries = summaries - - def __repr__(self): - return ''.format(self.id) - - def add_item(self, item, title=None): - super(Collection, self).add_item(item, title) - item.set_collection(self) - - def to_dict(self, include_self_link=True): - d = super(Collection, self).to_dict(include_self_link) - d['extent'] = self.extent.to_dict() - d['license'] = self.license - if self.stac_extensions is not None: - d['stac_extensions'] = self.stac_extensions - if self.keywords is not None: - d['keywords'] = self.keywords - if self.providers is not None: - d['providers'] = list(map(lambda x: x.to_dict(), self.providers)) - if self.properties is not None: - d['properties'] = self.properties - if self.summaries is not None: - d['summaries'] = self.summaries - - return d - - def clone(self): - clone = Collection(id=self.id, - description=self.description, - extent=self.extent.clone(), - title=self.title, - stac_extensions=self.stac_extensions, - extra_fields=self.extra_fields, - catalog_type=self.catalog_type, - license=self.license, - keywords=self.keywords, - providers=self.providers, - properties=self.properties, - summaries=self.summaries) - - clone._resolved_objects.cache(clone) - - for link in self.links: - if link.rel == 'root': - # Collection __init__ sets correct root to clone; don't reset - # if the root link points to self - root_is_self = link.is_resolved() and link.target is self - if not root_is_self: - clone.set_root(None) - clone.add_link(link.clone()) - else: - clone.add_link(link.clone()) - - return clone - - @classmethod - def from_dict(cls, d, href=None, root=None): - catalog_type = CatalogType.determine_type(d) - - d = deepcopy(d) - id = d.pop('id') - description = d.pop('description') - license = d.pop('license') - extent = Extent.from_dict(d.pop('extent')) - title = d.get('title') - stac_extensions = d.get('stac_extensions') - keywords = d.get('keywords') - providers = d.get('providers') - if providers is not None: - providers = list(map(lambda x: Provider.from_dict(x), providers)) - properties = d.get('properties') - summaries = d.get('summaries') - links = d.pop('links') - - d.pop('stac_version') - - collection = Collection(id=id, - description=description, - extent=extent, - title=title, - stac_extensions=stac_extensions, - extra_fields=d, - license=license, - keywords=keywords, - providers=providers, - properties=properties, - summaries=summaries, - href=href, - catalog_type=catalog_type) - - for link in links: - if link['rel'] == 'root': - # Remove the link that's generated in Catalog's constructor. - collection.remove_links('root') - - if link['rel'] != 'self' or href is None: - collection.add_link(Link.from_dict(link)) - - return collection - - def update_extent_from_items(self): - """ - Update datetime and bbox based on all items to a single bbox and time window. - """ - self.extent = Extent.from_items(self.get_all_items()) - - -class Extent: - """Describes the spatio-temporal extents of a Collection. - - Args: - spatial (SpatialExtent): Potential spatial extent covered by the collection. - temporal (TemporalExtent): Potential temporal extent covered by the collection. - - Attributes: - spatial (SpatialExtent): Potential spatial extent covered by the collection. - temporal (TemporalExtent): Potential temporal extent covered by the collection. - """ - def __init__(self, spatial, temporal): - self.spatial = spatial - self.temporal = temporal - - def to_dict(self): - """Generate a dictionary representing the JSON of this Extent. - - Returns: - dict: A serializion of the Extent that can be written out as JSON. - """ - d = {'spatial': self.spatial.to_dict(), 'temporal': self.temporal.to_dict()} - - return d - - def clone(self): - """Clones this object. - - Returns: - Extent: The clone of this extent. - """ - return Extent(spatial=copy(self.spatial), temporal=copy(self.temporal)) - - @staticmethod - def from_dict(d): - """Constructs an Extent from a dict. - - Returns: - Extent: The Extent deserialized from the JSON dict. - """ - - # Handle pre-0.8 spatial extents - spatial_extent_dict = d['spatial'] - if isinstance(spatial_extent_dict, list): - spatial_extent_dict = {'bbox': [spatial_extent_dict]} - - # Handle pre-0.8 temporal extents - temporal_extent_dict = d['temporal'] - if isinstance(temporal_extent_dict, list): - temporal_extent_dict = {'interval': [temporal_extent_dict]} - - return Extent(SpatialExtent.from_dict(spatial_extent_dict), - TemporalExtent.from_dict(temporal_extent_dict)) - - @staticmethod - def from_items(items): - """Create an Extent based on the datetimes and bboxes of a list of items. - - Args: - items (List[Item]): A list of items to derive the extent from. - - Returns: - Extent: An Extent that spatially and temporally covers all of the - given items. - """ - def extract_extent_props(item): - return item.bbox + [ - item.datetime, item.common_metadata.start_datetime, - item.common_metadata.end_datetime - ] - - xmins, ymins, xmaxs, ymaxs, datetimes, starts, ends = zip(*map(extract_extent_props, items)) - - if not any(datetimes + starts): - start_timestamp = None - else: - start_timestamp = min([ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in filter(None, datetimes + starts) - ]) - if not any(datetimes + ends): - end_timestamp = None - else: - end_timestamp = max([ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in filter(None, datetimes + ends) - ]) - - spatial = SpatialExtent([[min(xmins), min(ymins), max(xmaxs), max(ymaxs)]]) - temporal = TemporalExtent([[start_timestamp, end_timestamp]]) - - return Extent(spatial, temporal) - - class SpatialExtent: """Describes the spatial extent of a Collection. @@ -299,34 +25,33 @@ class SpatialExtent: array must be 2*n where n is the number of dimensions. For example, a 2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]] """ - def __init__(self, bboxes): + def __init__(self, bboxes: Union[List[List[float]], List[float]]) -> None: # 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 + if isinstance(bboxes, list) and isinstance(bboxes[0], float): + self.bboxes: List[List[float]] = [cast(List[float], bboxes)] + else: + self.bboxes: List[List[float]] = cast(List[List[float]], bboxes) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this SpatialExtent. Returns: - dict: A serializion of the SpatialExtent that can be written out as JSON. + dict: A serialization of the SpatialExtent that can be written out as JSON. """ d = {'bbox': self.bboxes} return d - def clone(self): + def clone(self) -> "SpatialExtent": """Clones this object. Returns: SpatialExtent: The clone of this object. """ - return SpatialExtent(self.bboxes) + return SpatialExtent(deepcopy(self.bboxes)) @staticmethod - def from_dict(d): + def from_dict(d: Dict[str, Any]) -> "SpatialExtent": """Constructs an SpatialExtent from a dict. Returns: @@ -335,7 +60,7 @@ def from_dict(d): return SpatialExtent(bboxes=d['bbox']) @staticmethod - def from_coordinates(coordinates): + def from_coordinates(coordinates: List[Any]) -> "SpatialExtent": """Constructs a SpatialExtent from a set of coordinates. This method will only produce a single bbox that covers all points @@ -348,9 +73,15 @@ def from_coordinates(coordinates): SpatialExtent: A SpatialExtent with a single bbox that covers the given coordinates. """ - def process_coords(link, xmin=None, ymin=None, xmax=None, ymax=None): - for coord in link: - if type(coord[0]) is list: + def process_coords( + coord_lists: List[Any], + xmin: Optional[float] = None, + ymin: Optional[float] = None, + xmax: Optional[float] = None, + ymax: Optional[float] = None + ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]: + for coord in coord_lists: + if isinstance(coord[0], list): xmin, ymin, xmax, ymax = process_coords(coord, xmin, ymin, xmax, ymax) else: x, y = coord @@ -366,6 +97,9 @@ def process_coords(link, xmin=None, ymin=None, xmax=None, ymax=None): xmin, ymin, xmax, ymax = process_coords(coordinates) + if xmin is None or ymin is None or xmax is None or ymax is None: + raise ValueError(f"Could not determine bounds from coordinate sequence {coordinates}") + return SpatialExtent([[xmin, ymin, xmax, ymax]]) @@ -388,23 +122,22 @@ class TemporalExtent: Note: Datetimes are required to be in UTC. """ - def __init__(self, intervals): + def __init__(self, intervals: Union[List[List[Optional[Datetime]]], List[Optional[Datetime]]]): # 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] - - self.intervals = intervals + if isinstance(intervals, list) and isinstance(intervals[0], Datetime): + self.intervals = [cast(List[Optional[Datetime]], intervals)] + else: + self.intervals = cast(List[List[Optional[Datetime]]], intervals) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this TemporalExtent. Returns: - dict: A serializion of the TemporalExtent that can be written out as JSON. + dict: A serialization of the TemporalExtent that can be written out as JSON. """ - encoded_intervals = [] + encoded_intervals: List[List[Optional[str]]] = [] for i in self.intervals: start = None end = None @@ -420,22 +153,22 @@ def to_dict(self): d = {'interval': encoded_intervals} return d - def clone(self): + def clone(self) -> "TemporalExtent": """Clones this object. Returns: TemporalExtent: The clone of this object. """ - return TemporalExtent(intervals=copy(self.intervals)) + return TemporalExtent(intervals=deepcopy(self.intervals)) @staticmethod - def from_dict(d): + def from_dict(d: Dict[str, Any]) -> "TemporalExtent": """Constructs an TemporalExtent from a dict. Returns: TemporalExtent: The TemporalExtent deserialized from the JSON dict. """ - parsed_intervals = [] + parsed_intervals: List[List[Optional[Datetime]]] = [] for i in d['interval']: start = None end = None @@ -449,14 +182,112 @@ def from_dict(d): return TemporalExtent(intervals=parsed_intervals) @staticmethod - def from_now(): + def from_now() -> "TemporalExtent": """Constructs an TemporalExtent with a single open interval that has the start time as the current time. Returns: TemporalExtent: The resulting TemporalExtent. """ - return TemporalExtent(intervals=[[datetime.utcnow().replace(microsecond=0), None]]) + return TemporalExtent(intervals=[[Datetime.utcnow().replace(microsecond=0), None]]) + + +class Extent: + """Describes the spatiotemporal extents of a Collection. + + Args: + spatial (SpatialExtent): Potential spatial extent covered by the collection. + temporal (TemporalExtent): Potential temporal extent covered by the collection. + + Attributes: + spatial (SpatialExtent): Potential spatial extent covered by the collection. + temporal (TemporalExtent): Potential temporal extent covered by the collection. + """ + def __init__(self, spatial: SpatialExtent, temporal: TemporalExtent): + self.spatial = spatial + self.temporal = temporal + + def to_dict(self) -> Dict[str, Any]: + """Generate a dictionary representing the JSON of this Extent. + + Returns: + dict: A serialization of the Extent that can be written out as JSON. + """ + d = {'spatial': self.spatial.to_dict(), 'temporal': self.temporal.to_dict()} + + return d + + def clone(self) -> "Extent": + """Clones this object. + + Returns: + Extent: The clone of this extent. + """ + return Extent(spatial=copy(self.spatial), temporal=copy(self.temporal)) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "Extent": + """Constructs an Extent from a dict. + + Returns: + Extent: The Extent deserialized from the JSON dict. + """ + + # Handle pre-0.8 spatial extents + spatial_extent = d['spatial'] + if isinstance(spatial_extent, list): + spatial_extent_dict: Dict[str, Any] = {'bbox': [spatial_extent]} + else: + spatial_extent_dict = spatial_extent + + # Handle pre-0.8 temporal extents + temporal_extent = d['temporal'] + if isinstance(temporal_extent, list): + temporal_extent_dict: Dict[str, Any] = {'interval': [temporal_extent]} + else: + temporal_extent_dict = temporal_extent + + return Extent(SpatialExtent.from_dict(spatial_extent_dict), + TemporalExtent.from_dict(temporal_extent_dict)) + + @staticmethod + def from_items(items: Iterable[Item]) -> "Extent": + """Create an Extent based on the datetimes and bboxes of a list of items. + + Args: + items (List[Item]): A list of items to derive the extent from. + + Returns: + Extent: An Extent that spatially and temporally covers all of the + given items. + """ + def extract_extent_props(item: Item) -> List[Any]: + return item.bbox + [ + item.datetime, item.common_metadata.start_datetime, + item.common_metadata.end_datetime + ] # type:ignore + + xmins, ymins, xmaxs, ymaxs, datetimes, starts, ends = zip(*map(extract_extent_props, items)) + + if not any(datetimes + starts): + start_timestamp = None + else: + start_timestamp = min([ + dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) + for dt in filter(None, datetimes + starts) + ]) + if not any(datetimes + ends): + end_timestamp = None + else: + end_timestamp = max([ + dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) + for dt in filter(None, datetimes + ends) + ]) + + spatial = SpatialExtent([[min(xmins), min(ymins), max(xmaxs), max(ymaxs)]]) + temporal = TemporalExtent([[start_timestamp, end_timestamp]]) + + return Extent(spatial, temporal) class Provider: @@ -485,19 +316,23 @@ class Provider: url (str): Optional homepage on which the provider describes the dataset and publishes contact information. """ - def __init__(self, name, description=None, roles=None, url=None): + def __init__(self, + name: str, + description: Optional[str] = None, + roles: Optional[List[str]] = None, + url: Optional[str] = None): self.name = name self.description = description self.roles = roles self.url = url - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this Provider. Returns: - dict: A serializion of the Provider that can be written out as JSON. + dict: A serialization of the Provider that can be written out as JSON. """ - d = {'name': self.name} + d: Dict[str, Any] = {'name': self.name} if self.description is not None: d['description'] = self.description if self.roles is not None: @@ -508,7 +343,7 @@ def to_dict(self): return d @staticmethod - def from_dict(d): + def from_dict(d: Dict[str, Any]) -> "Provider": """Constructs an Provider from a dict. Returns: @@ -518,3 +353,187 @@ def from_dict(d): description=d.get('description'), roles=d.get('roles'), url=d.get('url')) + + +class Collection(Catalog): + """A Collection extends the Catalog spec with additional metadata that helps + enable discovery. + + Args: + id (str): Identifier for the collection. Must be unique within the STAC. + description (str): Detailed multi-line description to fully explain the collection. + `CommonMark 0.28 syntax `_ MAY be used for rich text + representation. + extent (Extent): Spatial and temporal extents that describe the bounds of + all items contained within this Collection. + title (str or None): Optional short descriptive one-line title for the collection. + stac_extensions (List[str]): Optional list of extensions the Collection implements. + href (str or None): Optional HREF for this collection, which be set as the collection's + self link's HREF. + catalog_type (str or None): Optional catalog type for this catalog. Must + be one of the values in :class`~pystac.CatalogType`. + license (str): Collection's license(s) as a `SPDX License identifier + `_, `various`, or `proprietary`. If collection includes + data with multiple different licenses, use `various` and add a link for each. + Defaults to 'proprietary'. + keywords (List[str]): Optional list of keywords describing the collection. + providers (List[Provider]): Optional list of providers of this Collection. + properties (dict): Optional dict of common fields across referenced items. + summaries (dict): An optional map of property summaries, + either a set of values or statistics such as a range. + extra_fields (dict or None): Extra fields that are part of the top-level JSON properties + of the Collection. + + Attributes: + id (str): Identifier for the collection. + description (str): Detailed multi-line description to fully explain the collection. + extent (Extent): Spatial and temporal extents that describe the bounds of + all items contained within this Collection. + title (str or None): Optional short descriptive one-line title for the collection. + stac_extensions (List[str]): Optional list of extensions the Collection implements. + keywords (List[str] or None): Optional list of keywords describing the collection. + providers (List[Provider] or None): Optional list of providers of this Collection. + properties (dict or None): Optional dict of common fields across referenced items. + summaries (dict or None): An optional map of property summaries, + either a set of values or statistics such as a range. + links (List[Link]): A list of :class:`~pystac.Link` objects representing + all links associated with this Collection. + extra_fields (dict or None): Extra fields that are part of the top-level JSON properties + of the Catalog. + """ + + STAC_OBJECT_TYPE = STACObjectType.COLLECTION + + DEFAULT_FILE_NAME = "collection.json" + """Default file name that will be given to this STAC object in a canonical format.""" + def __init__(self, + id: str, + description: str, + extent: Extent, + title: Optional[str] = None, + stac_extensions: Optional[List[str]] = None, + href: Optional[str] = None, + extra_fields: Optional[Dict[str, Any]] = None, + catalog_type: Optional[CatalogType] = None, + license: str = 'proprietary', + keywords: Optional[List[str]] = None, + providers: Optional[List[Provider]] = None, + properties: Optional[Dict[str, Any]] = None, + summaries: Optional[Dict[str, Any]] = None): + super(Collection, self).__init__(id, description, title, stac_extensions, extra_fields, + href, catalog_type or CatalogType.ABSOLUTE_PUBLISHED) + self.extent = extent + self.license = license + + self.stac_extensions = stac_extensions + self.keywords = keywords + self.providers = providers + self.properties = properties + self.summaries = summaries + + def __repr__(self) -> str: + return ''.format(self.id) + + def add_item(self, item: Item, title: Optional[str] = None) -> None: + super(Collection, self).add_item(item, title) + item.set_collection(self) + + def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: + d = super(Collection, self).to_dict(include_self_link) + d['extent'] = self.extent.to_dict() + d['license'] = self.license + if self.stac_extensions is not None: + d['stac_extensions'] = self.stac_extensions + if self.keywords is not None: + d['keywords'] = self.keywords + if self.providers is not None: + d['providers'] = list(map(lambda x: x.to_dict(), self.providers)) + if self.properties is not None: + d['properties'] = self.properties + if self.summaries is not None: + d['summaries'] = self.summaries + + return d + + def clone(self): + clone = Collection(id=self.id, + description=self.description, + extent=self.extent.clone(), + title=self.title, + stac_extensions=self.stac_extensions, + extra_fields=self.extra_fields, + catalog_type=self.catalog_type, + license=self.license, + keywords=self.keywords, + providers=self.providers, + properties=self.properties, + summaries=self.summaries) + + clone._resolved_objects.cache(clone) + + for link in self.links: + if link.rel == 'root': + # Collection __init__ sets correct root to clone; don't reset + # if the root link points to self + root_is_self = link.is_resolved() and link.target is self + if not root_is_self: + clone.set_root(None) + clone.add_link(link.clone()) + else: + clone.add_link(link.clone()) + + return clone + + @classmethod + def from_dict(cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None) -> "Collection": + catalog_type = CatalogType.determine_type(d) + + d = deepcopy(d) + id = d.pop('id') + description = d.pop('description') + license = d.pop('license') + extent = Extent.from_dict(d.pop('extent')) + title = d.get('title') + stac_extensions = d.get('stac_extensions') + keywords = d.get('keywords') + providers = d.get('providers') + if providers is not None: + providers = list(map(lambda x: Provider.from_dict(x), providers)) + properties = d.get('properties') + summaries = d.get('summaries') + links = d.pop('links') + + d.pop('stac_version') + + collection = Collection(id=id, + description=description, + extent=extent, + title=title, + stac_extensions=stac_extensions, + extra_fields=d, + license=license, + keywords=keywords, + providers=providers, + properties=properties, + summaries=summaries, + href=href, + catalog_type=catalog_type) + + for link in links: + if link['rel'] == 'root': + # Remove the link that's generated in Catalog's constructor. + collection.remove_links('root') + + if link['rel'] != 'self' or href is None: + collection.add_link(Link.from_dict(link)) + + return collection + + def update_extent_from_items(self): + """ + Update datetime and bbox based on all items to a single bbox and time window. + """ + self.extent = Extent.from_items(self.get_all_items()) diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index e053f8f8a..a2fd8513d 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,14 +1,35 @@ from abc import (ABC, abstractmethod) +from typing import Any, Iterable, List, Optional, Type +from pystac.stac_object import STACObject from pystac.catalog import Catalog from pystac.collection import Collection -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions import ExtensionError +class STACObjectExtension(ABC): + @classmethod + def _from_object(cls, stac_object: STACObject) -> "STACObjectExtension": + ... + + @classmethod + @abstractmethod + def _object_links(cls) -> List[str]: + raise NotImplementedError("_object_links") + + @classmethod + def enable_extension(cls, stac_object: STACObject) -> None: + """Enables the extension for the given stac_object. + Child classes can choose to override this method in order to + modify the stac_object when an extension is enabled. + """ + pass + + class ExtendedObject: """ExtendedObject maps STACObject classes (Catalog, Collection and Item) to - extension classes (classes that implement one of CatalogExtension, CollectionExtesion, + extension classes (classes that implement one of CatalogExtension, CollectionExtension, or ItemCollection). When an extension is registered with PySTAC it uses the registered list of ExtendedObject to determine how to handle extending objects, e.g. when item.ext.label is called, it searches for the ExtendedObject associated with the label extension that @@ -18,7 +39,8 @@ class ExtendedObject: stac_object_class: The STAC object class that is being extended. extension_class: The class of the extension, e.g. LabelItemExt """ - def __init__(self, stac_object_class, extension_class): + def __init__(self, stac_object_class: Type[STACObject], + extension_class: Type[STACObjectExtension]): if stac_object_class is Catalog: if not issubclass(extension_class, CatalogExtension): raise ExtensionError( @@ -45,83 +67,56 @@ class ExtensionDefinition: extended_objects (List[ExtendedObject]): The list of ExtendedObjects which map STACObject types to their extension. Should only contain one entry per stac object type. """ - def __init__(self, extension_id, extended_objects): + def __init__(self, extension_id: str, extended_objects: List[ExtendedObject]): self.extension_id = extension_id self.extended_objects = extended_objects -class CatalogExtension(ABC): +class CatalogExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object): + def _from_object(cls, stac_object: STACObject) -> "CatalogExtension": + if not isinstance(stac_object, Catalog): + raise ValueError(f"This extension applies to Catalogs, not {cls}") return cls.from_catalog(stac_object) @classmethod @abstractmethod - def from_catalog(cls, catalog): + def from_catalog(cls, catalog: Catalog) -> "CatalogExtension": raise NotImplementedError("from_catalog") - @classmethod - @abstractmethod - def _object_links(cls): - raise NotImplementedError("_object_links") - - @classmethod - def enable_extension(cls, stac_object): - """Enables the extension for the given stac_object. - Child classes can choose to override this method in order to - modify the stac_object when an extension is enabled. - """ - pass - -class CollectionExtension(ABC): +class CollectionExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object): + def _from_object(cls, stac_object: STACObject) -> "CollectionExtension": + if not isinstance(stac_object, Collection): + raise ValueError(f"This extension applies to Collections, not {cls}") return cls.from_collection(stac_object) @classmethod @abstractmethod - def from_collection(cls, catalog): + def from_collection(cls, collection: Collection) -> "CollectionExtension": raise NotImplementedError("from_collection") - @classmethod - @abstractmethod - def _object_links(cls): - raise NotImplementedError("_object_links") - - @classmethod - def enable_extension(cls, stac_object): - """Enables the extension for the given stac_object. - Child classes can choose to override this method in order to - modify the stac_object when an extension is enabled. - """ - pass +class ItemExtension(STACObjectExtension): + item: Item -class ItemExtension(ABC): @classmethod - def _from_object(cls, stac_object): + def _from_object(cls, stac_object: STACObject) -> "ItemExtension": + if not isinstance(stac_object, Item): + raise ValueError(f"This extension applies to Items, not {cls}") return cls.from_item(stac_object) @classmethod @abstractmethod - def from_item(cls, item): + def from_item(cls, item: Item) -> "ItemExtension": raise NotImplementedError("from_item") - @classmethod - @abstractmethod - def _object_links(cls): - raise NotImplementedError("_object_links") - - @classmethod - def enable_extension(cls, stac_object): - """Enables the extension for the given stac_object. - Child classes can choose to override this method in order to - modify the stac_object when an extension is enabled. - """ - pass - - def _set_property(self, key, value, asset): + def _set_property(self, + key: str, + value: Any, + asset: Optional[Asset], + pop_if_none: bool = True) -> None: ''' Set an Item or an Asset property. @@ -138,6 +133,11 @@ def _set_property(self, key, value, asset): key (str): The name of the property value (Object): the value to set asset: The Asset to modify. If None, the property will be set in the Item + pop_if_none: If True and the value is None, the property will be removed from + the item or asset properties. If this is False, a None value will be set + (which will translate into a null JSON value). There are some cases + where required properties can be nullable, in which case False should + be used. Defaults to True. ''' target = self.item.properties if asset is None else asset.properties if value is None: @@ -147,32 +147,34 @@ def _set_property(self, key, value, asset): class RegisteredSTACExtensions: - def __init__(self, extension_definitions): + def __init__(self, extension_definitions: Iterable[ExtensionDefinition]): self.extensions = dict([(e.extension_id, e) for e in extension_definitions]) - def is_registered_extension(self, extension_id): + def is_registered_extension(self, extension_id: str) -> bool: """Determines whether or not the given extension ID has been registered.""" return extension_id in self.extensions - def get_registered_extensions(self): + def get_registered_extensions(self) -> List[str]: """Returns the list of registered extension IDs.""" return list(self.extensions.keys()) - def add_extension(self, extension_definition): + def add_extension(self, extension_definition: ExtensionDefinition) -> None: e_id = extension_definition.extension_id if e_id in self.extensions: raise ExtensionError("ExtensionDefinition with id '{}' already exists.".format(e_id)) self.extensions[e_id] = extension_definition - def remove_extension(self, extension_id): + def remove_extension(self, extension_id: str) -> None: """Remove an extension from PySTAC.""" if extension_id not in self.extensions: raise ExtensionError( "ExtensionDefinition with id '{}' is not registered.".format(extension_id)) del self.extensions[extension_id] - def get_extension_class(self, extension_id, stac_object_class): + def get_extension_class( + self, extension_id: str, + stac_object_class: Type[STACObject]) -> Optional[Type[STACObjectExtension]]: """Gets the extension class for a given stac object class if one exists, otherwise returns None """ @@ -207,7 +209,7 @@ def get_extension_class(self, extension_id, stac_object_class): return ext_class - def extend_object(self, extension_id, stac_object): + def extend_object(self, extension_id: str, stac_object: STACObject) -> STACObjectExtension: """Returns the extension object for the given STACObject and the given extension_id """ @@ -219,7 +221,7 @@ def extend_object(self, extension_id, stac_object): return ext_class._from_object(stac_object) - def get_extended_object_links(self, stac_object): + def get_extended_object_links(self, stac_object: STACObject) -> List[str]: if stac_object.stac_extensions is None: return [] return [ @@ -229,7 +231,7 @@ def get_extended_object_links(self, stac_object): for link_rel in e_obj.extension_class._object_links() ] - def can_extend(self, extension_id, stac_object_class): + def can_extend(self, extension_id: str, stac_object_class: Type[STACObject]) -> bool: """Returns True if the extension can extend the given object type. Args: @@ -252,7 +254,7 @@ def can_extend(self, extension_id, stac_object_class): if issubclass(stac_object_class, e.stac_object_class) ]) - def enable_extension(self, extension_id, stac_object): + def enable_extension(self, extension_id: str, stac_object: STACObject) -> None: """Enables the extension for the given object. This will at least ensure the extension ID is in the object's "stac_extensions" diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index c0451a7c9..c31fc55b5 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -1,154 +1,23 @@ +from typing import Any, Dict, List, Optional, Tuple, cast from pystac import Extensions -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) -class EOItemExt(ItemExtension): - """EOItemExt is the extension of the Item in the eo extension which - represents a snapshot of the earth for a single date and time. - - Args: - item (Item): The item to be extended. - - Attributes: - item (Item): The Item that is being extended. - - Note: - Using EOItemExt to directly wrap an item will add the 'eo' extension ID to - the item's stac_extensions. - """ - def __init__(self, item): - if item.stac_extensions is None: - item.stac_extensions = [Extensions.EO] - elif Extensions.EO not in item.stac_extensions: - item.stac_extensions.append(Extensions.EO) - - self.item = item - - def apply(self, bands, cloud_cover=None): - """Applies label extension properties to the extended Item. - - Args: - bands (List[Band]): a list of :class:`~pystac.Band` objects that represent - the available bands. - cloud_cover (float or None): The estimate of cloud cover as a percentage (0-100) of the - entire scene. If not available the field should not be provided. - """ - self.bands = bands - self.cloud_cover = cloud_cover - - @property - def bands(self): - """Get or sets a list of :class:`~pystac.Band` objects that represent - the available bands. - - Returns: - List[Band] - """ - return self.get_bands() - - @bands.setter - def bands(self, v): - self.set_bands(v) - - 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 or - all the asset's eo bands - - Returns: - List[Band] - """ - if asset is not None and 'eo:bands' in asset.properties: - bands = asset.properties.get('eo:bands') - 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] - - return bands - - def set_bands(self, bands, asset=None): - """Set an Item or an Asset bands. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - band_dicts = [b.to_dict() for b in bands] - self._set_property('eo:bands', band_dicts, asset) - - @property - def cloud_cover(self): - """Get or sets the estimate of cloud cover as a percentage (0-100) of the - entire scene. If not available the field should not be provided. - - Returns: - float or None - """ - return self.get_cloud_cover() - - @cloud_cover.setter - def cloud_cover(self, v): - self.set_cloud_cover(v) - - def get_cloud_cover(self, asset=None): - """Gets an Item or an Asset cloud_cover. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - float - """ - if asset is None or 'eo:cloud_cover' not in asset.properties: - return self.item.properties.get('eo:cloud_cover') - else: - return asset.properties.get('eo:cloud_cover') - - def set_cloud_cover(self, cloud_cover, asset=None): - """Set an Item or an Asset cloud_cover. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('eo:cloud_cover', cloud_cover, asset) - - def __repr__(self): - return ''.format(self.item.id) - - @classmethod - def _object_links(cls): - return [] - - @classmethod - def from_item(cls, item): - return cls(item) - - class Band: """Represents Band information attached to an Item that implements the eo extension. Use Band.create to create a new Band. """ - def __init__(self, properties): + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties def apply(self, - name, - common_name=None, - description=None, - center_wavelength=None, - full_width_half_max=None): + name: str, + common_name: Optional[str] = None, + description: Optional[str] = None, + center_wavelength: Optional[float] = None, + full_width_half_max: Optional[float] = None): """ Sets the properties for this Band. @@ -170,11 +39,11 @@ def apply(self, @classmethod def create(cls, - name, - common_name=None, - description=None, - center_wavelength=None, - full_width_half_max=None): + name: str, + common_name: Optional[str] = None, + description: Optional[str] = None, + center_wavelength: Optional[float] = None, + full_width_half_max: Optional[float] = None): """ Creates a new band. @@ -197,38 +66,38 @@ def create(cls, return b @property - def name(self): + def name(self) -> str: """Get or sets the name of the band (e.g., "B01", "B02", "B1", "B5", "QA"). Returns: str """ - return self.properties.get('name') + return self.properties['name'] @name.setter - def name(self, v): + def name(self, v: str) -> None: self.properties['name'] = v @property - def common_name(self): + def common_name(self) -> Optional[str]: """Get or sets the name commonly used to refer to the band to make it easier to search for bands across instruments. See the `list of accepted common names `_. Returns: - str + Optional[str] """ return self.properties.get('common_name') @common_name.setter - def common_name(self, v): + def common_name(self, v: Optional[str]) -> None: if v is not None: self.properties['common_name'] = v else: self.properties.pop('common_name', None) @property - def description(self): + def description(self) -> Optional[str]: """Get or sets the description to fully explain the band. CommonMark 0.29 syntax MAY be used for rich text representation. @@ -238,14 +107,14 @@ def description(self): return self.properties.get('description') @description.setter - def description(self, v): + def description(self, v: Optional[str]) -> None: if v is not None: self.properties['description'] = v else: self.properties.pop('description', None) @property - def center_wavelength(self): + def center_wavelength(self) -> Optional[float]: """Get or sets the center wavelength of the band, in micrometers (μm). Returns: @@ -254,14 +123,14 @@ def center_wavelength(self): return self.properties.get('center_wavelength') @center_wavelength.setter - def center_wavelength(self, v): + def center_wavelength(self, v: Optional[float]) -> None: if v is not None: self.properties['center_wavelength'] = v else: self.properties.pop('center_wavelength', None) @property - def full_width_half_max(self): + def full_width_half_max(self) -> Optional[float]: """Get or sets the full width at half maximum (FWHM). The width of the band, as measured at half the maximum transmission, in micrometers (μm). @@ -271,16 +140,16 @@ def full_width_half_max(self): return self.properties.get('full_width_half_max') @full_width_half_max.setter - def full_width_half_max(self, v): + def full_width_half_max(self, v: Optional[float]) -> None: if v is not None: self.properties['full_width_half_max'] = v else: self.properties.pop('full_width_half_max', None) - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.name) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this Band. Returns: @@ -289,7 +158,7 @@ def to_dict(self): return self.properties @staticmethod - def band_range(common_name): + def band_range(common_name: str) -> Optional[Tuple[float, float]]: """Gets the band range for a common band name. Args: @@ -321,7 +190,7 @@ def band_range(common_name): return name_to_range.get(common_name) @staticmethod - def band_description(common_name): + def band_description(common_name: str) -> Optional[str]: """Returns a description of the band for one with a common name. Args: @@ -337,4 +206,138 @@ def band_description(common_name): return r +class EOItemExt(ItemExtension): + """EOItemExt is the extension of the Item in the eo extension which + represents a snapshot of the earth for a single date and time. + + Args: + item (Item): The item to be extended. + + Attributes: + item (Item): The Item that is being extended. + + Note: + Using EOItemExt to directly wrap an item will add the 'eo' extension ID to + the item's stac_extensions. + """ + def __init__(self, item: Item) -> None: + if item.stac_extensions is None: + item.stac_extensions = [str(Extensions.EO)] + elif str(Extensions.EO) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.EO)) + + self.item = item + + def apply(self, bands: List[Band], cloud_cover: Optional[float] = None): + """Applies label extension properties to the extended Item. + + Args: + bands (List[Band]): a list of :class:`~pystac.Band` objects that represent + the available bands. + cloud_cover (float or None): The estimate of cloud cover as a percentage (0-100) of the + entire scene. If not available the field should not be provided. + """ + self.bands = bands + self.cloud_cover = cloud_cover + + @property + def bands(self) -> Optional[List[Band]]: + """Get or sets a list of :class:`~pystac.Band` objects that represent + the available bands. + + Returns: + List[Band] + """ + return self.get_bands() + + @bands.setter + def bands(self, v: List[Band]) -> None: + self.set_bands(v) + + def get_bands(self, asset: Optional[Asset] = None) -> Optional[List[Band]]: + """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 or + all the asset's eo bands + + Returns: + List[Band] + """ + bands: Optional[List[Dict[str, Any]]] = None + if asset is not None and 'eo:bands' in asset.properties: + bands = asset.properties.get('eo:bands') + 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: + asset_bands: List[Dict[str, Any]] = [] + for _, value in self.item.get_assets().items(): + if 'eo:bands' in value.properties: + asset_bands.extend(cast(List[Dict[str, Any]], value.properties.get('eo:bands'))) + if any(asset_bands): + bands = asset_bands + + if bands is not None: + return [Band(b) for b in bands] + return None + + def set_bands(self, bands: List[Band], asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset bands. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + band_dicts = [b.to_dict() for b in bands] + self._set_property('eo:bands', band_dicts, asset) + + @property + def cloud_cover(self) -> Optional[float]: + """Get or sets the estimate of cloud cover as a percentage (0-100) of the + entire scene. If not available the field should not be provided. + + Returns: + float or None + """ + return self.get_cloud_cover() + + @cloud_cover.setter + def cloud_cover(self, v: Optional[float]) -> None: + self.set_cloud_cover(v) + + def get_cloud_cover(self, asset: Optional[Asset] = None) -> Optional[float]: + """Gets an Item or an Asset cloud_cover. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + float + """ + if asset is None or 'eo:cloud_cover' not in asset.properties: + return self.item.properties.get('eo:cloud_cover') + else: + return asset.properties.get('eo:cloud_cover') + + def set_cloud_cover(self, cloud_cover: Optional[float], asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset cloud_cover. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + self._set_property('eo:cloud_cover', cloud_cover, asset) + + def __repr__(self) -> str: + return ''.format(self.item.id) + + @classmethod + def _object_links(cls) -> List[str]: + return [] + + @classmethod + def from_item(cls, item: Item) -> "EOItemExt": + return cls(item) + + EO_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.EO, [ExtendedObject(Item, EOItemExt)]) diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 6443a2e55..35cf94b00 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,7 +1,8 @@ import enum +from typing import Any, List, Optional from pystac import Extensions -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) @@ -38,15 +39,19 @@ class FileItemExt(ItemExtension): Using FileItemExt to directly wrap an item will add the 'file' extension ID to the item's stac_extensions. """ - def __init__(self, item): + def __init__(self, item: Item) -> None: if item.stac_extensions is None: - item.stac_extensions = [Extensions.FILE] - elif Extensions.FILE not in item.stac_extensions: - item.stac_extensions.append(Extensions.FILE) + item.stac_extensions = [str(Extensions.FILE)] + elif str(Extensions.FILE) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.FILE)) self.item = item - def apply(self, data_type=None, size=None, nodata=None, checksum=None): + def apply(self, + data_type: Optional[FileDataType] = None, + size: Optional[int] = None, + nodata: Optional[List[Any]] = None, + checksum: Optional[str] = None): """Applies file extension properties to the extended Item. Args: @@ -61,7 +66,7 @@ def apply(self, data_type=None, size=None, nodata=None, checksum=None): self.nodata = nodata self.checksum = checksum - def _set_property(self, key, value, asset): + def _set_property(self, key: str, value: Any, asset: Optional[Asset]) -> None: target = self.item.properties if asset is None else asset.properties if value is None: target.pop(key, None) @@ -69,7 +74,7 @@ def _set_property(self, key, value, asset): target[key] = value @property - def data_type(self): + def data_type(self) -> Optional[FileDataType]: """Get or sets the data_type of the file. Returns: @@ -78,10 +83,10 @@ def data_type(self): return self.get_data_type() @data_type.setter - def data_type(self, v): + def data_type(self, v: Optional[FileDataType]) -> None: self.set_data_type(v) - def get_data_type(self, asset=None): + def get_data_type(self, asset: Optional[Asset] = None) -> Optional[FileDataType]: """Gets an Item or an Asset data_type. If an Asset is supplied and the data_type property exists on the Asset, @@ -98,7 +103,9 @@ def get_data_type(self, asset=None): if data_type is not None: return FileDataType(data_type) - def set_data_type(self, data_type, asset=None): + def set_data_type(self, + data_type: Optional[FileDataType], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset data_type. If an Asset is supplied, sets the property on the Asset. @@ -107,7 +114,7 @@ def set_data_type(self, data_type, asset=None): self._set_property('file:data_type', data_type.value, asset) @property - def size(self): + def size(self) -> Optional[int]: """Get or sets the size in bytes of the file Returns: @@ -116,24 +123,24 @@ def size(self): return self.get_size() @size.setter - def size(self, v): + def size(self, v: Optional[int]) -> None: self.set_size(v) - def get_size(self, asset=None): + def get_size(self, asset: Optional[Asset]=None) -> Optional[int]: """Gets an Item or an Asset file size. If an Asset is supplied and the Item property exists on the Asset, returns the Asset's value. Otherwise returns the Item's value Returns: - float + int """ if asset is None or 'file:size' not in asset.properties: return self.item.properties.get('file:size') else: return asset.properties.get('file:size') - def set_size(self, size, asset=None): + def set_size(self, size: Optional[int], asset: Optional[Asset]=None) -> None: """Set an Item or an Asset size. If an Asset is supplied, sets the property on the Asset. @@ -142,19 +149,15 @@ def set_size(self, size, asset=None): self._set_property('file:size', size, asset) @property - def nodata(self): - """Get or sets the no data values - - Returns: - int or None - """ + def nodata(self) -> Optional[List[Any]]: + """Get or sets the no data values""" return self.get_nodata() @nodata.setter - def nodata(self, v): + def nodata(self, v: Optional[List[Any]])-> None: self.set_nodata(v) - def get_nodata(self, asset=None): + def get_nodata(self, asset: Optional[Asset]=None) -> Optional[List[Any]]: """Gets an Item or an Asset nodata values. If an Asset is supplied and the Item property exists on the Asset, @@ -168,7 +171,7 @@ def get_nodata(self, asset=None): else: return asset.properties.get('file:nodata') - def set_nodata(self, nodata, asset=None): + def set_nodata(self, nodata: Optional[List[Any]], asset: Optional[Asset]=None) -> None: """Set an Item or an Asset nodata values. If an Asset is supplied, sets the property on the Asset. @@ -177,7 +180,7 @@ def set_nodata(self, nodata, asset=None): self._set_property('file:nodata', nodata, asset) @property - def checksum(self): + def checksum(self) -> Optional[str]: """Get or sets the checksum Returns: @@ -186,24 +189,21 @@ def checksum(self): return self.get_checksum() @checksum.setter - def checksum(self, v): + def checksum(self, v: Optional[str]) -> None: self.set_checksum(v) - def get_checksum(self, asset=None): + def get_checksum(self, asset: Optional[Asset]=None) -> Optional[str]: """Gets an Item or an Asset checksum. If an Asset is supplied and the Item property exists on the Asset, returns the Asset's value. Otherwise returns the Item's value - - Returns: - list[object] """ if asset is None or 'file:checksum' not in asset.properties: return self.item.properties.get('file:checksum') else: return asset.properties.get('file:checksum') - def set_checksum(self, checksum, asset=None): + def set_checksum(self, checksum: Optional[str], asset: Optional[Asset]=None) -> None: """Set an Item or an Asset checksum. If an Asset is supplied, sets the property on the Asset. @@ -211,15 +211,15 @@ def set_checksum(self, checksum, asset=None): """ self._set_property('file:checksum', checksum, asset) - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.item.id) @classmethod - def _object_links(cls): + def _object_links(cls) -> List[str]: return [] @classmethod - def from_item(cls, item): + def from_item(cls, item: Item) -> "FileItemExt": return cls(item) diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index d4e5e69b3..6abfa3e2a 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -1,6 +1,8 @@ """STAC Model classes for Label extension. """ from enum import Enum +from pystac.media_type import MediaType +from typing import Any, Dict, Iterable, List, Optional, Union, cast from pystac import STACError from pystac.extensions import Extensions @@ -21,365 +23,228 @@ def __str__(self): """Convenience attribute for checking if values are valid label types""" -class LabelItemExt(ItemExtension): - """A LabelItemExt is the extension of the Item in the label extension which - represents a polygon, set of polygons, or raster data defining - labels and label metadata and should be part of a Collection. - - Args: - item (Item): The item to be extended. - - Attributes: - item (Item): The Item that is being extended. - - See: - `Item fields in the label extension spec `_ +class LabelClasses: + """Defines the list of possible class names (e.g., tree, building, car, hippo) - Note: - Using LabelItemExt to directly wrap an item will add the 'label' extension ID to - the item's stac_extensions. - """ # noqa E501 + Use LabelClasses.create to create a new instance of LabelClasses from property values. + """ + def __init__(self, properties: Dict[str, Any]): + self.properties = properties - def __init__(self, item): - if item.stac_extensions is None: - item.stac_extensions = [Extensions.LABEL] - elif Extensions.LABEL not in item.stac_extensions: - item.stac_extensions.append(Extensions.LABEL) + def apply(self, classes: Union[List[str], List[int], List[float]], name: Optional[str] = None): + """Sets the properties for this LabelClasses. - self.item = item + Args: + classes (List[str] or List[int] or List[float]): The different possible class values. + name (str): The property key within the asset's each Feature corresponding to + class labels. If labels are raster-formatted, do not supply; required otherwise. + """ + self.classes = classes + self.name = name - def apply(self, - label_description, - label_type, - label_properties=None, - label_classes=None, - label_tasks=None, - label_methods=None, - label_overviews=None): - """Applies label extension properties to the extended Item. + @classmethod + def create(cls, + classes: Union[List[str], List[int], List[float]], + name: Optional[str] = None) -> "LabelClasses": + """Creates a new LabelClasses. Args: - label_description (str): A description of the label, how it was created, - and what it is recommended for - label_type (str): An ENUM of either vector label type or raster label type. Use - one of :class:`~pystac.LabelType`. - label_properties (list or None): These are the names of the property field(s) in each - Feature of the label asset's FeatureCollection that contains the classes - (keywords from label:classes if the property defines classes). - If labels are rasters, this should be None. - label_classes (List[LabelClass]): Optional, but reqiured if ussing categorical data. - A list of LabelClasses defining the list of possible class names for each - label:properties. (e.g., tree, building, car, hippo) - label_tasks (List[str]): Recommended to be a subset of 'regression', 'classification', - 'detection', or 'segmentation', but may be an arbitrary value. - label_methods: Recommended to be a subset of 'automated' or 'manual', - but may be an arbitrary value. - label_overviews (List[LabelOverview]): Optional list of LabelOverview classes - that store counts (for classification-type data) or summary statistics (for - continuous numerical/regression data). - """ # noqa E501 - self.label_description = label_description - self.label_type = label_type - self.label_properties = label_properties - self.label_classes = label_classes - self.label_tasks = label_tasks - self.label_methods = label_methods - self.label_overviews = label_overviews - - @property - def label_description(self): - """Get or sets a description of the label, how it was created, - and what it is recommended for. + classes (List[str] or List[int] or List[float]): The different possible class values. + name (str): The property key within the asset's each Feature corresponding to + class labels. If labels are raster-formatted, do not supply; required otherwise. Returns: - str + LabelClasses """ - return self.item.properties.get('label:description') - - @label_description.setter - def label_description(self, v): - self.item.properties['label:description'] = v + c = cls({}) + c.apply(classes, name) + return c @property - def label_type(self): - """Gets or sets an ENUM of either vector label type or raster label type (one - of :class:`~pystac.LabelType`). + def classes(self) -> Union[List[str], List[int], List[float]]: + """Get or sets the class values. Returns: - str + List[str] or List[int] or List[float] """ - return self.item.properties.get('label:type') + result = self.properties.get('classes') + if result is None: + raise STACError(f'LabelClasses does not contain classes property: {self.properties}') + return result - @label_type.setter - def label_type(self, v): - if v not in LabelType.ALL: - raise STACError("label_type must be one of " - "{}. Invalid input: {}".format(LabelType.ALL, v)) + @classes.setter + def classes(self, v: Union[List[str], List[int], List[float]]) -> None: + if not type(v) is list: + raise STACError("classes must be a list! Invalid input: {}".format(v)) - self.item.properties['label:type'] = v + self.properties['classes'] = v @property - def label_properties(self): - """Label Properties - - Gets or sets the names of the property field(s) in each - Feature of the label asset's FeatureCollection that contains the classes - (keywords from label:classes if the property defines classes). - If labels are rasters, this should be None. - - Returns: - List[str] or None + def name(self) -> Optional[str]: + """Get or sets the property key within the asset's each Feature corresponding to + class labels. If labels are raster-formatted, do not supply; required otherwise. """ - return self.item.properties.get('label:properties') + return self.properties.get('name') - @label_properties.setter - def label_properties(self, v): + @name.setter + def name(self, v: Optional[str]) -> None: if v is not None: - if not type(v) is list: - raise STACError("label_properties must be a list! Invalid input: {}".format(v)) - - self.item.properties['label:properties'] = v + self.properties['name'] = v + else: + self.properties.pop('name', None) - @property - def label_classes(self): - """Get or set a list of LabelClasses defining the list of possible class names for each - label:properties. (e.g., tree, building, car, hippo). + def __repr__(self) -> str: + return ''.format(','.join([str(x) for x in self.classes])) - Optional, but reqiured if using categorical data. + def to_dict(self) -> Dict[str, Any]: + """Returns the dictionary representing the JSON of this LabelClasses. Returns: - List[LabelClasses] or None + dict: The wrapped dict of the LabelClasses that can be written out as JSON. """ - label_classes = self.item.properties.get('label:classes') - if label_classes is not None: - return [LabelClasses(classes) for classes in label_classes] - else: - return None + return self.properties - @label_classes.setter - def label_classes(self, v): - if v is None: - self.item.properties.pop('label:classes', None) - else: - if not type(v) is list: - raise STACError("label_classes must be a list! Invalid input: {}".format(v)) - classes = [x.to_dict() for x in v] - self.item.properties['label:classes'] = classes +class LabelCount: + """Contains counts for categorical data. - @property - def label_tasks(self): - """Get or set a list of tasks these labels apply to. Usually a subset of 'regression', - 'classification', 'detection', or 'segmentation', but may be arbitrary values. + Use LabelCount.create to create a new LabelCount + """ + def __init__(self, properties: Dict[str, Any]): + self.properties = properties - Returns: - List[str] or None + def apply(self, name: str, count: int): + """Sets the properties for this LabelCount. + + Args: + name (str): One of the different possible classes within the property. + count (int): The number of occurrences of the class. """ - return self.item.properties.get('label:tasks') + self.name = name + self.count = count - @label_tasks.setter - def label_tasks(self, v): - if v is None: - self.item.properties.pop('label:tasks', None) - else: - if not type(v) is list: - raise STACError("label_tasks must be a list! Invalid input: {}".format(v)) + @classmethod + def create(cls, name: str, count: int) -> "LabelCount": + """Creates a LabelCount. - self.item.properties['label:tasks'] = v + Args: + name (str): One of the different possible classes within the property. + count (int): The number of occurrences of the class. + """ + x = cls({}) + x.apply(name, count) + return x @property - def label_methods(self): - """Get or set a list of methods used for labeling. Usually a subset of 'automated' or 'manual', - but may be arbitrary values. + def name(self) -> str: + """Get or sets the class that this count represents. Returns: - List[str] or None + str """ - return self.item.properties.get('label:methods') - - @label_methods.setter - def label_methods(self, v): - if v is None: - self.item.properties.pop('label:methods', None) - else: - if not type(v) is list: - raise STACError("label_methods must be a list! Invalid input: {}".format(v)) + result = self.properties.get('name') + if result is None: + raise STACError(f"Label count has no name property: {self.properties}") + return result - self.item.properties['label:methods'] = v + @name.setter + def name(self, v: str) -> None: + self.properties['name'] = v @property - def label_overviews(self): - """Get or set a list of LabelOverview classes - that store counts (for classification-type data) or summary statistics (for - continuous numerical/regression data). + def count(self) -> int: + """Get or sets the number of occurrences of the class. Returns: - List[LabelOverview] or None + int """ - overviews = self.item.properties.get('label:overviews') - if overviews is not None: - return [LabelOverview(overview) for overview in overviews] - else: - return None - - @label_overviews.setter - def label_overviews(self, v): - if v is None: - self.item.properties.pop('label:overviews', None) - else: - if not type(v) is list: - raise STACError("label_overviews must be a list! Invalid input: {}".format(v)) - - overviews = [x.to_dict() for x in v] - self.item.properties['label:overviews'] = overviews - - def __repr__(self): - return ''.format(self.item.id) - - def add_source(self, source_item, title=None, assets=None): - """Adds a link to a source item. + result = self.properties.get('count') + if result is None: + raise STACError(f"Label count has no count property: {self.properties}") + return result - Args: - source_item (Item): Source imagery that the LabelItem applies to. - title (str): Optional title for the link. - assets (List[str]): Optional list of assets that determine what - assets in the source item this label item data applies to. - """ - properties = None - if assets is not None: - properties = {'label:assets': assets} - link = Link('source', - source_item, - title=title, - media_type='application/json', - properties=properties) - self.item.add_link(link) + @count.setter + def count(self, v: int): + self.properties['count'] = v - def get_sources(self): - """Gets any source items that describe the source imagery used to generate - this LabelItem. + def to_dict(self) -> Dict[str, Any]: + """Returns the dictionary representing the JSON of this LabelCount. Returns: - Generator[Items]: A possibly empty list of source imagery items. Determined by - links of this LabelItem that have ``rel=='source'``. - """ - return self.item.get_stac_objects('source') - - def add_labels(self, href, title=None, media_type=None, properties=None): - """Adds a label asset to this LabelItem. - - Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. - """ - - self.item.add_asset( - "labels", Asset(href=href, title=title, media_type=media_type, properties=properties)) - - def add_geojson_labels(self, href, title=None, properties=None): - """Adds a GeoJSON label asset to this LabelItem. - - Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. + dict: The wrapped dict of the LabelCount that can be written out as JSON. """ - self.add_labels(href, title=title, properties=properties, media_type='application/geo+json') - - @classmethod - def _object_links(cls): - return ['source'] - - @classmethod - def from_item(cls, item): - return cls(item) + return {'name': self.name, 'count': self.count} -class LabelClasses: - """Defines the list of possible class names (e.g., tree, building, car, hippo) +class LabelStatistics: + """Contains statistics for regression/continuous numeric value data. - Use LabelClasses.create to create a new instance of LabelClasses from property values. + Use LabelStatistics.create to create a new instance. """ - def __init__(self, properties): + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties - def apply(self, classes, name=None): - """Sets the properties for this LabelClasses. + def apply(self, name: str, value: float) -> None: + """Sets the property values for this instance. Args: - classes (List[str] or List[int] or List[float]): The different possible class values. - name (str): The property key within the asset's each Feature corresponding to - class labels. If labels are raster-formatted, do not supply; required otherwise. + name (str): The name of the statistic being reported. + value (float): The value of the statistic """ - self.classes = classes self.name = name + self.value = value @classmethod - def create(cls, classes, name=None): - """Creates a new LabelClasses. + def create(cls, name: str, value: float) -> "LabelStatistics": + """Sets the property values for this instance. Args: - classes (List[str] or List[int] or List[float]): The different possible class values. - name (str): The property key within the asset's each Feature corresponding to - class labels. If labels are raster-formatted, do not supply; required otherwise. - - Returns: - LabelClasses + name (str): The name of the statistic being reported. + value (float): The value of the statistic """ - c = cls({}) - c.apply(classes, name) - return c + x = cls({}) + x.apply(name, value) + return x @property - def classes(self): - """Get or sets the class values. + def name(self) -> str: + """Get or sets the name of the statistic being reported. Returns: - List[str] or List[int] or List[float] + str """ - return self.properties.get('classes') - - @classes.setter - def classes(self, v): - if not type(v) is list: - raise STACError("classes must be a list! Invalid input: {}".format(v)) + result = self.properties.get('name') + if result is None: + raise STACError(f"Label statistics has no name property: {self.properties}") + return result - self.properties['classes'] = v + @name.setter + def name(self, v: str) -> None: + self.properties['name'] = v @property - def name(self): - """Get or sets the property key within the asset's each Feature corresponding to - class labels. If labels are raster-formatted, do not supply; required otherwise. + def value(self) -> float: + """Get or sets the value of the statistic Returns: - str + float """ - return self.properties.get('name') + result = self.properties.get('value') + if result is None: + raise STACError(f"Label statistics has no value property: {self.properties}") + return result - @name.setter - def name(self, v): - if v is not None: - self.properties['name'] = v - else: - self.properties.pop('name', None) - - def __repr__(self): - return ''.format(','.join(self.classes)) + @value.setter + def value(self, v: float) -> None: + self.properties['value'] = v - def to_dict(self): - """Returns the dictionary representing the JSON of this LabelClasses. + def to_dict(self) -> Dict[str, Any]: + """Returns the dictionary representing the JSON of this LabelStatistics. Returns: - dict: The wrapped dict of the LabelClasses that can be written out as JSON. + dict: The wrapped dict of the LabelStatistics that can be written out as JSON. """ - return self.properties + return {'name': self.name, 'value': self.value} class LabelOverview: @@ -388,10 +253,13 @@ class LabelOverview: Use LabelOverview.create to create a new LabelOverview. """ - def __init__(self, properties): + def __init__(self, properties: Dict[str, Any]): self.properties = properties - def apply(self, property_key, counts=None, statistics=None): + def apply(self, + property_key: str, + counts: Optional[List[LabelCount]] = None, + statistics: Optional[List[LabelStatistics]] = None): """Sets the properties for this LabelOverview. Either ``counts`` or ``statistics``, or both, can be placed in an overview; @@ -399,9 +267,9 @@ def apply(self, property_key, counts=None, statistics=None): Args: property_key (str): The property key within the asset corresponding to class labels. - counts (List[LabelCounts]): Optional list of LabelCounts containing counts + counts: Optional list of LabelCounts containing counts for categorical data. - statistics (List[Statistics]): Optional list of Statistics containing statistics for + statistics: Optional list of statistics containing statistics for regression/continuous numeric value data. """ self.property_key = property_key @@ -409,7 +277,10 @@ def apply(self, property_key, counts=None, statistics=None): self.statistics = statistics @classmethod - def create(cls, property_key, counts=None, statistics=None): + def create(cls, + property_key: str, + counts: Optional[List[LabelCount]] = None, + statistics: Optional[List[LabelStatistics]] = None) -> "LabelOverview": """Creates a new LabelOverview. Either ``counts`` or ``statistics``, or both, can be placed in an overview; @@ -417,9 +288,9 @@ def create(cls, property_key, counts=None, statistics=None): Args: property_key (str): The property key within the asset corresponding to class labels. - counts (List[LabelCounts]): Optional list of LabelCounts containing counts for + counts: Optional list of LabelCounts containing counts for categorical data. - statistics (List[Statistics]): Optional list of Statistics containing statistics for + statistics: Optional list of Statistics containing statistics for regression/continuous numeric value data. """ x = LabelOverview({}) @@ -427,42 +298,45 @@ def create(cls, property_key, counts=None, statistics=None): return x @property - def property_key(self): + def property_key(self) -> str: """Get or sets the property key within the asset corresponding to class labels. Returns: str """ - return self.properties.get('property_key') + result = self.properties.get('property_key') + if result is None: + raise STACError(f"Label overview has no property_key: {self.properties}") + return result @property_key.setter - def property_key(self, v): + def property_key(self, v: str) -> None: self.properties['property_key'] = v @property - def counts(self): + def counts(self) -> Optional[List[LabelCount]]: """Get or sets the list of LabelCounts containing counts for categorical data. Returns: List[LabelCount] """ counts = self.properties.get('counts') - if counts is not None: - counts = [LabelCount(c) for c in counts] - return counts + if counts is None: + return None + return [LabelCount(c) for c in counts] @counts.setter - def counts(self, v): + def counts(self, v: Optional[List[LabelCount]]) -> None: if v is None: self.properties.pop('counts', None) else: - if not type(v) is list: + if not isinstance(v, list): raise STACError("counts must be a list! Invalid input: {}".format(v)) self.properties['counts'] = [c.to_dict() for c in v] @property - def statistics(self): + def statistics(self) -> Optional[List[LabelStatistics]]: """Get or sets the list of Statistics containing statistics for regression/continuous numeric value data. @@ -470,22 +344,22 @@ def statistics(self): List[Statistics] """ statistics = self.properties.get('statistics') - if statistics is not None: - statistics = [LabelStatistics(s) for s in statistics] + if statistics is None: + return None - return statistics + return [LabelStatistics(s) for s in statistics] @statistics.setter - def statistics(self, v): + def statistics(self, v: Optional[List[LabelStatistics]]) -> None: if v is None: self.properties.pop('statistics', None) else: - if not type(v) is list: + if not isinstance(v, list): raise STACError("statistics must be a list! Invalid input: {}".format(v)) self.properties['statistics'] = [s.to_dict() for s in v] - def merge_counts(self, other): + def merge_counts(self, other: "LabelOverview") -> "LabelOverview": """Merges the counts associated with this overview with another overview. Creates a new LabelOverview. @@ -505,9 +379,9 @@ def merge_counts(self, other): if other.counts is None: new_counts = self.counts else: - count_by_prop = {} + count_by_prop: Dict[str, int] = {} - def add_counts(counts): + def add_counts(counts: List[LabelCount]): for c in counts: if c.name not in count_by_prop: count_by_prop[c.name] = c.count @@ -516,10 +390,10 @@ def add_counts(counts): add_counts(self.counts) add_counts(other.counts) - new_counts = [LabelCount(k, v) for k, v in count_by_prop.items()] + new_counts = [LabelCount.create(k, v) for k, v in count_by_prop.items()] return LabelOverview.create(self.property_key, counts=new_counts) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this LabelOverview. Returns: @@ -528,134 +402,290 @@ def to_dict(self): return self.properties -class LabelCount: - """Contains counts for categorical data. +class LabelItemExt(ItemExtension): + """A LabelItemExt is the extension of the Item in the label extension which + represents a polygon, set of polygons, or raster data defining + labels and label metadata and should be part of a Collection. - Use LabelCount.create to create a new LabelCount - """ - def __init__(self, properties): - self.properties = properties + Args: + item (Item): The item to be extended. - def apply(self, name, count): - """Sets the properties for this LabelCount. + Attributes: + item (Item): The Item that is being extended. - Args: - name (str): One of the different possible classes within the property. - count (int): The number of occurrences of the class. - """ - self.name = name - self.count = count + See: + `Item fields in the label extension spec `_ - @classmethod - def create(cls, name, count): - """Creates a LabelCount. + Note: + Using LabelItemExt to directly wrap an item will add the 'label' extension ID to + the item's stac_extensions. + """ # noqa E501 + + def __init__(self, item: Item) -> None: + if item.stac_extensions is None: + item.stac_extensions = [str(Extensions.LABEL)] + elif str(Extensions.LABEL) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.LABEL)) + + self.item = item + + def apply(self, + label_description: str, + label_type: LabelType, + label_properties: Optional[List[str]] = None, + label_classes: Optional[List[LabelClasses]] = None, + label_tasks: Optional[List[str]] = None, + label_methods: Optional[List[str]] = None, + label_overviews: Optional[List[LabelOverview]] = None): + """Applies label extension properties to the extended Item. Args: - name (str): One of the different possible classes within the property. - count (int): The number of occurrences of the class. - """ - x = cls({}) - x.apply(name, count) - return x + label_description (str): A description of the label, how it was created, + and what it is recommended for + label_type (str): An ENUM of either vector label type or raster label type. Use + one of :class:`~pystac.LabelType`. + label_properties (list or None): These are the names of the property field(s) in each + Feature of the label asset's FeatureCollection that contains the classes + (keywords from label:classes if the property defines classes). + If labels are rasters, this should be None. + label_classes (List[LabelClass]): Optional, but required if ussing categorical data. + A list of LabelClasses defining the list of possible class names for each + label:properties. (e.g., tree, building, car, hippo) + label_tasks (List[str]): Recommended to be a subset of 'regression', 'classification', + 'detection', or 'segmentation', but may be an arbitrary value. + label_methods: Recommended to be a subset of 'automated' or 'manual', + but may be an arbitrary value. + label_overviews (List[LabelOverview]): Optional list of LabelOverview classes + that store counts (for classification-type data) or summary statistics (for + continuous numerical/regression data). + """ # noqa E501 + self.label_description = label_description + self.label_type = label_type + self.label_properties = label_properties + self.label_classes = label_classes + self.label_tasks = label_tasks + self.label_methods = label_methods + self.label_overviews = label_overviews @property - def name(self): - """Get or sets the class that this count represents. + def label_description(self) -> str: + """Get or sets a description of the label, how it was created, + and what it is recommended for. Returns: str """ - return self.properties.get('name') + result = self.item.properties.get('label:description') + if result is None: + raise STACError(f"label:description not set for item {self.item.id}") + return result - @name.setter - def name(self, v): - self.properties['name'] = v + @label_description.setter + def label_description(self, v: str) -> None: + self.item.properties['label:description'] = v @property - def count(self): - """Get or sets the number of occurrences of the class. - - Returns: - int + def label_type(self) -> LabelType: + """Gets or sets an ENUM of either vector label type or raster label type. """ - return self.properties.get('count') + result = self.item.properties.get('label:type') + if result is None: + raise STACError(f"label:type is not set for item {self.item.id}") + return LabelType(result) - @count.setter - def count(self, v): - self.properties['count'] = v + @label_type.setter + def label_type(self, v: LabelType): + if v not in LabelType.ALL: + raise STACError("label_type must be one of " + "{}. Invalid input: {}".format(LabelType.ALL, v)) - def to_dict(self): - """Returns the dictionary representing the JSON of this LabelCount. + self.item.properties['label:type'] = v + + @property + def label_properties(self) -> Optional[List[str]]: + """Label Properties + + Gets or sets the names of the property field(s) in each + Feature of the label asset's FeatureCollection that contains the classes + (keywords from label:classes if the property defines classes). + If labels are rasters, this should be None. Returns: - dict: The wrapped dict of the LabelCount that can be written out as JSON. + List[str] or None """ - return {'name': self.name, 'count': self.count} + return self.item.properties.get('label:properties') + @label_properties.setter + def label_properties(self, v: Optional[List[str]]) -> None: + if v is not None: + if not isinstance(v, list): + raise STACError("label_properties must be a list! Invalid input: {}".format(v)) -class LabelStatistics: - """Contains statistics for regression/continuous numeric value data. + self.item.properties['label:properties'] = v - Use LabelStatistics.create to create a new instance. - """ - def __init__(self, properties): - self.properties = properties + @property + def label_classes(self) -> Optional[List[LabelClasses]]: + """Get or set a list of LabelClasses defining the list of possible class names for each + label:properties. (e.g., tree, building, car, hippo). - def apply(self, name, value): - """Sets the property values for this instance. + Optional, but required if using categorical data. - Args: - name (str): The name of the statistic being reported. - value (float): The value of the statistic + Returns: + List[LabelClasses] or None """ - self.name = name - self.value = value + label_classes = self.item.properties.get('label:classes') + if label_classes is not None: + return [LabelClasses(classes) for classes in label_classes] + else: + return None - @classmethod - def create(cls, name, value): - """Sets the property values for this instance. + @label_classes.setter + def label_classes(self, v: Optional[List[LabelClasses]]) -> None: + if v is None: + self.item.properties.pop('label:classes', None) + else: + if not isinstance(v, list): + raise STACError("label_classes must be a list! Invalid input: {}".format(v)) - Args: - name (str): The name of the statistic being reported. - value (float): The value of the statistic + classes = [x.to_dict() for x in v] + self.item.properties['label:classes'] = classes + + @property + def label_tasks(self) -> Optional[List[str]]: + """Get or set a list of tasks these labels apply to. Usually a subset of 'regression', + 'classification', 'detection', or 'segmentation', but may be arbitrary values. + + Returns: + List[str] or None """ - x = cls({}) - x.apply(name, value) - return x + return self.item.properties.get('label:tasks') + + @label_tasks.setter + def label_tasks(self, v: Optional[List[str]]) -> None: + if v is None: + self.item.properties.pop('label:tasks', None) + else: + if not isinstance(v, list): + raise STACError("label_tasks must be a list! Invalid input: {}".format(v)) + + self.item.properties['label:tasks'] = v @property - def name(self): - """Get or sets the name of the statistic being reported. + def label_methods(self) -> Optional[List[str]]: + """Get or set a list of methods used for labeling. Usually a subset of 'automated' or 'manual', + but may be arbitrary values. Returns: - str + List[str] or None """ - return self.properties.get('name') + return self.item.properties.get('label:methods') - @name.setter - def name(self, v): - self.properties['name'] = v + @label_methods.setter + def label_methods(self, v: Optional[List[str]]) -> None: + if v is None: + self.item.properties.pop('label:methods', None) + else: + if not isinstance(v, list): + raise STACError("label_methods must be a list! Invalid input: {}".format(v)) + + self.item.properties['label:methods'] = v @property - def value(self): - """Get or sets the value of the statistic + def label_overviews(self) -> Optional[List[LabelOverview]]: + """Get or set a list of LabelOverview classes + that store counts (for classification-type data) or summary statistics (for + continuous numerical/regression data). Returns: - int or float + List[LabelOverview] or None """ - return self.properties.get('value') + overviews = self.item.properties.get('label:overviews') + if overviews is not None: + return [LabelOverview(overview) for overview in overviews] + else: + return None - @value.setter - def value(self, v): - self.properties['value'] = v + @label_overviews.setter + def label_overviews(self, v: Optional[List[LabelOverview]]) -> None: + if v is None: + self.item.properties.pop('label:overviews', None) + else: + if not isinstance(v, list): + raise STACError("label_overviews must be a list! Invalid input: {}".format(v)) - def to_dict(self): - """Returns the dictionary representing the JSON of this LabelStatistics. + overviews = [x.to_dict() for x in v] + self.item.properties['label:overviews'] = overviews + + def __repr__(self) -> str: + return ''.format(self.item.id) + + def add_source(self, + source_item: Item, + title: Optional[str] = None, + assets: Optional[List[str]] = None): + """Adds a link to a source item. + + Args: + source_item (Item): Source imagery that the LabelItem applies to. + title (str): Optional title for the link. + assets (List[str]): Optional list of assets that determine what + assets in the source item this label item data applies to. + """ + properties = None + if assets is not None: + properties = {'label:assets': assets} + link = Link('source', + source_item, + title=title, + media_type='application/json', + properties=properties) + self.item.add_link(link) + + def get_sources(self) -> Iterable[Item]: + """Gets any source items that describe the source imagery used to generate + this LabelItem. Returns: - dict: The wrapped dict of the LabelStatistics that can be written out as JSON. + Generator[Items]: A possibly empty list of source imagery items. Determined by + links of this LabelItem that have ``rel=='source'``. """ - return {'name': self.name, 'value': self.value} + return map(lambda x: cast(Item, x), self.item.get_stac_objects('source')) + + def add_labels(self, href: str, title: Optional[str]=None, media_type: Optional[str]=None, properties: Optional[Dict[str, Any]]=None): + """Adds a label asset to this LabelItem. + + Args: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + media_type (str): Optional description of the media type. Registered Media Types + are preferred. See :class:`~pystac.MediaType` for common media types. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. + """ + + self.item.add_asset( + "labels", Asset(href=href, title=title, media_type=media_type, properties=properties)) + + def add_geojson_labels(self, href: str, title: Optional[str]=None, properties:Optional[Dict[str, Any]]=None): + """Adds a GeoJSON label asset to this LabelItem. + + Args: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. + """ + self.add_labels(href, title=title, properties=properties, media_type=MediaType.GEOJSON) + + @classmethod + def _object_links(cls) -> List[str]: + return ['source'] + + @classmethod + def from_item(cls, item: Item) -> "LabelItemExt": + return cls(item) LABEL_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.LABEL, diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 4573841ec..c173281bd 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -1,302 +1,18 @@ +from typing import Any, Dict, List, Optional from pystac import Extensions, STACError -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) -class PointcloudItemExt(ItemExtension): - """PointcloudItemExt is the extension of an Item in the PointCloud Extension. - The Pointclout extension adds pointcloud information to STAC Items. - - Args: - item (Item): The item to be extended. - - Attributes: - item (Item): The Item that is being extended. - - """ - def __init__(self, item): - if item.stac_extensions is None: - item.stac_extensions = [Extensions.POINTCLOUD] - elif Extensions.POINTCLOUD not in item.stac_extensions: - item.stac_extensions.append(Extensions.POINTCLOUD) - - self.item = item - - def apply(self, count, type, encoding, schemas, density=None, statistics=None, epsg=None): - """Applies Pointcloud extension properties to the extended Item. - - Args: - count (int): REQUIRED. The number of points in the cloud. - type (str): REQUIRED. Phenomenology type for the point cloud. Possible valid - values might include lidar, eopc, radar, sonar, or otherThe type of file - or data format of the cloud. - encoding (str): REQUIRED. Content encoding or format of the data. - schemas (List[dict]): REQUIRED. A sequential array of items that define the - dimensions and their types. - density (dict or None): Number of points per square unit area. - statistics (List[int] or None): A sequential array of items mapping to pc:schemas - defines per-channel statistics. - epsg (str): An EPSG code for the projected coordinates of the pointcloud. - """ - self.count = count - self.type = type - self.encoding = encoding - self.schemas = schemas - self.density = density - self.statistics = statistics - self.epsg = epsg - - @property - def count(self): - """Get or sets the count property of the datasource. - - Returns: - int - """ - return self.get_count() - - @count.setter - def count(self, v): - self.set_count(v) - - def get_count(self, asset=None): - """Gets an Item or an Asset count. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'pc:count' not in asset.properties: - return self.item.properties.get('pc:count') - else: - return asset.properties.get('pc:count') - - def set_count(self, count, asset=None): - """Set an Item or an Asset count. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:count', count, asset) - - @property - def type(self): - """Get or sets the pc:type prop on the Item - - Returns: - str - """ - return self.get_type() - - @type.setter - def type(self, v): - self.set_type(v) - - def get_type(self, asset=None): - """Gets an Item or an Asset type. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - str - """ - if asset is None or 'pc:type' not in asset.properties: - return self.item.properties.get('pc:type') - else: - return asset.properties.get('pc:type') - - def set_type(self, type, asset=None): - """Set an Item or an Asset type. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:type', type, asset) - - @property - def encoding(self): - """Get or sets the content-encoding for the item. - - The content-encoding is the underlying encoding format for the point cloud. - Examples may include: laszip, ascii, binary, etc. - - Returns: - str - """ - return self.get_encoding() - - @encoding.setter - def encoding(self, v): - self.set_encoding(v) - - def get_encoding(self, asset=None): - """Gets an Item or an Asset encoding. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - str - """ - if asset is None or 'pc:encoding' not in asset.properties: - return self.item.properties.get('pc:encoding') - else: - return asset.properties.get('pc:encoding') - - def set_encoding(self, encoding, asset=None): - """Set an Item or an Asset encoding. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:encoding', encoding, asset) - - @property - def schemas(self): - """Get or sets a - - The schemas represent the structure of the data attributes in the pointcloud, - and is represented as a sequential array of items that define the dimensions - and their types, - - Returns: - List[PointcloudSchema] - """ - return self.get_schemas() - - @schemas.setter - def schemas(self, v): - self.set_schemas(v) - - def get_schemas(self, asset=None): - """Gets an Item or an Asset projection geometry. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - List[PointcloudSchema] - """ - if asset is None or 'pc:schemas' not in asset.properties: - schemas = self.item.properties.get('pc:schemas') - return [PointcloudSchema(s) for s in schemas] - else: - return [PointcloudSchema.create(s) for s in asset.properties.get('pc:schemas')] - - def set_schemas(self, schemas, asset=None): - """Set an Item or an Asset schema - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - dicts = [s.to_dict() for s in schemas] - self._set_property('pc:schemas', dicts, asset) - - @property - def density(self): - """Get or sets the density for the item. - - Density is defined as the number of points per square unit area. - - Returns: - int - """ - return self.get_density() - - @density.setter - def density(self, v): - self.set_density(v) - - def get_density(self, asset=None): - """Gets an Item or an Asset density. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'pc:density' not in asset.properties: - return self.item.properties.get('pc:density') - else: - return asset.properties.get('pc:density') - - def set_density(self, density, asset=None): - """Set an Item or an Asset density property. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:density', density, asset) - - @property - def statistics(self): - """Get or sets the statistics for each property of the dataset. - - A sequential array of items mapping to pc:schemas defines per-channel statistics. - - Example:: - - item.ext.pointcloud.statistics = [{ 'name': 'red', 'min': 0, 'max': 255 }] - - Returns: - List[dict] - """ - return self.get_statistics() - - @statistics.setter - def statistics(self, v): - self.set_statistics(v) - - def get_statistics(self, asset=None): - """Gets an Item or an Asset centroid. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - List[PointCloudStatistics] or None - """ - if asset is None or 'pc:statistics' not in asset.properties: - stats = self.item.properties.get('pc:statistics') - if stats: - return [PointcloudStatistic(s) for s in stats] - else: - return None - else: - return [PointcloudStatistic.create(s) for s in asset.properties.get('pc:statistics')] - - def set_statistics(self, statistics, asset=None): - """Set an Item or an Asset centroid. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if statistics is not None: - statistics = [s.to_dict() for s in statistics] - self._set_property('pc:statistics', statistics, asset) - - @classmethod - def _object_links(cls): - return [] - - @classmethod - def from_item(cls, item): - return cls(item) - - class PointcloudSchema: """Defines a schema for dimension of a pointcloud (e.g., name, size, type) Use PointCloudSchema.create to create a new instance of PointCloudSchema from properties. """ - def __init__(self, properties): + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties - def apply(self, name, size, type): + def apply(self, name: str, size: int, type: str) -> None: """Sets the properties for this PointCloudSchema. Args: @@ -309,7 +25,7 @@ def apply(self, name, size, type): self.properties['type'] = type @classmethod - def create(cls, *args): + def create(cls, name: str, size: int, type: str) -> "PointcloudSchema": """Creates a new PointCloudSchema. Args: @@ -321,61 +37,64 @@ def create(cls, *args): PointCloudSchema """ c = cls({}) - c.apply(*args) + c.apply(name=name, size=size, type=type) return c @property - def size(self): + def size(self) -> int: """Get or sets the size value. Returns: int """ - return self.properties.get('size') + result = self.properties.get('size') + if result is None: + raise STACError(f"Pointcloud schema does not have size property: {self.properties}") + return result @size.setter - def size(self, v): - if not type(v) is int: + def size(self, v: int) -> None: + if not isinstance(v, int): raise STACError("size must be an int! Invalid input: {}".format(v)) self.properties['size'] = v @property - def name(self): + def name(self) -> str: """Get or sets the name property for this PointCloudSchema. Returns: str """ - return self.properties.get('name') + result = self.properties.get('name') + if result is None: + raise STACError(f"Pointcloud schema does not have name property: {self.properties}") + return result @name.setter - def name(self, v): - if v is not None: - self.properties['name'] = v - else: - self.properties.pop('name', None) + def name(self, v: str) -> None: + self.properties['name'] = v @property - def type(self): + def type(self) -> str: """Get or sets the type property. Valid values are `floating`, `unsigned`, and `signed` Returns: str """ - return self.properties.get('type') + result = self.properties.get('type') + if result is None: + raise STACError(f"Pointcloud schema has no type property: {self.properties}") + return result @type.setter - def type(self, v): - if v is not None: - self.properties['type'] = v - else: - self.properties.pop('type', None) + def type(self, v: str) -> None: + self.properties['type'] = v - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.name, self.size, self.type) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointCloudSchema. Returns: @@ -389,18 +108,18 @@ class PointcloudStatistic: Use PointcloudStatistic.create to create a new instance of LabelClasses from property values. """ - def __init__(self, properties): + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties def apply(self, - name, - position=None, - average=None, - count=None, - maximum=None, - minimum=None, - stddev=None, - variance=None): + name: str, + position: Optional[int] = None, + average: Optional[float] = None, + count: Optional[int] = None, + maximum: Optional[float] = None, + minimum: Optional[float] = None, + stddev: Optional[float] = None, + variance: Optional[float] = None): """Sets the properties for this PointcloudStatistic. Args: @@ -424,14 +143,14 @@ def apply(self, @classmethod def create(cls, - name, - position=None, - average=None, - count=None, - maximum=None, - minimum=None, - stddev=None, - variance=None): + name: str, + position: Optional[int] = None, + average: Optional[float] = None, + count: Optional[int] = None, + maximum: Optional[float] = None, + minimum: Optional[float] = None, + stddev: Optional[float] = None, + variance: Optional[float] = None): """Creates a new PointcloudStatistic class. Args: @@ -448,27 +167,37 @@ def create(cls, LabelClasses """ c = cls({}) - c.apply(name, ) + c.apply(name=name, + position=position, + average=average, + count=count, + maximum=maximum, + minimum=minimum, + stddev=stddev, + variance=variance) return c @property - def name(self): + def name(self) -> str: """Get or sets the name property Returns: str """ - return self.properties.get('name') + result = self.properties.get('name') + if result is None: + raise STACError(f"Pointcloud statistics does not have name property: {self.properties}") + return result @name.setter - def name(self, v): + def name(self, v: str) -> None: if v is not None: self.properties['name'] = v else: self.properties.pop('name', None) @property - def position(self): + def position(self) -> Optional[int]: """Get or sets the position property Returns: @@ -477,14 +206,14 @@ def position(self): return self.properties.get('position') @position.setter - def position(self, v): + def position(self, v: Optional[int]) -> None: if v is not None: self.properties['position'] = v else: self.properties.pop('position', None) @property - def average(self): + def average(self) -> Optional[float]: """Get or sets the average property Returns: @@ -493,14 +222,14 @@ def average(self): return self.properties.get('average') @average.setter - def average(self, v): + def average(self, v: Optional[float]) -> None: if v is not None: self.properties['average'] = v else: self.properties.pop('average', None) @property - def count(self): + def count(self) -> Optional[int]: """Get or sets the count property Returns: @@ -509,14 +238,14 @@ def count(self): return self.properties.get('count') @count.setter - def count(self, v): + def count(self, v: Optional[int]) -> None: if v is not None: self.properties['count'] = v else: self.properties.pop('count', None) @property - def maximum(self): + def maximum(self) -> Optional[float]: """Get or sets the maximum property Returns: @@ -525,14 +254,14 @@ def maximum(self): return self.properties.get('maximum') @maximum.setter - def maximum(self, v): + def maximum(self, v: Optional[float]) -> None: if v is not None: self.properties['maximum'] = v else: self.properties.pop('maximum', None) @property - def minimum(self): + def minimum(self) -> Optional[float]: """Get or sets the minimum property Returns: @@ -541,14 +270,14 @@ def minimum(self): return self.properties.get('minimum') @minimum.setter - def minimum(self, v): + def minimum(self, v: Optional[float]) -> None: if v is not None: self.properties['minimum'] = v else: self.properties.pop('minimum', None) @property - def stddev(self): + def stddev(self) -> Optional[float]: """Get or sets the stddev property Returns: @@ -557,14 +286,14 @@ def stddev(self): return self.properties.get('stddev') @stddev.setter - def stddev(self, v): + def stddev(self, v: Optional[float]) -> None: if v is not None: self.properties['stddev'] = v else: self.properties.pop('stddev', None) @property - def variance(self): + def variance(self) -> Optional[float]: """Get or sets the variance property Returns: @@ -573,16 +302,16 @@ def variance(self): return self.properties.get('variance') @variance.setter - def variance(self, v): + def variance(self, v: Optional[float]) -> None: if v is not None: self.properties['variance'] = v else: self.properties.pop('variance', None) - def __repr__(self): + def __repr__(self) -> str: return ''.format(str(self.properties)) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointcloudStatistic. Returns: @@ -591,5 +320,313 @@ def to_dict(self): return self.properties +class PointcloudItemExt(ItemExtension): + """PointcloudItemExt is the extension of an Item in the PointCloud Extension. + The Pointclout extension adds pointcloud information to STAC Items. + + Args: + item (Item): The item to be extended. + + Attributes: + item (Item): The Item that is being extended. + + """ + def __init__(self, item: Item) -> None: + if item.stac_extensions is None: + item.stac_extensions = [str(Extensions.POINTCLOUD)] + elif str(Extensions.POINTCLOUD) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.POINTCLOUD)) + + self.item = item + + def apply(self, + count: int, + type: str, + encoding: str, + schemas: List[PointcloudSchema], + density: Optional[float] = None, + statistics: Optional[List[PointcloudStatistic]] = None, + epsg: Optional[int] = None): # TODO: Remove epsg per spec + """Applies Pointcloud extension properties to the extended Item. + + Args: + count (int): REQUIRED. The number of points in the cloud. + type (str): REQUIRED. Phenomenology type for the point cloud. Possible valid + values might include lidar, eopc, radar, sonar, or otherThe type of file + or data format of the cloud. + encoding (str): REQUIRED. Content encoding or format of the data. + schemas (List[PointcloudSchema]): REQUIRED. A sequential array of items that define the + dimensions and their types. + density (float or None): Number of points per square unit area. + statistics (List[int] or None): A sequential array of items mapping to pc:schemas + defines per-channel statistics. + epsg (str): An EPSG code for the projected coordinates of the pointcloud. + """ + self.count = count + self.type = type + self.encoding = encoding + self.schemas = schemas + self.density = density + self.statistics = statistics + self.epsg = epsg + + @property + def count(self) -> int: + """Get or sets the count property of the datasource. + + Returns: + int + """ + return self.get_count() + + @count.setter + def count(self, v: int) -> None: + self.set_count(v) + + def get_count(self, asset: Optional[Asset] = None) -> int: + """Gets an Item or an Asset count. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + int + """ + if asset is None or 'pc:count' not in asset.properties: + result = self.item.properties.get('pc:count') + else: + result = asset.properties.get('pc:count') + + if result is None: + raise STACError(f"pc:count not found on point cloud item with ID {self.item.id}") + + return result + + def set_count(self, count: int, asset: Optional[Asset] = None): + """Set an Item or an Asset count. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + self._set_property('pc:count', count, asset) + + @property + def type(self) -> str: + """Get or sets the pc:type prop on the Item + + Returns: + str + """ + return self.get_type() + + @type.setter + def type(self, v: str) -> None: + self.set_type(v) + + def get_type(self, asset: Optional[Asset] = None) -> str: + """Gets an Item or an Asset type. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + str + """ + if asset is None or 'pc:type' not in asset.properties: + result = self.item.properties.get('pc:type') + else: + result = asset.properties.get('pc:type') + + if result is None: + raise STACError(f"pc:type not found on point cloud item with ID {self.item.id}") + + return result + + def set_type(self, type: str, asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset type. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + self._set_property('pc:type', type, asset) + + @property + def encoding(self) -> str: + """Get or sets the content-encoding for the item. + + The content-encoding is the underlying encoding format for the point cloud. + Examples may include: laszip, ascii, binary, etc. + + Returns: + str + """ + return self.get_encoding() + + @encoding.setter + def encoding(self, v: str) -> None: + self.set_encoding(v) + + def get_encoding(self, asset: Optional[Asset] = None) -> str: + """Gets an Item or an Asset encoding. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + str + """ + if asset is None or 'pc:encoding' not in asset.properties: + result = self.item.properties.get('pc:encoding') + else: + result = asset.properties.get('pc:encoding') + + if result is None: + raise STACError(f"pc:encoding not found on point cloud item with ID {self.item.id}") + + return result + + def set_encoding(self, encoding: str, asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset encoding. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + self._set_property('pc:encoding', encoding, asset) + + @property + def schemas(self) -> List[PointcloudSchema]: + """Get or sets a + + The schemas represent the structure of the data attributes in the pointcloud, + and is represented as a sequential array of items that define the dimensions + and their types, + + Returns: + List[PointcloudSchema] + """ + return self.get_schemas() + + @schemas.setter + def schemas(self, v: List[PointcloudSchema]) -> None: + self.set_schemas(v) + + def get_schemas(self, asset: Optional[Asset] = None) -> List[PointcloudSchema]: + """Gets an Item or an Asset projection geometry. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + List[PointcloudSchema] + """ + if asset is None or 'pc:schemas' not in asset.properties: + schemas = self.item.properties.get('pc:schemas') + else: + schemas = asset.properties.get('pc:schemas') + + return [PointcloudSchema(s) for s in schemas] + + def set_schemas(self, schemas: List[PointcloudSchema], asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset schema + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + dicts = [s.to_dict() for s in schemas] + self._set_property('pc:schemas', dicts, asset) + + @property + def density(self) -> Optional[float]: + """Get or sets the density for the item. + + Density is defined as the number of points per square unit area. + + Returns: + int + """ + return self.get_density() + + @density.setter + def density(self, v: Optional[float]) -> None: + self.set_density(v) + + def get_density(self, asset: Optional[Asset] = None) -> Optional[float]: + """Gets an Item or an Asset density. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + int + """ + if asset is None or 'pc:density' not in asset.properties: + return self.item.properties.get('pc:density') + else: + return asset.properties.get('pc:density') + + def set_density(self, density: Optional[float], asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset density property. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + self._set_property('pc:density', density, asset) + + @property + def statistics(self) -> Optional[List[PointcloudStatistic]]: + """Get or sets the statistics for each property of the dataset. + + A sequential array of items mapping to pc:schemas defines per-channel statistics. + + Example:: + + item.ext.pointcloud.statistics = [{ 'name': 'red', 'min': 0, 'max': 255 }] + """ + return self.get_statistics() + + @statistics.setter + def statistics(self, v: Optional[List[PointcloudStatistic]]): + self.set_statistics(v) + + def get_statistics(self, asset: Optional[Asset] = None) -> Optional[List[PointcloudStatistic]]: + """Gets an Item or an Asset centroid. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value + + Returns: + List[PointCloudStatistics] or None + """ + if asset is None or 'pc:statistics' not in asset.properties: + stats = self.item.properties.get('pc:statistics') + if stats: + return [PointcloudStatistic(s) for s in stats] + else: + return None + else: + return [PointcloudStatistic.create(s) for s in asset.properties['pc:statistics']] + + def set_statistics(self, + statistics: Optional[List[PointcloudStatistic]], + asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset centroid. + + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + if statistics is not None: + self._set_property('pc:statistics', [s.to_dict() for s in statistics], asset) + else: + self._set_property('pc:statistics', None, asset) + + @classmethod + def _object_links(cls) -> List[str]: + return [] + + @classmethod + def from_item(cls, item: Item) -> "PointcloudItemExt": + return cls(item) + + POINTCLOUD_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.POINTCLOUD, [ExtendedObject(Item, PointcloudItemExt)]) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index a30ecec72..9984796b3 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,5 +1,6 @@ +from typing import Any, Dict, List, Optional from pystac import Extensions -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) @@ -17,23 +18,23 @@ class ProjectionItemExt(ItemExtension): Using ProjectionItemExt to directly wrap an item will add the 'proj' extension ID to the item's stac_extensions. """ - def __init__(self, item): + def __init__(self, item: Item) -> None: if item.stac_extensions is None: - item.stac_extensions = [Extensions.PROJECTION] - elif Extensions.PROJECTION not in item.stac_extensions: - item.stac_extensions.append(Extensions.PROJECTION) + item.stac_extensions = [str(Extensions.PROJECTION)] + elif str(Extensions.PROJECTION) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.PROJECTION)) self.item = item def apply(self, - epsg, - wkt2=None, - projjson=None, - geometry=None, - bbox=None, - centroid=None, - shape=None, - transform=None): + epsg: Optional[int], + wkt2: Optional[str] = None, + projjson: Optional[Dict[str, Any]] = None, + geometry: Optional[Dict[str, Any]] = None, + bbox: Optional[List[float]] = None, + centroid: Optional[Dict[str, float]] = None, + shape: Optional[List[int]] = None, + transform: Optional[List[float]] = None): """Applies Projection extension properties to the extended Item. Args: @@ -64,7 +65,7 @@ def apply(self, self.transform = transform @property - def epsg(self): + def epsg(self) -> Optional[int]: """Get or sets the EPSG code of the datasource. A Coordinate Reference System (CRS) is the data reference system (sometimes called a @@ -80,10 +81,10 @@ def epsg(self): return self.get_epsg() @epsg.setter - def epsg(self, v): + def epsg(self, v: Optional[int]) -> None: self.set_epsg(v) - def get_epsg(self, asset=None): + def get_epsg(self, asset: Optional[Asset] = None) -> Optional[int]: """Gets an Item or an Asset epsg. If an Asset is supplied and the Item property exists on the Asset, @@ -97,16 +98,16 @@ def get_epsg(self, asset=None): else: return asset.properties.get('proj:epsg') - def set_epsg(self, epsg, asset=None): + def set_epsg(self, epsg: Optional[int], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset epsg. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ - self._set_property('proj:epsg', epsg, asset) + self._set_property('proj:epsg', epsg, asset, pop_if_none=False) @property - def wkt2(self): + def wkt2(self) -> Optional[str]: """Get or sets the WKT2 string representing the Coordinate Reference System (CRS) that the proj:geometry and proj:bbox fields represent @@ -121,10 +122,10 @@ def wkt2(self): return self.get_wkt2() @wkt2.setter - def wkt2(self, v): + def wkt2(self, v: Optional[str]) -> None: self.set_wkt2(v) - def get_wkt2(self, asset=None): + def get_wkt2(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset wkt2. If an Asset is supplied and the Item property exists on the Asset, @@ -138,7 +139,7 @@ def get_wkt2(self, asset=None): else: return asset.properties.get('proj:wkt2') - def set_wkt2(self, wkt2, asset=None): + def set_wkt2(self, wkt2: Optional[str], asset: Optional[Asset] = None): """Set an Item or an Asset wkt2. If an Asset is supplied, sets the property on the Asset. @@ -147,7 +148,7 @@ def set_wkt2(self, wkt2, asset=None): self._set_property('proj:wkt2', wkt2, asset) @property - def projjson(self): + def projjson(self) -> Optional[Dict[str, Any]]: """Get or sets the PROJJSON string representing the Coordinate Reference System (CRS) that the proj:geometry and proj:bbox fields represent @@ -165,10 +166,10 @@ def projjson(self): return self.get_projjson() @projjson.setter - def projjson(self, v): + def projjson(self, v: Optional[Dict[str, Any]]) -> None: self.set_projjson(v) - def get_projjson(self, asset=None): + def get_projjson(self, asset: Optional[Asset] = None) -> Optional[Dict[str, Any]]: """Gets an Item or an Asset projjson. If an Asset is supplied and the Item property exists on the Asset, @@ -182,7 +183,9 @@ def get_projjson(self, asset=None): else: return asset.properties.get('proj:projjson') - def set_projjson(self, projjson, asset=None): + def set_projjson(self, + projjson: Optional[Dict[str, Any]], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset projjson. If an Asset is supplied, sets the property on the Asset. @@ -191,7 +194,7 @@ def set_projjson(self, projjson, asset=None): self._set_property('proj:projjson', projjson, asset) @property - def geometry(self): + def geometry(self) -> Optional[Dict[str, Any]]: """Get or sets a Polygon GeoJSON dict representing the footprint of this item. This dict should be formatted according the Polygon object format specified in @@ -207,10 +210,10 @@ def geometry(self): return self.get_geometry() @geometry.setter - def geometry(self, v): + def geometry(self, v: Optional[Dict[str, Any]]) -> None: self.set_geometry(v) - def get_geometry(self, asset=None): + def get_geometry(self, asset: Optional[Asset] = None) -> Optional[Dict[str, Any]]: """Gets an Item or an Asset projection geometry. If an Asset is supplied and the Item property exists on the Asset, @@ -224,7 +227,9 @@ def get_geometry(self, asset=None): else: return asset.properties.get('proj:geometry') - def set_geometry(self, geometry, asset=None): + def set_geometry(self, + geometry: Optional[Dict[str, Any]], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset projection geometry. If an Asset is supplied, sets the property on the Asset. @@ -233,7 +238,7 @@ def set_geometry(self, geometry, asset=None): self._set_property('proj:geometry', geometry, asset) @property - def bbox(self): + def bbox(self) -> Optional[List[float]]: """Get or sets the bounding box of the assets represented by this item in the asset data CRS. @@ -250,10 +255,10 @@ def bbox(self): return self.get_bbox() @bbox.setter - def bbox(self, v): + def bbox(self, v: Optional[List[float]]) -> None: self.set_bbox(v) - def get_bbox(self, asset=None): + def get_bbox(self, asset: Optional[Asset] = None) -> Optional[List[float]]: """Gets an Item or an Asset projection bbox. If an Asset is supplied and the Item property exists on the Asset, @@ -267,7 +272,7 @@ def get_bbox(self, asset=None): else: return asset.properties.get('proj:bbox') - def set_bbox(self, bbox, asset=None): + def set_bbox(self, bbox: Optional[List[float]], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset projection bbox. If an Asset is supplied, sets the property on the Asset. @@ -276,7 +281,7 @@ def set_bbox(self, bbox, asset=None): self._set_property('proj:bbox', bbox, asset) @property - def centroid(self): + def centroid(self) -> Optional[Dict[str, float]]: """Get or sets coordinates representing the centroid of the item in the asset data CRS. Coordinates are defined in latitude and longitude, even if the data coordinate system @@ -292,10 +297,10 @@ def centroid(self): return self.get_centroid() @centroid.setter - def centroid(self, v): + def centroid(self, v: Optional[Dict[str, float]]) -> None: self.set_centroid(v) - def get_centroid(self, asset=None): + def get_centroid(self, asset: Optional[Asset] = None) -> Optional[Dict[str, float]]: """Gets an Item or an Asset centroid. If an Asset is supplied and the Item property exists on the Asset, @@ -309,7 +314,7 @@ def get_centroid(self, asset=None): else: return asset.properties.get('proj:centroid') - def set_centroid(self, centroid, asset=None): + def set_centroid(self, centroid: Optional[Dict[str, float]], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset centroid. If an Asset is supplied, sets the property on the Asset. @@ -318,7 +323,7 @@ def set_centroid(self, centroid, asset=None): self._set_property('proj:centroid', centroid, asset) @property - def shape(self): + def shape(self) -> Optional[List[int]]: """Get or sets the number of pixels in Y and X directions for the default grid. The shape is an array of integers that represents the number of pixels in the most @@ -332,10 +337,10 @@ def shape(self): return self.get_shape() @shape.setter - def shape(self, v): + def shape(self, v: Optional[List[int]]) -> None: self.set_shape(v) - def get_shape(self, asset=None): + def get_shape(self, asset: Optional[Asset] = None) -> Optional[List[int]]: """Gets an Item or an Asset shape. If an Asset is supplied and the Item property exists on the Asset, @@ -349,7 +354,7 @@ def get_shape(self, asset=None): else: return asset.properties.get('proj:shape') - def set_shape(self, shape, asset=None): + def set_shape(self, shape: Optional[List[int]], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset shape. If an Asset is supplied, sets the property on the Asset. @@ -358,7 +363,7 @@ def set_shape(self, shape, asset=None): self._set_property('proj:shape', shape, asset) @property - def transform(self): + def transform(self) -> Optional[List[float]]: """Get or sets the the affine transformation coefficients for the default grid. The transform is a linear mapping from pixel coordinate space (Pixel, Line) to @@ -375,10 +380,10 @@ def transform(self): return self.get_transform() @transform.setter - def transform(self, v): + def transform(self, v: Optional[List[float]]) -> None: self.set_transform(v) - def get_transform(self, asset=None): + def get_transform(self, asset: Optional[Asset] = None) -> Optional[List[float]]: """Gets an Item or an Asset transform. If an Asset is supplied and the Item property exists on the Asset, @@ -392,7 +397,7 @@ def get_transform(self, asset=None): else: return asset.properties.get('proj:transform') - def set_transform(self, transform, asset=None): + def set_transform(self, transform: Optional[List[float]], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset transform. If an Asset is supplied, sets the property on the Asset. @@ -401,11 +406,11 @@ def set_transform(self, transform, asset=None): self._set_property('proj:transform', transform, asset) @classmethod - def _object_links(cls): + def _object_links(cls) -> List[str]: return [] @classmethod - def from_item(cls, item): + def from_item(cls, item: Item) -> "ProjectionItemExt": return cls(item) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index c334a11c5..56c40a071 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -4,10 +4,10 @@ """ import enum -from typing import List, Optional, TypeVar +from typing import List, Optional import pystac -from pystac import Extensions +from pystac import Extensions, STACError from pystac.extensions import base # Required @@ -27,32 +27,31 @@ LOOKS_EQUIVALENT_NUMBER: str = 'sar:looks_equivalent_number' OBSERVATION_DIRECTION: str = 'sar:observation_direction' -SarItemExtType = TypeVar('SarItemExtType') - -class FrequencyBand(enum.Enum): - P: str = 'P' - L: str = 'L' - S: str = 'S' - C: str = 'C' - X: str = 'X' - KU: str = 'Ku' - K: str = 'K' - KA: str = 'Ka' +class FrequencyBand(str, enum.Enum): + P = 'P' + L = 'L' + S = 'S' + C = 'C' + X = 'X' + KU = 'Ku' + K = 'K' + KA = 'Ka' class Polarization(enum.Enum): - HH: str = 'HH' - VV: str = 'VV' - HV: str = 'HV' - VH: str = 'VH' + HH = 'HH' + VV = 'VV' + HV = 'HV' + VH = 'VH' class ObservationDirection(enum.Enum): - LEFT: str = 'left' - RIGHT: str = 'right' + LEFT = 'left' + RIGHT = 'right' +# TODO: Fix to work with Assets class SarItemExt(base.ItemExtension): """SarItemExt extends Item to add sar properties to a STAC Item. @@ -139,11 +138,11 @@ def apply(self, self.observation_direction = observation_direction @classmethod - def from_item(cls: SarItemExtType, an_item: pystac.Item) -> SarItemExtType: + def from_item(cls, an_item: pystac.Item) -> "SarItemExt": return cls(an_item) @classmethod - def _object_links(cls) -> List: + def _object_links(cls) -> List[str]: return [] @property @@ -153,7 +152,12 @@ def instrument_mode(self) -> str: Returns: str """ - return self.item.properties.get(INSTRUMENT_MODE) + result = self.item.properties.get(INSTRUMENT_MODE) + if result is None: + raise STACError( + f"Item with sar extension does not have property {INSTRUMENT_MODE}, id {self.item.id}" + ) + return result @instrument_mode.setter def instrument_mode(self, v: str) -> None: @@ -166,7 +170,12 @@ def frequency_band(self) -> FrequencyBand: Returns: FrequencyBand """ - return FrequencyBand(self.item.properties.get(FREQUENCY_BAND)) + result = self.item.properties.get(FREQUENCY_BAND) + if result is None: + raise STACError( + f"Item with sar extension does not have property {FREQUENCY_BAND}, id {self.item.id}" + ) + return FrequencyBand(result) @frequency_band.setter def frequency_band(self, v: FrequencyBand) -> None: @@ -179,7 +188,12 @@ def polarizations(self) -> List[Polarization]: Returns: List[Polarization] """ - return [Polarization(v) for v in self.item.properties.get(POLARIZATIONS)] + result = self.item.properties.get(POLARIZATIONS) + if result is None: + raise STACError( + f"Item with sar extension does not have property {POLARIZATIONS}, id {self.item.id}" + ) + return [Polarization(v) for v in result] @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: @@ -194,14 +208,18 @@ def product_type(self) -> str: Returns: str """ - return self.item.properties.get(PRODUCT_TYPE) + result = self.item.properties.get(PRODUCT_TYPE) + if result is None: + raise STACError( + f"Item with sar extension does not have property {PRODUCT_TYPE}, id {self.item.id}") + return result @product_type.setter def product_type(self, v: str) -> None: self.item.properties[PRODUCT_TYPE] = v @property - def center_frequency(self) -> float: + def center_frequency(self) -> Optional[float]: """Get or sets a center frequency for the item. Returns: @@ -214,7 +232,7 @@ def center_frequency(self, v: float) -> None: self.item.properties[CENTER_FREQUENCY] = v @property - def resolution_range(self) -> float: + def resolution_range(self) -> Optional[float]: """Get or sets a resolution range for the item. Returns: @@ -227,7 +245,7 @@ def resolution_range(self, v: float) -> None: self.item.properties[RESOLUTION_RANGE] = v @property - def resolution_azimuth(self) -> float: + def resolution_azimuth(self) -> Optional[float]: """Get or sets a resolution azimuth for the item. Returns: @@ -240,7 +258,7 @@ def resolution_azimuth(self, v: float) -> None: self.item.properties[RESOLUTION_AZIMUTH] = v @property - def pixel_spacing_range(self) -> float: + def pixel_spacing_range(self) -> Optional[float]: """Get or sets a pixel spacing range for the item. Returns: @@ -253,7 +271,7 @@ def pixel_spacing_range(self, v: float) -> None: self.item.properties[PIXEL_SPACING_RANGE] = v @property - def pixel_spacing_azimuth(self) -> float: + def pixel_spacing_azimuth(self) -> Optional[float]: """Get or sets a pixel spacing azimuth for the item. Returns: @@ -266,7 +284,7 @@ def pixel_spacing_azimuth(self, v: float) -> None: self.item.properties[PIXEL_SPACING_AZIMUTH] = v @property - def looks_range(self) -> int: + def looks_range(self) -> Optional[int]: """Get or sets a looks range for the item. Returns: @@ -279,7 +297,7 @@ def looks_range(self, v: int) -> None: self.item.properties[LOOKS_RANGE] = v @property - def looks_azimuth(self) -> int: + def looks_azimuth(self) -> Optional[int]: """Get or sets a looks azimuth for the item. Returns: @@ -292,7 +310,7 @@ def looks_azimuth(self, v: int) -> None: self.item.properties[LOOKS_AZIMUTH] = v @property - def looks_equivalent_number(self) -> float: + def looks_equivalent_number(self) -> Optional[float]: """Get or sets a looks equivalent number for the item. Returns: @@ -305,13 +323,16 @@ def looks_equivalent_number(self, v: float) -> None: self.item.properties[LOOKS_EQUIVALENT_NUMBER] = v @property - def observation_direction(self) -> ObservationDirection: + def observation_direction(self) -> Optional[ObservationDirection]: """Get or sets an observation direction for the item. Returns: ObservationDirection """ - return ObservationDirection(self.item.properties.get(OBSERVATION_DIRECTION)) + result = self.item.properties.get(OBSERVATION_DIRECTION) + if result is None: + return None + return ObservationDirection(result) @observation_direction.setter def observation_direction(self, v: ObservationDirection) -> None: diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 9406c9ac7..84faf14f9 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -8,7 +8,6 @@ import pystac from pystac import Extensions -from pystac import item from pystac.extensions import base ORBIT_STATE: str = 'sat:orbit_state' @@ -16,9 +15,9 @@ class OrbitState(enum.Enum): - ASCENDING: str = 'ascending' - DESCENDING: str = 'descending' - GEOSTATIONARY: str = 'geostationary' + ASCENDING = 'ascending' + DESCENDING = 'descending' + GEOSTATIONARY = 'geostationary' class SatItemExt(base.ItemExtension): @@ -36,10 +35,10 @@ class SatItemExt(base.ItemExtension): """ item: pystac.Item - def __init__(self, an_item: item.Item) -> None: + def __init__(self, an_item: pystac.Item) -> None: self.item = an_item - def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Optional[str] = None): + def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Optional[int] = None): """Applies ext extension properties to the extended Item. Must specify at least one of orbit_state or relative_orbit. @@ -58,11 +57,11 @@ def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Option self.relative_orbit = relative_orbit @classmethod - def from_item(cls, an_item: item.Item): + def from_item(cls, an_item: pystac.Item): return cls(an_item) @classmethod - def _object_links(cls) -> List: + def _object_links(cls) -> List[str]: return [] @property @@ -74,7 +73,7 @@ def orbit_state(self) -> Optional[OrbitState]: """ if ORBIT_STATE not in self.item.properties: return - return OrbitState(self.item.properties.get(ORBIT_STATE)) + return OrbitState(self.item.properties[ORBIT_STATE]) @orbit_state.setter def orbit_state(self, v: Optional[OrbitState]) -> None: @@ -87,7 +86,7 @@ def orbit_state(self, v: Optional[OrbitState]) -> None: self.item.properties[ORBIT_STATE] = v.value @property - def relative_orbit(self) -> int: + def relative_orbit(self) -> Optional[int]: """Get or sets a relative orbit number of the item. Returns: diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index cb68f4b17..ba044ef22 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,7 +8,7 @@ """ import copy -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from urllib import parse import pystac @@ -37,7 +37,7 @@ def __init__(self, doi: str, citation: str) -> None: self.doi = doi self.citation = citation - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, Publication): return NotImplemented @@ -109,11 +109,11 @@ def from_item(cls, an_item: pystac.Item): return cls(an_item) @classmethod - def _object_links(cls) -> List: + def _object_links(cls) -> List[str]: return [] @property - def doi(self) -> str: + def doi(self) -> Optional[str]: """Get or sets the DOI for the item. Returns: @@ -122,18 +122,19 @@ def doi(self) -> str: return self.item.properties.get(DOI) @doi.setter - def doi(self, v: str) -> None: + def doi(self, v: Optional[str]) -> None: if DOI in self.item.properties: if v == self.item.properties[DOI]: return remove_link(self.item.links, self.item.properties[DOI]) - self.item.properties[DOI] = v - url = doi_to_url(self.doi) - self.item.add_link(pystac.Link(CITE_AS, url)) + if v is not None: + self.item.properties[DOI] = v + url = doi_to_url(v) + self.item.add_link(pystac.Link(CITE_AS, url)) @property - def citation(self) -> str: + def citation(self) -> Optional[str]: """Get or sets the citation for the item. Returns: @@ -142,23 +143,31 @@ def citation(self) -> str: return self.item.properties.get(CITATION) @citation.setter - def citation(self, v: str) -> None: - self.item.properties[CITATION] = v + def citation(self, v: Optional[str]) -> None: + if v is None: + self.item.properties.pop(CITATION, None) + else: + self.item.properties[CITATION] = v @property - def publications(self) -> List[Publication]: + def publications(self) -> Optional[List[Publication]]: """Get or sets the publication list for the item. Returns: List of Publication instances. """ - return [Publication.from_dict(pub) for pub in self.item.properties.get(PUBLICATIONS, [])] + if PUBLICATIONS in self.item.properties: + return [Publication.from_dict(pub) for pub in self.item.properties[PUBLICATIONS]] + return None @publications.setter - def publications(self, v: List[Publication]) -> None: - self.item.properties[PUBLICATIONS] = [pub.to_dict() for pub in v] - for pub in v: - self.item.add_link(pub.get_link()) + def publications(self, v: Optional[List[Publication]]) -> None: + if v is None: + self.item.properties.pop(PUBLICATIONS, None) + else: + self.item.properties[PUBLICATIONS] = [pub.to_dict() for pub in v] + for pub in v: + self.item.add_link(pub.get_link()) # None for publication will clear all. def remove_publication(self, publication: Optional[Publication] = None) -> None: @@ -171,8 +180,10 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: return if not publication: - for one_pub in self.item.ext.scientific.publications: - remove_link(self.item.links, one_pub.doi) + pubs = self.publications + if pubs is not None: + for one_pub in pubs: + remove_link(self.item.links, one_pub.doi) del self.item.properties[PUBLICATIONS] return @@ -201,7 +212,7 @@ class ScientificCollectionExt(base.CollectionExtension): """ collection: pystac.Collection - def __init__(self, a_collection): + def __init__(self, a_collection: pystac.Collection): self.collection = a_collection def apply(self, @@ -228,11 +239,11 @@ def from_collection(cls, a_collection: pystac.Collection): return cls(a_collection) @classmethod - def _object_links(cls) -> List: + def _object_links(cls) -> List[str]: return [] @property - def doi(self) -> str: + def doi(self) -> Optional[str]: """Get or sets the DOI for the collection. Returns: @@ -241,17 +252,18 @@ def doi(self) -> str: return self.collection.extra_fields.get(DOI) @doi.setter - def doi(self, v: str) -> None: + def doi(self, v: Optional[str]) -> None: if DOI in self.collection.extra_fields: if v == self.collection.extra_fields[DOI]: return remove_link(self.collection.links, self.collection.extra_fields[DOI]) - self.collection.extra_fields[DOI] = v - url = doi_to_url(self.doi) - self.collection.add_link(pystac.Link(CITE_AS, url)) + if v is not None: + self.collection.extra_fields[DOI] = v + url = doi_to_url(v) + self.collection.add_link(pystac.Link(CITE_AS, url)) @property - def citation(self) -> str: + def citation(self) -> Optional[str]: """Get or sets the citation for the collection. Returns: @@ -260,25 +272,31 @@ def citation(self) -> str: return self.collection.extra_fields.get(CITATION) @citation.setter - def citation(self, v: str) -> None: - self.collection.extra_fields[CITATION] = v + def citation(self, v: Optional[str]) -> None: + if v is None: + self.collection.extra_fields.pop(CITATION, None) + else: + self.collection.extra_fields[CITATION] = v @property - def publications(self) -> List[Publication]: + def publications(self) -> Optional[List[Publication]]: """Get or sets the publication list for the collection. Returns: List of Publication instances. """ - return [ - Publication.from_dict(p) for p in self.collection.extra_fields.get(PUBLICATIONS, []) - ] + if PUBLICATIONS in self.collection.extra_fields: + return [Publication.from_dict(p) for p in self.collection.extra_fields[PUBLICATIONS]] + return None @publications.setter - def publications(self, v: List[Publication]) -> None: - self.collection.extra_fields[PUBLICATIONS] = [pub.to_dict() for pub in v] - for pub in v: - self.collection.add_link(pub.get_link()) + def publications(self, v: Optional[List[Publication]]) -> None: + if v is None: + self.collection.extra_fields.pop(PUBLICATIONS, None) + else: + self.collection.extra_fields[PUBLICATIONS] = [pub.to_dict() for pub in v] + for pub in v: + self.collection.add_link(pub.get_link()) # None for publication will clear all. def remove_publication(self, publication: Optional[Publication] = None) -> None: @@ -291,7 +309,7 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: return if not publication: - for one_pub in self.collection.ext.scientific.publications: + for one_pub in self.publications: remove_link(self.collection.links, one_pub.doi) del self.collection.extra_fields[PUBLICATIONS] diff --git a/pystac/extensions/single_file_stac.py b/pystac/extensions/single_file_stac.py index 361d0da53..f940e67b4 100644 --- a/pystac/extensions/single_file_stac.py +++ b/pystac/extensions/single_file_stac.py @@ -1,3 +1,5 @@ +from pystac.item import Item +from typing import List, Optional, cast from pystac.catalog import Catalog import pystac @@ -6,7 +8,7 @@ from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) -def create_single_file_stac(catalog): +def create_single_file_stac(catalog: Catalog): """Creates a Single File STAC from a STAC catalog. This method will recursively collect any collections and items in the catalog @@ -18,7 +20,7 @@ def create_single_file_stac(catalog): All links in the items and collections will be cleared in the Single File STAC. Args: - catalog (Catalog): Catalog to walk while constructin the Single File STAC + catalog (Catalog): Catalog to walk while constructing the Single File STAC """ collections = {} items = [] @@ -41,7 +43,8 @@ def create_single_file_stac(catalog): result = catalog.clone() result.clear_links() result.ext.enable(Extensions.SINGLE_FILE_STAC) - result.ext[Extensions.SINGLE_FILE_STAC].apply(features=items, collections=filtered_collections) + sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[Extensions.SINGLE_FILE_STAC]) + sfs_ext.apply(features=items, collections=filtered_collections) return result @@ -62,15 +65,15 @@ class SingleFileSTACCatalogExt(CatalogExtension): Using SingleFileSTACCatalogExt to directly wrap a Catalog will add the 'proj' extension ID to the catalog's stac_extensions. """ - def __init__(self, catalog): + def __init__(self, catalog: Catalog): if catalog.stac_extensions is None: - catalog.stac_extensions = [Extensions.SINGLE_FILE_STAC] - elif Extensions.SINGLE_FILE_STAC not in catalog.stac_extensions: - catalog.stac_extensions.append(Extensions.SINGLE_FILE_STAC) + catalog.stac_extensions = [str(Extensions.SINGLE_FILE_STAC)] + elif str(Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: + catalog.stac_extensions.append(str(Extensions.SINGLE_FILE_STAC)) self.catalog = catalog - def apply(self, features, collections=None): + def apply(self, features: List[Item], collections: Optional[List[Collection]] = None): """ Args: features (List[Item]): List of items contained by @@ -82,12 +85,12 @@ def apply(self, features, collections=None): self.collections = collections @classmethod - def enable_extension(cls, catalog): + def enable_extension(cls, catalog: Catalog): # Ensure the 'type' property is correct so that the Catalog is valid GeoJSON. catalog.extra_fields['type'] = 'FeatureCollection' @property - def features(self): + def features(self) -> List[Item]: """Get or sets a list of :class:`~pystac.Item` contained in this Single File STAC. Returns: @@ -97,39 +100,37 @@ def features(self): if features is None: raise STACError('Invalid Single File STAC: does not have "features" property.') - return [pystac.read_dict(feature) for feature in features] + return [Item.from_dict(feature) for feature in features] @features.setter - def features(self, v): + def features(self, v: List[Item]) -> None: self.catalog.extra_fields['features'] = [item.to_dict() for item in v] @property - def collections(self): + def collections(self) -> Optional[List[Collection]]: """Get or sets a list of :class:`~pystac.Collection` objects contained in this Single File STAC. - - Returns: - List[Band] """ collections = self.catalog.extra_fields.get('collections') - if collections is not None: - collections = [pystac.read_dict(col) for col in collections] - return collections + if collections is None: + return None + else: + return [Collection.from_dict(col) for col in collections] @collections.setter - def collections(self, v): + def collections(self, v: Optional[List[Collection]]) -> None: if v is not None: self.catalog.extra_fields['collections'] = [col.to_dict() for col in v] else: self.catalog.extra_fields.pop('collections', None) @classmethod - def _object_links(cls): + def _object_links(cls) -> List[str]: return [] @classmethod - def from_catalog(cls, catalog): + def from_catalog(cls, catalog: Catalog): return SingleFileSTACCatalogExt(catalog) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index d72ac746d..4801029c4 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -1,7 +1,10 @@ +from datetime import datetime as Datetime +from typing import List, Optional + import pystac from pystac import Extensions from pystac.extensions.base import (ExtendedObject, ExtensionDefinition, ItemExtension) -from pystac.item import Item +from pystac.item import Asset, Item from pystac.utils import datetime_to_str, str_to_datetime @@ -19,23 +22,26 @@ class TimestampsItemExt(ItemExtension): Using TimestampsItemExt to directly wrap an item will add the 'timestamps' extension ID to the item's stac_extensions. """ - def __init__(self, item): + def __init__(self, item: Item) -> None: if item.stac_extensions is None: - item.stac_extensions = [Extensions.TIMESTAMPS] - elif Extensions.TIMESTAMPS not in item.stac_extensions: - item.stac_extensions.append(Extensions.TIMESTAMPS) + item.stac_extensions = [str(Extensions.TIMESTAMPS)] + elif str(Extensions.TIMESTAMPS) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.TIMESTAMPS)) self.item = item @classmethod - def from_item(cls, item): + def from_item(cls, item: Item) -> "TimestampsItemExt": return cls(item) @classmethod - def _object_links(cls): + def _object_links(cls) -> List[str]: return [] - def apply(self, published=None, expires=None, unpublished=None): + def apply(self, + published: Optional[Datetime] = None, + expires: Optional[Datetime] = None, + unpublished: Optional[Datetime] = None): """Applies timestamps extension properties to the extended Item. Args: @@ -53,7 +59,7 @@ def apply(self, published=None, expires=None, unpublished=None): self.expires = expires self.unpublished = unpublished - def _timestamp_getter(self, key, asset=None): + def _timestamp_getter(self, key: str, asset: Optional[Asset] = None) -> Optional[Datetime]: if asset is not None and key in asset.properties: timestamp_str = asset.properties.get(key) else: @@ -65,13 +71,18 @@ def _timestamp_getter(self, key, asset=None): return timestamp - def _timestamp_setter(self, timestamp, key, asset=None): + def _timestamp_setter(self, + timestamp: Optional[Datetime], + key: str, + asset: Optional[Asset] = None): if timestamp is not None: - timestamp = datetime_to_str(timestamp) - self._set_property(key, timestamp, asset) + value = datetime_to_str(timestamp) + else: + value = None + self._set_property(key, value, asset) @property - def published(self): + def published(self) -> Optional[Datetime]: """Get or sets a datetime objects that represent the date and time that the corresponding data was published the first time. @@ -82,10 +93,10 @@ def published(self): return self.get_published() @published.setter - def published(self, v): + def published(self, v: Optional[Datetime]) -> None: self.set_published(v) - def get_published(self, asset=None): + def get_published(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Get an Item or Asset published datetime If an Asset is supplied and the published property exists on the Asset, @@ -100,7 +111,7 @@ def get_published(self, asset=None): """ return self._timestamp_getter('published', asset) - def set_published(self, published, asset=None): + def set_published(self, published: Optional[Datetime], asset: Optional[Asset] = None) -> None: """Set an Item or asset published datetime If an Asset is supplied, sets the property on the Asset. @@ -109,7 +120,7 @@ def set_published(self, published, asset=None): self._timestamp_setter(published, 'published', asset) @property - def expires(self): + def expires(self) -> Optional[Datetime]: """Get or sets a datetime objects that represent the date and time the corresponding data expires (is not valid any longer). @@ -120,10 +131,10 @@ def expires(self): return self.get_expires() @expires.setter - def expires(self, v): + def expires(self, v: Optional[Datetime]) -> None: self.set_expires(v) - def get_expires(self, asset=None): + def get_expires(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Get an Item or Asset expires datetime If an Asset is supplied and the expires property exists on the Asset, @@ -138,7 +149,7 @@ def get_expires(self, asset=None): """ return self._timestamp_getter('expires', asset) - def set_expires(self, expires, asset=None): + def set_expires(self, expires: Optional[Datetime], asset: Optional[Asset] = None) -> None: """Set an Item or asset expires datetime If an Asset is supplied, sets the property on the Asset. @@ -147,7 +158,7 @@ def set_expires(self, expires, asset=None): self._timestamp_setter(expires, 'expires', asset) @property - def unpublished(self): + def unpublished(self) -> Optional[Datetime]: """Get or sets a datetime objects that represent the Date and time the corresponding data was unpublished. @@ -157,10 +168,10 @@ def unpublished(self): return self.get_unpublished() @unpublished.setter - def unpublished(self, v): + def unpublished(self, v: Optional[Datetime]) -> None: self.set_unpublished(v) - def get_unpublished(self, asset=None): + def get_unpublished(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Get an Item or Asset unpublished datetime If an Asset is supplied and the unpublished property exists on the Asset, @@ -175,7 +186,9 @@ def get_unpublished(self, asset=None): """ return self._timestamp_getter('unpublished', asset) - def set_unpublished(self, unpublished, asset=None): + def set_unpublished(self, + unpublished: Optional[Datetime], + asset: Optional[Asset] = None) -> None: """Set an Item or asset unpublished datetime If an Asset is supplied, sets the property on the Asset. diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 9456c7c09..ebb89ddf0 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -5,10 +5,10 @@ Note that the version/schema.json does not know about the links. """ -from typing import List, Optional +from typing import List, Optional, cast import pystac -from pystac import Extensions +from pystac import Extensions, STACError from pystac.extensions import base # STAC fields - These are unusual for an extension in that they do not have @@ -78,7 +78,10 @@ def version(self) -> str: Returns: str """ - return self.item.properties.get(VERSION) + result = self.item.properties.get(VERSION) + if result is None: + raise STACError(f"Item {self.item.id} has version extension but no version property") + return result @version.setter def version(self, v: str) -> None: @@ -106,7 +109,10 @@ def latest(self) -> Optional[pystac.Item]: Returns: Item or None """ - return next(self.item.get_stac_objects(LATEST), None) + result = next(self.item.get_stac_objects(LATEST), None) + if result is None: + return None + return cast(pystac.Item, result) @latest.setter def latest(self, source_item: pystac.Item) -> None: @@ -121,7 +127,10 @@ def predecessor(self) -> Optional[pystac.Item]: Returns: Item or None """ - return next(self.item.get_stac_objects(PREDECESSOR), None) + result = next(self.item.get_stac_objects(PREDECESSOR), None) + if result is None: + return None + return cast(pystac.Item, result) @predecessor.setter def predecessor(self, source_item: pystac.Item) -> None: @@ -136,7 +145,10 @@ def successor(self) -> Optional[pystac.Item]: Returns: Item or None """ - return next(self.item.get_stac_objects(SUCCESSOR), None) + result = next(self.item.get_stac_objects(SUCCESSOR), None) + if result is None: + return None + return cast(pystac.Item, result) @successor.setter def successor(self, source_item: pystac.Item) -> None: @@ -179,10 +191,13 @@ def version(self) -> str: Returns: str """ - return self.collection.extra_fields.get(VERSION) + result = self.collection.extra_fields.get(VERSION) + if result is None: + raise STACError(f"Collection {self.collection.id} does not have property {VERSION}") + return result @version.setter - def version(self, v) -> None: + def version(self, v: str) -> None: self.collection.extra_fields[VERSION] = v @property @@ -207,10 +222,13 @@ def latest(self) -> Optional[pystac.Collection]: Returns: Collection or None """ - return next(self.collection.get_stac_objects(LATEST), None) + result = next(self.collection.get_stac_objects(LATEST), None) + if result is None: + return None + return cast(pystac.Collection, result) @latest.setter - def latest(self, source_collection) -> None: + def latest(self, source_collection: pystac.Collection) -> None: self.collection.clear_links(LATEST) if source_collection: self.collection.add_link(pystac.Link(LATEST, source_collection, MEDIA_TYPE)) @@ -222,10 +240,13 @@ def predecessor(self) -> Optional[pystac.Collection]: Returns: Collection or None """ - return next(self.collection.get_stac_objects(PREDECESSOR), None) + result = next(self.collection.get_stac_objects(PREDECESSOR), None) + if result is None: + return None + return cast(pystac.Collection, result) @predecessor.setter - def predecessor(self, source_collection) -> None: + def predecessor(self, source_collection: pystac.Collection) -> None: self.collection.clear_links(PREDECESSOR) if source_collection: self.collection.add_link(pystac.Link(PREDECESSOR, source_collection, MEDIA_TYPE)) @@ -237,10 +258,13 @@ def successor(self) -> Optional[pystac.Collection]: Returns: Collection or None """ - return next(self.collection.get_stac_objects(SUCCESSOR), None) + result = next(self.collection.get_stac_objects(SUCCESSOR), None) + if result is None: + return None + return cast(pystac.Collection, result) @successor.setter - def successor(self, source_collection) -> None: + def successor(self, source_collection: pystac.Collection) -> None: self.collection.clear_links(SUCCESSOR) if source_collection: self.collection.add_link(pystac.Link(SUCCESSOR, source_collection, MEDIA_TYPE)) diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 0b08244df..9d8554ebd 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -1,8 +1,8 @@ import pystac from pystac import Extensions -from pystac.item import Item +from pystac.item import Asset, Item from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) -from typing import Optional +from typing import List, Optional class ViewItemExt(ItemExtension): @@ -21,11 +21,11 @@ class ViewItemExt(ItemExtension): Using ViewItemExt to directly wrap an item will add the 'view' extension ID to the item's stac_extensions. """ - def __init__(self, item): + def __init__(self, item: Item) -> None: if item.stac_extensions is None: - item.stac_extensions = [Extensions.VIEW] - elif Extensions.VIEW not in item.stac_extensions: - item.stac_extensions.append(Extensions.VIEW) + item.stac_extensions = [str(Extensions.VIEW)] + elif str(Extensions.VIEW) not in item.stac_extensions: + item.stac_extensions.append(str(Extensions.VIEW)) self.item = item @@ -68,7 +68,7 @@ def apply(self, self.sun_elevation = sun_elevation @property - def off_nadir(self): + def off_nadir(self) -> Optional[float]: """Get or sets the angle from the sensor between nadir (straight down) and the scene center. Measured in degrees (0-90). @@ -78,10 +78,10 @@ def off_nadir(self): return self.get_off_nadir() @off_nadir.setter - def off_nadir(self, v): + def off_nadir(self, v: Optional[float]): self.set_off_nadir(v) - def get_off_nadir(self, asset=None): + def get_off_nadir(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset off_nadir. If an Asset is supplied and the Item property exists on the Asset, @@ -95,7 +95,7 @@ def get_off_nadir(self, asset=None): else: return asset.properties.get('view:off_nadir') - def set_off_nadir(self, off_nadir, asset=None): + def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[Asset] = None): """Set an Item or an Asset off_nadir. If an Asset is supplied, sets the property on the Asset. @@ -104,7 +104,7 @@ def set_off_nadir(self, off_nadir, asset=None): self._set_property('view:off_nadir', off_nadir, asset) @property - def incidence_angle(self): + def incidence_angle(self) -> Optional[float]: """Get or sets the incidence angle is the angle between the vertical (normal) to the intercepting surface and the line of sight back to the satellite at the scene center. Measured in degrees (0-90). @@ -115,10 +115,10 @@ def incidence_angle(self): return self.get_incidence_angle() @incidence_angle.setter - def incidence_angle(self, v): + def incidence_angle(self, v: Optional[float]) -> None: self.set_incidence_angle(v) - def get_incidence_angle(self, asset=None): + def get_incidence_angle(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset incidence_angle. If an Asset is supplied and the Item property exists on the Asset, @@ -132,7 +132,7 @@ def get_incidence_angle(self, asset=None): else: return asset.properties.get('view:incidence_angle') - def set_incidence_angle(self, incidence_angle, asset=None): + def set_incidence_angle(self, incidence_angle: Optional[float], asset: Optional[Asset] = None): """Set an Item or an Asset incidence_angle. If an Asset is supplied, sets the property on the Asset. @@ -141,7 +141,7 @@ def set_incidence_angle(self, incidence_angle, asset=None): self._set_property('view:incidence_angle', incidence_angle, asset) @property - def azimuth(self): + def azimuth(self) -> Optional[float]: """Get or sets the viewing azimuth angle. The angle measured from the sub-satellite point (point on the ground below the platform) between the scene center and true north. Measured clockwise from north in degrees (0-360). @@ -152,10 +152,10 @@ def azimuth(self): return self.get_azimuth() @azimuth.setter - def azimuth(self, v): + def azimuth(self, v: Optional[float]) -> None: self.set_azimuth(v) - def get_azimuth(self, asset=None): + def get_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset azimuth. If an Asset is supplied and the Item property exists on the Asset, @@ -169,7 +169,7 @@ def get_azimuth(self, asset=None): else: return asset.properties.get('view:azimuth') - def set_azimuth(self, azimuth, asset=None): + def set_azimuth(self, azimuth: Optional[float], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset azimuth. If an Asset is supplied, sets the property on the Asset. @@ -178,7 +178,7 @@ def set_azimuth(self, azimuth, asset=None): self._set_property('view:azimuth', azimuth, asset) @property - def sun_azimuth(self): + def sun_azimuth(self) -> Optional[float]: """Get or sets the sun azimuth angle. From the scene center point on the ground, this is the angle between truth north and the sun. Measured clockwise in degrees (0-360). @@ -188,10 +188,10 @@ def sun_azimuth(self): return self.get_sun_azimuth() @sun_azimuth.setter - def sun_azimuth(self, v): + def sun_azimuth(self, v: Optional[float]) -> None: self.set_sun_azimuth(v) - def get_sun_azimuth(self, asset=None): + def get_sun_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset sun_azimuth. If an Asset is supplied and the Item property exists on the Asset, @@ -205,7 +205,7 @@ def get_sun_azimuth(self, asset=None): else: return asset.properties.get('view:sun_azimuth') - def set_sun_azimuth(self, sun_azimuth, asset=None): + def set_sun_azimuth(self, sun_azimuth: Optional[float], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset sun_azimuth. If an Asset is supplied, sets the property on the Asset. @@ -214,7 +214,7 @@ def set_sun_azimuth(self, sun_azimuth, asset=None): self._set_property('view:sun_azimuth', sun_azimuth, asset) @property - def sun_elevation(self): + def sun_elevation(self) -> Optional[float]: """Get or sets the sun elevation angle. The angle from the tangent of the scene center point to the sun. Measured from the horizon in degrees (0-90). @@ -224,10 +224,10 @@ def sun_elevation(self): return self.get_sun_elevation() @sun_elevation.setter - def sun_elevation(self, v): + def sun_elevation(self, v: Optional[float]) -> None: self.set_sun_elevation(v) - def get_sun_elevation(self, asset=None): + def get_sun_elevation(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset sun_elevation. If an Asset is supplied and the Item property exists on the Asset, @@ -241,7 +241,9 @@ def get_sun_elevation(self, asset=None): else: return asset.properties.get('view:sun_elevation') - def set_sun_elevation(self, sun_elevation, asset=None): + def set_sun_elevation(self, + sun_elevation: Optional[float], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset sun_elevation. If an Asset is supplied, sets the property on the Asset. @@ -250,11 +252,11 @@ def set_sun_elevation(self, sun_elevation, asset=None): self._set_property('view:sun_elevation', sun_elevation, asset) @classmethod - def _object_links(cls): + def _object_links(cls) -> List[str]: return [] @classmethod - def from_item(cls, item): + def from_item(cls, item: Item) -> "ViewItemExt": return cls(item) diff --git a/pystac/item.py b/pystac/item.py index 3de409b1b..affa49ce2 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -1,4 +1,7 @@ from copy import copy, deepcopy +from datetime import datetime as Datetime +from pystac.catalog import Catalog +from typing import Any, Dict, List, Optional, Union, cast import dateutil.parser @@ -11,1022 +14,1045 @@ from pystac.collection import Collection, Provider -class Item(STACObject): - """An Item is the core granular entity in a STAC, containing the core metadata - that enables any client to search or crawl online catalogs of spatial 'assets' - - satellite imagery, derived data, DEM's, etc. +class CommonMetadata: + """Object containing fields that are not included in core item schema but + are still commonly used. All attributes are defined within the properties of + this item and are optional Args: - id (str): Provider identifier. Must be unique within the STAC. - geometry (dict): Defines the full footprint of the asset represented by this item, - formatted according to `RFC 7946, section 3.1 (GeoJSON) - `_. - bbox (List[float] or None): Bounding Box of the asset represented by this item using - either 2D or 3D geometries. The length of the array must be 2*n where n is the - number of dimensions. Could also be None in the case of a null geometry. - datetime (datetime or None): Datetime associated with this item. If None, - a start_datetime and end_datetime must be supplied in the properties. - properties (dict): A dictionary of additional metadata for the item. - stac_extensions (List[str]): Optional list of extensions the Item implements. - href (str or None): Optional HREF for this item, which be set as the item's - self link's HREF. - collection (Collection or str): The Collection or Collection ID that this item - belongs to. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Item. - - Attributes: - id (str): Provider identifier. Unique within the STAC. - geometry (dict): Defines the full footprint of the asset represented by this item, - formatted according to `RFC 7946, section 3.1 (GeoJSON) - `_. - bbox (List[float] or None): Bounding Box of the asset represented by this item using - either 2D or 3D geometries. The length of the array is 2*n where n is the - number of dimensions. Could also be None in the case of a null geometry. - datetime (datetime or None): Datetime associated with this item. If None, - the start_datetime and end_datetime in the common_metadata - will supply the datetime range of the Item. - properties (dict): A dictionary of additional metadata for the item. - stac_extensions (List[str] or None): Optional list of extensions the Item implements. - collection (Collection or None): Collection that this item is a part of. - links (List[Link]): A list of :class:`~pystac.Link` objects representing - all links associated with this STACObject. - assets (Dict[str, Asset]): Dictionary of asset objects that can be downloaded, - each with a unique key. - collection_id (str or None): The Collection ID that this item belongs to, if any. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Item. + properties (dict): Dictionary of attributes that is the Item's properties """ - - STAC_OBJECT_TYPE = STACObjectType.ITEM - - def __init__(self, - id, - geometry, - bbox, - datetime, - properties, - stac_extensions=None, - href=None, - collection=None, - extra_fields=None): - super().__init__(stac_extensions) - - self.id = id - self.geometry = geometry - self.bbox = bbox - self.datetime = datetime + def __init__(self, properties: Dict[str, Any]): self.properties = properties - if extra_fields is None: - self.extra_fields = {} - else: - self.extra_fields = extra_fields - self.assets = {} + # Basics + @property + def title(self) -> Optional[str]: + """Get or set the item's title - if datetime is None: - if 'start_datetime' not in properties or \ - 'end_datetime' not in properties: - raise STACError('Invalid Item: If datetime is None, ' - 'a start_datetime and end_datetime ' - 'must be supplied in ' - 'the properties.') + Returns: + str: Human readable title describing the item + """ + return self.properties.get('title') - if href is not None: - self.set_self_href(href) + @title.setter + def title(self, v: Optional[str]): + self.properties['title'] = v - if collection is not None: - if isinstance(collection, Collection): - self.set_collection(collection) - else: - self.collection_id = collection - else: - self.collection_id = None + @property + def description(self) -> Optional[str]: + """Get or set the item's description - def __repr__(self): - return ''.format(self.id) + Returns: + str: Detailed description of the item + """ + return self.properties.get('description') - def set_self_href(self, href): - """Sets the absolute HREF that is represented by the ``rel == 'self'`` - :class:`~pystac.Link`. + @description.setter + def description(self, v: Optional[str]): + self.properties['description'] = v - Changing the self HREF of the item will ensure that all asset HREFs - remain valid. If asset HREFs are relative, the HREFs will change - to point to the same location based on the new item self HREF, - either by making them relative to the new location or making them - absolute links if the new location does not share the same protocol - as the old location. + # Date and Time Range + @property + def start_datetime(self) -> Optional[Datetime]: + """Get or set the item's start_datetime. - Args: - href (str): The absolute HREF of this object. If the given HREF - is not absolute, it will be transformed to an absolute - HREF based on the current working directory. If this is None - the call will clear the self HREF link. + Returns: + datetime: Start date and time for the item """ - prev_href = self.get_self_href() - super().set_self_href(href) - new_href = self.get_self_href() # May have been made absolute. + return self.get_start_datetime() - if prev_href is not None and new_href is not None: - # Make sure relative asset links remain valid. - for asset in self.assets.values(): - asset_href = asset.href - if not is_absolute_href(asset_href): - abs_href = make_absolute_href(asset_href, prev_href) - new_relative_href = make_relative_href(abs_href, new_href) - asset.href = new_relative_href + @start_datetime.setter + def start_datetime(self, v: Optional[Datetime]) -> None: + self.set_start_datetime(v) - def get_datetime(self, asset=None): - """Gets an Item or an Asset datetime. + def get_start_datetime(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + """Gets an Item or an Asset start_datetime. If an Asset is supplied and the Item 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 Returns: - datetime or None + datetime """ - if asset is None or 'datetime' not in asset.properties: - return self.datetime + if asset is None or 'start_datetime' not in asset.properties: + start_datetime = self.properties.get('start_datetime') else: - return str_to_datetime(asset.properties.get('datetime')) + start_datetime = asset.properties.get('start_datetime') - def set_datetime(self, datetime, asset=None): - """Set an Item or an Asset datetime. + if start_datetime: + start_datetime = str_to_datetime(start_datetime) + + return start_datetime + + def set_start_datetime(self, + start_datetime: Optional[Datetime], + asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset start_datetime. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.datetime = datetime + self.properties['start_datetime'] = None if start_datetime is None else datetime_to_str( + start_datetime) else: - asset.properties['datetime'] = datetime_to_str(datetime) + asset.properties[ + 'start_datetime'] = None if start_datetime is None else datetime_to_str( + start_datetime) - def get_assets(self): - """Get this item's assets. + @property + def end_datetime(self) -> Optional[Datetime]: + """Get or set the item's end_datetime. All datetime attributes have + setters that can take either a string or a datetime, but always stores + the attribute as a string Returns: - Dict[str, Asset]: A copy of the dictionary of this item's assets. + datetime: End date and time for the item """ - return dict(self.assets.items()) + return self.get_end_datetime() - def add_asset(self, key, asset): - """Adds an Asset to this item. + @end_datetime.setter + def end_datetime(self, v: Optional[Datetime]) -> None: + self.set_end_datetime(v) - Args: - key (str): The unique key of this asset. - asset (Asset): The Asset to add. - """ - asset.set_owner(self) - self.assets[key] = asset - return self + def get_end_datetime(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + """Gets an Item or an Asset end_datetime. - def make_asset_hrefs_relative(self): - """Modify each asset's HREF to be relative to this item's self HREF. + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value Returns: - Item: self + datetime """ + if asset is None or 'end_datetime' not in asset.properties: + end_datetime = self.properties.get('end_datetime') + else: + end_datetime = asset.properties.get('end_datetime') - self_href = None - for asset in self.assets.values(): - href = asset.href - if is_absolute_href(href): - if self_href is None: - self_href = self.get_self_href() - if self_href is None: - raise STACError('Cannot make asset HREFs relative ' - 'if no self_href is set.') - asset.href = make_relative_href(asset.href, self_href) - return self + if end_datetime: + end_datetime = str_to_datetime(end_datetime) - def make_asset_hrefs_absolute(self): - """Modify each asset's HREF to be absolute. + return end_datetime - Any asset HREFs that are relative will be modified to absolute based on this - item's self HREF. + def set_end_datetime(self, + end_datetime: Optional[Datetime], + asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset end_datetime. - Returns: - Item: self + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. """ - self_href = None - for asset in self.assets.values(): - href = asset.href - if not is_absolute_href(href): - if self_href is None: - self_href = self.get_self_href() - if self_href is None: - raise STACError('Cannot make relative asset HREFs absolute ' - 'if no self_href is set.') - asset.href = make_absolute_href(asset.href, self_href) - - return self - - def set_collection(self, collection): - """Set the collection of this item. - - This method will replace any existing Collection link and attribute for - this item. + if asset is None: + self.properties['end_datetime'] = None if end_datetime is None else datetime_to_str( + end_datetime) + else: + asset.properties['end_datetime'] = None if end_datetime is None else datetime_to_str( + end_datetime) - Args: - collection (Collection or None): The collection to set as this - item's collection. If None, will clear the collection. + # License + @property + def license(self) -> Optional[str]: + """Get or set the current license Returns: - Item: self + str: Item's license(s), either SPDX identifier of 'various' """ - self.remove_links('collection') - self.collection_id = None - if collection is not None: - self.add_link(Link.collection(collection)) - self.collection_id = collection.id + return self.get_license() - return self + @license.setter + def license(self, v: Optional[str]) -> None: + self.set_license(v) - def get_collection(self): - """Gets the collection of this item, if one exists. + def get_license(self, asset: Optional["Asset"] = None) -> Optional[str]: + """Gets an Item or an Asset license. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value Returns: - Collection or None: If this item belongs to a collection, returns - a reference to the collection. Otherwise returns None. + str """ - collection_link = self.get_single_link('collection') - if collection_link is None: - return None + if asset is None or 'license' not in asset.properties: + return self.properties.get('license') else: - return collection_link.resolve_stac_object().target - - def to_dict(self, include_self_link=True): - links = self.links - if not include_self_link: - links = filter(lambda x: x.rel != 'self', links) + return asset.properties.get('license') - assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items())) + def set_license(self, license: Optional[str], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset license. - if self.datetime is not None: - self.properties['datetime'] = datetime_to_str(self.datetime) - else: - self.properties['datetime'] = None + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + if asset is None: + self.properties['license'] = license + else: + asset.properties['license'] = license - d = { - 'type': 'Feature', - 'stac_version': pystac.get_stac_version(), - 'id': self.id, - 'properties': self.properties, - 'geometry': self.geometry, - 'links': [link.to_dict() for link in links], - 'assets': assets - } + # Providers + @property + def providers(self) -> Optional[List[Provider]]: + """Get or set a list of the item's providers. The setter can take either + a Provider object or a dict but always stores each provider as a dict - if self.bbox is not None: - d['bbox'] = self.bbox + Returns: + List[Provider]: List of organizations that captured or processed the data, + encoded as Provider objects + """ + return self.get_providers() - if self.stac_extensions is not None: - d['stac_extensions'] = self.stac_extensions + @providers.setter + def providers(self, v: Optional[List[Provider]]) -> None: + self.set_providers(v) - if self.collection_id: - d['collection'] = self.collection_id + def get_providers(self, asset: Optional["Asset"] = None) -> Optional[List[Provider]]: + """Gets an Item or an Asset providers. - for key in self.extra_fields: - d[key] = self.extra_fields[key] + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value - return d + Returns: + List[Provider] + """ + if asset is None or 'providers' not in asset.properties: + providers = self.properties.get('providers') + else: + providers = asset.properties.get('providers') - def clone(self): - clone = Item(id=self.id, - geometry=deepcopy(self.geometry), - bbox=copy(self.bbox), - datetime=copy(self.datetime), - properties=deepcopy(self.properties), - stac_extensions=deepcopy(self.stac_extensions), - collection=self.collection_id) - for link in self.links: - clone.add_link(link.clone()) + if providers is not None: + providers = [Provider.from_dict(d) for d in providers] - for k, asset in self.assets.items(): - clone.add_asset(k, asset.clone()) + return providers - return clone + def set_providers(self, + providers: Optional[List[Provider]], + asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset providers. - def _object_links(self): - return ['collection'] + (pystac.STAC_EXTENSIONS.get_extended_object_links(self)) + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + providers_dicts = [d.to_dict() for d in providers] + if asset is None: + self.properties['providers'] = providers_dicts + else: + asset.properties['providers'] = providers_dicts - @classmethod - def from_dict(cls, d, href=None, root=None): - d = deepcopy(d) - id = d.pop('id') - geometry = d.pop('geometry') - properties = d.pop('properties') - bbox = d.pop('bbox', None) - stac_extensions = d.get('stac_extensions') - collection_id = d.pop('collection', None) + # Instrument + @property + def platform(self) -> Optional[str]: + """Get or set the item's platform attribute - datetime = properties.get('datetime') - if datetime is not None: - datetime = dateutil.parser.parse(datetime) - links = d.pop('links') - assets = d.pop('assets') + Returns: + str: Unique name of the specific platform to which the instrument + is attached + """ + return self.get_platform() - d.pop('type') - d.pop('stac_version') + @platform.setter + def platform(self, v: Optional[str]) -> None: + self.set_platform(v) - item = Item(id=id, - geometry=geometry, - bbox=bbox, - datetime=datetime, - properties=properties, - stac_extensions=stac_extensions, - collection=collection_id, - extra_fields=d) + def get_platform(self, asset: Optional["Asset"] = None) -> Optional[str]: + """Gets an Item or an Asset platform. - has_self_link = False - for link in links: - has_self_link |= link['rel'] == 'self' - item.add_link(Link.from_dict(link)) + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value - if not has_self_link and href is not None: - item.add_link(Link.self_href(href)) + Returns: + str + """ + if asset is None or 'platform' not in asset.properties: + return self.properties.get('platform') + else: + return asset.properties.get('platform') - for k, v in assets.items(): - asset = Asset.from_dict(v) - asset.set_owner(item) - item.assets[k] = asset + def set_platform(self, platform: Optional[str], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset platform. - return item + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + if asset is None: + self.properties['platform'] = platform + else: + asset.properties['platform'] = platform @property - def common_metadata(self): - """Access the item's common metadat fields as a CommonMetadata object + def instruments(self) -> Optional[List[str]]: + """Get or set the names of the instruments used Returns: - CommonMetada: contains all common metadata fields in the items properties + List[str]: Name(s) of instrument(s) used """ - return CommonMetadata(self.properties) - + return self.get_instruments() -class Asset: - """An object that contains a link to data associated with the Item that can be - downloaded or streamed. + @instruments.setter + def instruments(self, v: Optional[List[str]]) -> None: + self.set_instruments(v) - Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) - of the asset. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. + def get_instruments(self, asset: Optional["Asset"] = None) -> Optional[List[str]]: + """Gets an Item or an Asset instruments. - Attributes: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. - owner (Item or None): The Item this asset belongs to. - """ - def __init__(self, - href, - title=None, - description=None, - media_type=None, - roles=None, - properties=None): - self.href = href - self.title = title - self.description = description - self.media_type = media_type - self.roles = roles + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value - if properties is not None: - self.properties = properties + Returns: + Optional[List[str]] + """ + if asset is None or 'instruments' not in asset.properties: + return self.properties.get('instruments') else: - self.properties = {} - - # The Item which owns this Asset. - self.owner = None - - def set_owner(self, item): - """Sets the owning item of this Asset. + return asset.properties.get('instruments') - The owning item will be used to resolve relative HREFs of this asset. + def set_instruments(self, + instruments: Optional[List[str]], + asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset instruments. - Args: - item (Item): The Item that owns this asset. + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. """ - self.owner = item - - def get_absolute_href(self): - """Gets the absolute href for this asset, if possible. + if asset is None: + self.properties['instruments'] = instruments + else: + asset.properties['instruments'] = instruments - If this Asset has no associated Item, this will return whatever the - href is (as it cannot determine the absolute path, if the asset - href is relative). + @property + def constellation(self) -> Optional[str]: + """Get or set the name of the constellation associate with an item Returns: - str: The absolute HREF of this asset, or a relative HREF is an abslolute HREF - cannot be determined. + str: Name of the constellation to which the platform belongs """ - if not is_absolute_href(self.href): - if self.owner is not None: - return make_absolute_href(self.href, self.owner.get_self_href()) + return self.get_constellation() - return self.href + @constellation.setter + def constellation(self, v: Optional[str]) -> None: + self.set_constellation(v) - def to_dict(self): - """Generate a dictionary representing the JSON of this Asset. + def get_constellation(self, asset: Optional["Asset"] = None) -> Optional[str]: + """Gets an Item or an Asset constellation. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value Returns: - dict: A serializion of the Asset that can be written out as JSON. + str """ + if asset is None or 'constellation' not in asset.properties: + return self.properties.get('constellation') + else: + return asset.properties.get('constellation') - d = {'href': self.href} - - if self.media_type is not None: - d['type'] = self.media_type + def set_constellation(self, + constellation: Optional[str], + asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset constellation. - if self.title is not None: - d['title'] = self.title + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + if asset is None: + self.properties['constellation'] = constellation + else: + asset.properties['constellation'] = constellation - if self.description is not None: - d['description'] = self.description + @property + def mission(self) -> Optional[str]: + """Get or set the name of the mission associated with an item - if self.properties is not None and len(self.properties) > 0: - for k, v in self.properties.items(): - d[k] = v + Returns: + str: Name of the mission in which data are collected + """ + return self.get_mission() - if self.roles is not None: - d['roles'] = self.roles + @mission.setter + def mission(self, v: Optional[str]) -> None: + self.set_mission(v) - return d + def get_mission(self, asset: Optional["Asset"] = None) -> Optional[str]: + """Gets an Item or an Asset mission. - def clone(self): - """Clones this asset. + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value Returns: - Asset: The clone of this asset. + str """ - return Asset(href=self.href, - title=self.title, - description=self.description, - media_type=self.media_type, - roles=self.roles, - properties=self.properties) - - def __repr__(self): - return ''.format(self.href) + if asset is None or 'mission' not in asset.properties: + return self.properties.get('mission') + else: + return asset.properties.get('mission') - @staticmethod - def from_dict(d): - """Constructs an Asset from a dict. + def set_mission(self, mission: Optional[str], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset mission. - Returns: - Asset: The Asset deserialized from the JSON dict. + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. """ - d = copy(d) - href = d.pop('href') - media_type = d.pop('type', None) - title = d.pop('title', None) - description = d.pop('description', None) - roles = d.pop('roles', None) - properties = None - if any(d): - properties = d - - return Asset(href=href, - media_type=media_type, - title=title, - description=description, - roles=roles, - properties=properties) - - -class CommonMetadata: - """Object containing fields that are not included in core item schema but - are still commonly used. All attributes are defined within the properties of - this item and are optional - - Args: - properties (dict): Dictionary of attributes that is the Item's properties - """ - def __init__(self, properties): - self.properties = properties + if asset is None: + self.properties['mission'] = mission + else: + asset.properties['mission'] = mission - # Basics @property - def title(self): - """Get or set the item's title + def gsd(self) -> Optional[float]: + """Get or sets the Ground Sample Distance at the sensor. Returns: - str: Human readable title describing the item + [float]: Ground Sample Distance at the sensor """ - return self.properties.get('title') + return self.get_gsd() - @title.setter - def title(self, v): - self.properties['title'] = v + @gsd.setter + def gsd(self, v: Optional[float]) -> None: + self.set_gsd(v) - @property - def description(self): - """Get or set the item's description + def get_gsd(self, asset: Optional["Asset"] = None) -> Optional[float]: + """Gets an Item or an Asset gsd. + + If an Asset is supplied and the Item property exists on the Asset, + returns the Asset's value. Otherwise returns the Item's value Returns: - str: Detailed description of the item + float """ - return self.properties.get('description') + if asset is None or 'gsd' not in asset.properties: + return self.properties.get('gsd') + else: + return asset.properties.get('gsd') - @description.setter - def description(self, v): - self.properties['description'] = v + def set_gsd(self, gsd: Optional[float], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset gsd. - # Date and Time Range + If an Asset is supplied, sets the property on the Asset. + Otherwise sets the Item's value. + """ + if asset is None: + self.properties['gsd'] = gsd + else: + asset.properties['gsd'] = gsd + + # Metadata @property - def start_datetime(self): - """Get or set the item's start_datetime. All datetime attributes have + def created(self) -> Optional[Datetime]: + """Get or set the metadata file's creation date. All datetime attributes have setters that can take either a string or a datetime, but always stores the attribute as a string Returns: - datetime: Start date and time for the item + datetime: Creation date and time of the metadata file """ - return self.get_start_datetime() + return self.get_created() - @start_datetime.setter - def start_datetime(self, v): - self.set_start_datetime(v) + @created.setter + def created(self, v: Optional[Datetime]) -> None: + self.set_created(v) - def get_start_datetime(self, asset=None): - """Gets an Item or an Asset start_datetime. + def get_created(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + """Gets an Item or an Asset created time. If an Asset is supplied and the Item 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. + + Note: + ``created`` and ``updated`` have different meaning depending on where they are used. + If those fields are available in the Item `properties`, it's referencing to the + creation and update times of the metadata. Having those fields in the Item `assets` + refers to the creation and update times of the actual data linked to + in the Asset Object. Returns: datetime """ - if asset is None or 'start_datetime' not in asset.properties: - start_datetime = self.properties.get('start_datetime') + if asset is None or 'created' not in asset.properties: + created = self.properties.get('created') else: - start_datetime = asset.properties.get('start_datetime') + created = asset.properties.get('created') - if start_datetime: - start_datetime = str_to_datetime(start_datetime) + if created: + created = str_to_datetime(created) - return start_datetime + return created - def set_start_datetime(self, start_datetime, asset=None): - """Set an Item or an Asset start_datetime. + def set_created(self, created: Optional[Datetime], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset created time. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['start_datetime'] = datetime_to_str(start_datetime) + self.properties['created'] = None if created is None else datetime_to_str(created) else: - asset.properties['start_datetime'] = datetime_to_str(start_datetime) + asset.properties['created'] = None if created is None else datetime_to_str(created) @property - def end_datetime(self): - """Get or set the item's end_datetime. All datetime attributes have + def updated(self) -> Optional[Datetime]: + """Get or set the metadata file's update date. All datetime attributes have setters that can take either a string or a datetime, but always stores the attribute as a string + Note: + ``created`` and ``updated`` have different meaning depending on where they are used. + If those fields are available in the Item `properties`, it's referencing to the + creation and update times of the metadata. Having those fields in the Item `assets` + refers to the creation and update times of the actual data linked to + in the Asset Object. + + Returns: - datetime: End date and time for the item + datetime: Date and time that the metadata file was most recently + updated """ - return self.get_end_datetime() + return self.get_updated() - @end_datetime.setter - def end_datetime(self, v): - self.set_end_datetime(v) + @updated.setter + def updated(self, v: Optional[Datetime]) -> None: + self.set_updated(v) - def get_end_datetime(self, asset=None): - """Gets an Item or an Asset end_datetime. + def get_updated(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + """Gets an Item or an Asset updated time. If an Asset is supplied and the Item 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. + + Note: + ``created`` and ``updated`` have different meaning depending on where they are used. + If those fields are available in the Item `properties`, it's referencing to the + creation and update times of the metadata. Having those fields in the Item `assets` + refers to the creation and update times of the actual data linked to + in the Asset Object. Returns: datetime """ - if asset is None or 'end_datetime' not in asset.properties: - end_datetime = self.properties.get('end_datetime') + if asset is None or 'updated' not in asset.properties: + updated = self.properties.get('updated') else: - end_datetime = asset.properties.get('end_datetime') + updated = asset.properties.get('updated') - if end_datetime: - end_datetime = str_to_datetime(end_datetime) + if updated: + updated = str_to_datetime(updated) - return end_datetime + return updated - def set_end_datetime(self, end_datetime, asset=None): - """Set an Item or an Asset end_datetime. + def set_updated(self, updated: Optional[Datetime], asset: Optional["Asset"] = None) -> None: + """Set an Item or an Asset updated time. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['end_datetime'] = datetime_to_str(end_datetime) + self.properties['updated'] = None if updated is None else datetime_to_str(updated) else: - asset.properties['end_datetime'] = datetime_to_str(end_datetime) - - # License - @property - def license(self): - """Get or set the current license + asset.properties['updated'] = None if updated is None else datetime_to_str(updated) - Returns: - str: Item's license(s), either SPDX identifier of 'various' - """ - return self.get_license() - @license.setter - def license(self, v): - self.set_license(v) +class Asset: + """An object that contains a link to data associated with the Item that can be + downloaded or streamed. - def get_license(self, asset=None): - """Gets an Item or an Asset license. + Args: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + description (str): A description of the Asset providing additional details, such as + how it was processed or created. CommonMark 0.29 syntax MAY be used for rich + text representation. + media_type (str): Optional description of the media type. Registered Media Types + are preferred. See :class:`~pystac.MediaType` for common media types. + roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) + of the asset. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + Attributes: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + description (str): A description of the Asset providing additional details, such as + how it was processed or created. CommonMark 0.29 syntax MAY be used for rich + text representation. + media_type (str): Optional description of the media type. Registered Media Types + are preferred. See :class:`~pystac.MediaType` for common media types. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. + owner (Item or None): The Item this asset belongs to. + """ + def __init__(self, + href: str, + title: Optional[str] = None, + description: Optional[str] = None, + media_type: Optional[str] = None, + roles: Optional[List[str]] = None, + properties: Optional[Dict[str, Any]] = None) -> None: + self.href = href + self.title = title + self.description = description + self.media_type = media_type + self.roles = roles - Returns: - str - """ - if asset is None or 'license' not in asset.properties: - return self.properties.get('license') + if properties is not None: + self.properties = properties else: - return asset.properties.get('license') + self.properties = {} - def set_license(self, license, asset=None): - """Set an Item or an Asset license. + # The Item which owns this Asset. + self.owner = None - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. + def set_owner(self, item: "Item") -> None: + """Sets the owning item of this Asset. + + The owning item will be used to resolve relative HREFs of this asset. + + Args: + item (Item): The Item that owns this asset. """ - if asset is None: - self.properties['license'] = license - else: - asset.properties['license'] = license + self.owner = item - # Providers - @property - def providers(self): - """Get or set a list of the item's providers. The setter can take either - a Provider object or a dict but always stores each provider as a dict + def get_absolute_href(self) -> Optional[str]: + """Gets the absolute href for this asset, if possible. + + If this Asset has no associated Item, this will return whatever the + href is (as it cannot determine the absolute path, if the asset + href is relative). Returns: - List[Provider]: List of organizations that captured or processed the data, - encoded as Provider objects + str: The absolute HREF of this asset, or a relative HREF is an absolute HREF + cannot be determined. """ - return self.get_providers() - - @providers.setter - def providers(self, v): - self.set_providers(v) + if not is_absolute_href(self.href): + if self.owner is not None: + return make_absolute_href(self.href, self.owner.get_self_href()) - def get_providers(self, asset=None): - """Gets an Item or an Asset providers. + return self.href - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + def to_dict(self) -> Dict[str, Any]: + """Generate a dictionary representing the JSON of this Asset. Returns: - List[Provider] + dict: A serialization of the Asset that can be written out as JSON. """ - if asset is None or 'providers' not in asset.properties: - providers = self.properties.get('providers') - else: - providers = asset.properties.get('providers') - if providers is not None: - providers = [Provider.from_dict(d) for d in providers] + d: Dict[str, Any] = {'href': self.href} - return providers + if self.media_type is not None: + d['type'] = self.media_type - def set_providers(self, providers, asset=None): - """Set an Item or an Asset providers. + if self.title is not None: + d['title'] = self.title - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - providers_dicts = [d.to_dict() for d in providers] - if asset is None: - self.properties['providers'] = providers_dicts - else: - asset.properties['providers'] = providers_dicts + if self.description is not None: + d['description'] = self.description - # Instrument - @property - def platform(self): - """Get or set the item's platform attribute + if self.properties is not None and len(self.properties) > 0: + for k, v in self.properties.items(): + d[k] = v + + if self.roles is not None: + d['roles'] = self.roles + + return d + + def clone(self): + """Clones this asset. Returns: - str: Unique name of the specific platform to which the instrument - is attached + Asset: The clone of this asset. """ - return self.get_platform() - - @platform.setter - def platform(self, v): - self.set_platform(v) + return Asset(href=self.href, + title=self.title, + description=self.description, + media_type=self.media_type, + roles=self.roles, + properties=self.properties) - def get_platform(self, asset=None): - """Gets an Item or an Asset platform. + def __repr__(self): + return ''.format(self.href) - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + @staticmethod + def from_dict(d: Dict[str, Any]) -> "Asset": + """Constructs an Asset from a dict. Returns: - str + Asset: The Asset deserialized from the JSON dict. """ - if asset is None or 'platform' not in asset.properties: - return self.properties.get('platform') - else: - return asset.properties.get('platform') + d = copy(d) + href = d.pop('href') + media_type = d.pop('type', None) + title = d.pop('title', None) + description = d.pop('description', None) + roles = d.pop('roles', None) + properties = None + if any(d): + properties = d - def set_platform(self, platform, asset=None): - """Set an Item or an Asset platform. + return Asset(href=href, + media_type=media_type, + title=title, + description=description, + roles=roles, + properties=properties) - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.properties['platform'] = platform - else: - asset.properties['platform'] = platform - @property - def instruments(self): - """Get or set the names of the instruments used +class Item(STACObject): + """An Item is the core granular entity in a STAC, containing the core metadata + that enables any client to search or crawl online catalogs of spatial 'assets' - + satellite imagery, derived data, DEM's, etc. - Returns: - List[str]: Name(s) of instrument(s) used - """ - return self.get_instruments() + Args: + id (str): Provider identifier. Must be unique within the STAC. + geometry (dict): Defines the full footprint of the asset represented by this item, + formatted according to `RFC 7946, section 3.1 (GeoJSON) + `_. + bbox (List[float] or None): Bounding Box of the asset represented by this item using + either 2D or 3D geometries. The length of the array must be 2*n where n is the + number of dimensions. Could also be None in the case of a null geometry. + datetime (datetime or None): Datetime associated with this item. If None, + a start_datetime and end_datetime must be supplied in the properties. + properties (dict): A dictionary of additional metadata for the item. + stac_extensions (List[str]): Optional list of extensions the Item implements. + href (str or None): Optional HREF for this item, which be set as the item's + self link's HREF. + collection (Collection or str): The Collection or Collection ID that this item + belongs to. + extra_fields (dict or None): Extra fields that are part of the top-level JSON properties + of the Item. - @instruments.setter - def instruments(self, v): - self.set_instruments(v) + Attributes: + id (str): Provider identifier. Unique within the STAC. + geometry (dict): Defines the full footprint of the asset represented by this item, + formatted according to `RFC 7946, section 3.1 (GeoJSON) + `_. + bbox (List[float] or None): Bounding Box of the asset represented by this item using + either 2D or 3D geometries. The length of the array is 2*n where n is the + number of dimensions. Could also be None in the case of a null geometry. + datetime (datetime or None): Datetime associated with this item. If None, + the start_datetime and end_datetime in the common_metadata + will supply the datetime range of the Item. + properties (dict): A dictionary of additional metadata for the item. + stac_extensions (List[str] or None): Optional list of extensions the Item implements. + collection (Collection or None): Collection that this item is a part of. + links (List[Link]): A list of :class:`~pystac.Link` objects representing + all links associated with this STACObject. + assets (Dict[str, Asset]): Dictionary of asset objects that can be downloaded, + each with a unique key. + collection_id (str or None): The Collection ID that this item belongs to, if any. + extra_fields (dict or None): Extra fields that are part of the top-level JSON properties + of the Item. + """ - def get_instruments(self, asset=None): - """Gets an Item or an Asset instruments. + STAC_OBJECT_TYPE = STACObjectType.ITEM - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + def __init__(self, + id: str, + geometry: Dict[str, Any], + bbox: List[float], + datetime: Optional[Datetime], + properties: Dict[str, Any], + stac_extensions: Optional[List[str]] = None, + href: Optional[str] = None, + collection: Optional[Union[str, Collection]] = None, + extra_fields: Optional[Dict[str, Any]] = None): + super().__init__(stac_extensions or []) - Returns: - List[str] - """ - if asset is None or 'instruments' not in asset.properties: - return self.properties.get('instruments') + self.id = id + self.geometry = geometry + self.bbox = bbox + self.properties = properties + if extra_fields is None: + self.extra_fields = {} else: - return asset.properties.get('instruments') + self.extra_fields = extra_fields - def set_instruments(self, instruments, asset=None): - """Set an Item or an Asset instruments. + self.assets: Dict[str, Asset] = {} - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.properties['instruments'] = instruments + self.datetime: Optional[Datetime] = None + if datetime is None: + if 'start_datetime' not in properties or \ + 'end_datetime' not in properties: + raise STACError('Invalid Item: If datetime is None, ' + 'a start_datetime and end_datetime ' + 'must be supplied in ' + 'the properties.') + self.datetime = None else: - asset.properties['instruments'] = instruments + self.datetime = datetime - @property - def constellation(self): - """Get or set the name of the constellation associate with an item + if href is not None: + self.set_self_href(href) - Returns: - str: Name of the constellation to which the platform belongs + if collection is not None: + if isinstance(collection, Collection): + self.set_collection(collection) + else: + self.collection_id = collection + else: + self.collection_id = None + + def __repr__(self): + return ''.format(self.id) + + def set_self_href(self, href: str) -> None: + """Sets the absolute HREF that is represented by the ``rel == 'self'`` + :class:`~pystac.Link`. + + Changing the self HREF of the item will ensure that all asset HREFs + remain valid. If asset HREFs are relative, the HREFs will change + to point to the same location based on the new item self HREF, + either by making them relative to the new location or making them + absolute links if the new location does not share the same protocol + as the old location. + + Args: + href (str): The absolute HREF of this object. If the given HREF + is not absolute, it will be transformed to an absolute + HREF based on the current working directory. If this is None + the call will clear the self HREF link. """ - return self.get_constellation() + prev_href = self.get_self_href() + super().set_self_href(href) + new_href = self.get_self_href() # May have been made absolute. - @constellation.setter - def constellation(self, v): - self.set_constellation(v) + if prev_href is not None and new_href is not None: + # Make sure relative asset links remain valid. + for asset in self.assets.values(): + asset_href = asset.href + if not is_absolute_href(asset_href): + abs_href = make_absolute_href(asset_href, prev_href) + new_relative_href = make_relative_href(abs_href, new_href) + asset.href = new_relative_href - def get_constellation(self, asset=None): - """Gets an Item or an Asset constellation. + def get_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: + """Gets an Item or an Asset datetime. If an Asset is supplied and the Item 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. Returns: - str + datetime or None """ - if asset is None or 'constellation' not in asset.properties: - return self.properties.get('constellation') + if asset is None or 'datetime' not in asset.properties: + return self.datetime else: - return asset.properties.get('constellation') + asset_dt = asset.properties.get('datetime') + if asset_dt is None: + return None + else: + return str_to_datetime(asset_dt) - def set_constellation(self, constellation, asset=None): - """Set an Item or an Asset constellation. + def set_datetime(self, datetime: Datetime, asset: Optional[Asset] = None) -> None: + """Set an Item or an Asset datetime. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['constellation'] = constellation + self.datetime = datetime else: - asset.properties['constellation'] = constellation + asset.properties['datetime'] = datetime_to_str(datetime) - @property - def mission(self): - """Get or set the name of the mission associated with an item + def get_assets(self) -> Dict[str, Asset]: + """Get this item's assets. Returns: - str: Name of the mission in which data are collected + Dict[str, Asset]: A copy of the dictionary of this item's assets. """ - return self.get_mission() + return dict(self.assets.items()) - @mission.setter - def mission(self, v): - self.set_mission(v) + def add_asset(self, key: str, asset: Asset) -> "Item": + """Adds an Asset to this item. - def get_mission(self, asset=None): - """Gets an Item or an Asset mission. + Args: + key (str): The unique key of this asset. + asset (Asset): The Asset to add. + """ + asset.set_owner(self) + self.assets[key] = asset + return self - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + def make_asset_hrefs_relative(self) -> "Item": + """Modify each asset's HREF to be relative to this item's self HREF. Returns: - str + Item: self """ - if asset is None or 'mission' not in asset.properties: - return self.properties.get('mission') - else: - return asset.properties.get('mission') - def set_mission(self, mission, asset=None): - """Set an Item or an Asset mission. + self_href = None + for asset in self.assets.values(): + href = asset.href + if is_absolute_href(href): + if self_href is None: + self_href = self.get_self_href() + if self_href is None: + raise STACError('Cannot make asset HREFs relative ' + 'if no self_href is set.') + asset.href = make_relative_href(asset.href, self_href) + return self - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.properties['mission'] = mission - else: - asset.properties['mission'] = mission + def make_asset_hrefs_absolute(self) -> "Item": + """Modify each asset's HREF to be absolute. - @property - def gsd(self): - """Get or sets the Ground Sample Distance at the sensor. + Any asset HREFs that are relative will be modified to absolute based on this + item's self HREF. Returns: - [float]: Ground Sample Distance at the senso + Item: self """ - return self.get_gsd() + self_href = None + for asset in self.assets.values(): + href = asset.href + if not is_absolute_href(href): + if self_href is None: + self_href = self.get_self_href() + if self_href is None: + raise STACError('Cannot make relative asset HREFs absolute ' + 'if no self_href is set.') + asset.href = make_absolute_href(asset.href, self_href) - @gsd.setter - def gsd(self, v): - self.set_gsd(v) + return self - def get_gsd(self, asset=None): - """Gets an Item or an Asset gsd. + def set_collection(self, collection: Collection) -> "Item": + """Set the collection of this item. - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + This method will replace any existing Collection link and attribute for + this item. + + Args: + collection (Collection or None): The collection to set as this + item's collection. If None, will clear the collection. Returns: - float + Item: self """ - if asset is None or 'gsd' not in asset.properties: - return self.properties.get('gsd') - else: - return asset.properties.get('gsd') - - def set_gsd(self, gsd, asset=None): - """Set an Item or an Asset gsd. + self.remove_links('collection') + self.collection_id = None + if collection is not None: + self.add_link(Link.collection(collection)) + self.collection_id = collection.id - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.properties['gsd'] = gsd - else: - asset.properties['gsd'] = gsd + return self - # Metadata - @property - def created(self): - """Get or set the metadata file's creation date. All datetime attributes have - setters that can take either a string or a datetime, but always stores - the attribute as a string + def get_collection(self) -> Optional[Collection]: + """Gets the collection of this item, if one exists. Returns: - datetime: Creation date and time of the metadata file + Collection or None: If this item belongs to a collection, returns + a reference to the collection. Otherwise returns None. """ - return self.get_created() + collection_link = self.get_single_link('collection') + if collection_link is None: + return None + else: + return cast(Collection, collection_link.resolve_stac_object().target) - @created.setter - def created(self, v): - self.set_created(v) + def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: + links = self.links + if not include_self_link: + links = filter(lambda x: x.rel != 'self', links) - def get_created(self, asset=None): - """Gets an Item or an Asset created time. + assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items())) - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value. + if self.datetime is not None: + self.properties['datetime'] = datetime_to_str(self.datetime) + else: + self.properties['datetime'] = None - Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + d: Dict[str, Any] = { + 'type': 'Feature', + 'stac_version': pystac.get_stac_version(), + 'id': self.id, + 'properties': self.properties, + 'geometry': self.geometry, + 'links': [link.to_dict() for link in links], + 'assets': assets + } - Returns: - datetime - """ - if asset is None or 'created' not in asset.properties: - created = self.properties.get('created') - else: - created = asset.properties.get('created') + if self.bbox is not None: + d['bbox'] = self.bbox - if created: - created = str_to_datetime(created) + if self.stac_extensions is not None: + d['stac_extensions'] = self.stac_extensions - return created + if self.collection_id: + d['collection'] = self.collection_id - def set_created(self, created, asset=None): - """Set an Item or an Asset created time. + for key in self.extra_fields: + d[key] = self.extra_fields[key] - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.properties['created'] = datetime_to_str(created) - else: - asset.properties['created'] = datetime_to_str(created) + return d - @property - def updated(self): - """Get or set the metadata file's update date. All datetime attributes have - setters that can take either a string or a datetime, but always stores - the attribute as a string + def clone(self) -> "Item": + clone = Item(id=self.id, + geometry=deepcopy(self.geometry), + bbox=copy(self.bbox), + datetime=copy(self.datetime), + properties=deepcopy(self.properties), + stac_extensions=deepcopy(self.stac_extensions), + collection=self.collection_id) + for link in self.links: + clone.add_link(link.clone()) - Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + for k, asset in self.assets.items(): + clone.add_asset(k, asset.clone()) + return clone - Returns: - datetime: Date and time that the metadata file was most recently - updated - """ - return self.get_updated() + def _object_links(self) -> List[str]: + return ['collection'] + (pystac.STAC_EXTENSIONS.get_extended_object_links(self)) - @updated.setter - def updated(self, v): - self.set_updated(v) + @classmethod + def from_dict(cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None) -> "Item": + d = deepcopy(d) + id = d.pop('id') + geometry = d.pop('geometry') + properties = d.pop('properties') + bbox = d.pop('bbox') # TODO: Ensure this shouldn't pop with a default + stac_extensions = d.get('stac_extensions') + collection_id = d.pop('collection', None) - def get_updated(self, asset=None): - """Gets an Item or an Asset updated time. + datetime = properties.get('datetime') + if datetime is not None: + datetime = dateutil.parser.parse(datetime) + links = d.pop('links') + assets = d.pop('assets') - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value. + d.pop('type') + d.pop('stac_version') - Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + item = cls(id=id, + geometry=geometry, + bbox=bbox, + datetime=datetime, + properties=properties, + stac_extensions=stac_extensions, + collection=collection_id, + extra_fields=d) - Returns: - datetime - """ - if asset is None or 'updated' not in asset.properties: - updated = self.properties.get('updated') - else: - updated = asset.properties.get('updated') + has_self_link = False + for link in links: + has_self_link |= link['rel'] == 'self' + item.add_link(Link.from_dict(link)) - if updated: - updated = str_to_datetime(updated) + if not has_self_link and href is not None: + item.add_link(Link.self_href(href)) - return updated + for k, v in assets.items(): + asset = Asset.from_dict(v) + asset.set_owner(item) + item.assets[k] = asset - def set_updated(self, updated, asset=None): - """Set an Item or an Asset updated time. + return item - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. + @property + def common_metadata(self) -> CommonMetadata: + """Access the item's common metadat fields as a CommonMetadata object + + Returns: + CommonMetada: contains all common metadata fields in the items properties """ - if asset is None: - self.properties['updated'] = datetime_to_str(updated) - else: - asset.properties['updated'] = datetime_to_str(updated) + return CommonMetadata(self.properties) diff --git a/pystac/layout.py b/pystac/layout.py index 25c02d352..d97aff543 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -1,9 +1,14 @@ from abc import (abstractmethod, ABC) from collections import OrderedDict import os +from pystac.collection import Collection from string import Formatter +from typing import Any, Callable, Dict, List, Optional import pystac +from pystac.catalog import Catalog +from pystac.item import Item +from pystac.stac_object import STACObject class TemplateError(Exception): @@ -66,12 +71,12 @@ class LayoutTemplate: # Special template vars specific to Items ITEM_TEMPLATE_VARS = ['date', 'year', 'month', 'day', 'collection'] - def __init__(self, template, defaults=None): + def __init__(self, template: str, defaults: Dict[str, str] = None) -> None: self.template = template self.defaults = defaults or {} # Generate list of template vars - template_vars = [] + template_vars: List[str] = [] for formatter_parse_result in Formatter().parse(template): v = formatter_parse_result[1] if v is not None: @@ -80,9 +85,9 @@ def __init__(self, template, defaults=None): template_vars.append(v) self.template_vars = template_vars - def _get_template_value(self, stac_object, template_var): + def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: - if stac_object.STAC_OBJECT_TYPE == pystac.STACObjectType.ITEM: + if isinstance(stac_object, Item): # Datetime dt = stac_object.datetime if dt is None: @@ -121,17 +126,18 @@ def _get_template_value(self, stac_object, template_var): try: if hasattr(stac_object, props[0]): prop_location = stac_object - elif hasattr( - stac_object, - "properties") and stac_object.properties and props[0] in stac_object.properties: - prop_location = stac_object.properties - elif hasattr(stac_object, "extra_fields" - ) and stac_object.extra_fields and props[0] in stac_object.extra_fields: - prop_location = stac_object.extra_fields + elif hasattr(stac_object, "properties"): + obj_props: Optional[Dict[str, Any]] = stac_object.properties # type:ignore + if obj_props is not None and props[0] in obj_props: + prop_location = obj_props + elif hasattr(stac_object, "extra_fields"): + extra_fields: Optional[Dict[str, Any]] = stac_object.extra_fields # type:ignore + if extra_fields is not None and props[0] in extra_fields: + prop_location = extra_fields else: raise error - v = prop_location + v: Any = prop_location for prop in template_var.split('.'): if type(v) is dict: if prop not in v: @@ -148,7 +154,7 @@ def _get_template_value(self, stac_object, template_var): return v - def get_template_values(self, stac_object): + def get_template_values(self, stac_object: STACObject) -> Dict[str, Any]: """Gets a dictionary of template variables to values derived from the given stac_object. If the template vars cannot be found in the stac object, and defaults was supplied to this template, a default @@ -171,7 +177,7 @@ def get_template_values(self, stac_object): return OrderedDict([(k, self._get_template_value(stac_object, k)) for k in self.template_vars]) - def substitute(self, stac_object): + def substitute(self, stac_object: STACObject) -> str: """Substitutes the values derived from :meth:`~pystac.layout.LayoutTemplate.get_template_values` into the template string for this template. @@ -181,7 +187,7 @@ def substitute(self, stac_object): variable values from. Returns: - [str]: The original template supplied to this LayoutTemplate + str: The original template supplied to this LayoutTemplate with template variables replaced by the values derived from this stac object. @@ -200,27 +206,26 @@ def substitute(self, stac_object): class HrefLayoutStrategy(ABC): """Base class for HREF Layout strategies.""" - def get_href(self, stac_object, parent_dir, is_root=False): - stac_object_type = stac_object.STAC_OBJECT_TYPE - if stac_object_type == pystac.STACObjectType.CATALOG: + def get_href(self, stac_object: STACObject, parent_dir: str, is_root: bool = False) -> str: + if isinstance(stac_object, Catalog): return self.get_catalog_href(stac_object, parent_dir, is_root) - elif stac_object_type == pystac.STACObjectType.COLLECTION: + elif isinstance(stac_object, Collection): return self.get_collection_href(stac_object, parent_dir, is_root) - elif stac_object_type == pystac.STACObjectType.ITEM: + elif isinstance(stac_object, Item): return self.get_item_href(stac_object, parent_dir) else: - raise pystac.STACError('Unknown STAC object type {}'.format(stac_object_type)) + raise pystac.STACError('Unknown STAC object type {}'.format(stac_object)) @abstractmethod - def get_catalog_href(self, cat, parent_dir, is_root): + def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_collection_href(self, col, parent_dir, is_root): + def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_item_href(self, item, parent_dir): + def get_item_href(self, item: Item, parent_dir: str) -> str: pass @@ -245,32 +250,32 @@ class CustomLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ def __init__(self, - catalog_func=None, - collection_func=None, - item_func=None, - fallback_strategy=None): + catalog_func: Optional[Callable[[Catalog, str, bool], str]] = None, + collection_func: Optional[Callable[[Collection, str, bool], str]] = None, + item_func: Optional[Callable[[Item, str], str]] = None, + fallback_strategy: Optional[HrefLayoutStrategy] = None): self.item_func = item_func self.collection_func = collection_func self.catalog_func = catalog_func if fallback_strategy is None: fallback_strategy = BestPracticesLayoutStrategy() - self.fallback_strategy = fallback_strategy + self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat, parent_dir, is_root): + def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: if self.catalog_func is not None: result = self.catalog_func(cat, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) - def get_collection_href(self, col, parent_dir, is_root): + def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: if self.collection_func is not None: result = self.collection_func(col, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) - def get_item_href(self, item, parent_dir): + def get_item_href(self, item: Item, parent_dir: str) -> str: if self.item_func is not None: result = self.item_func(item, parent_dir) if result is not None: @@ -303,10 +308,10 @@ class TemplateLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ def __init__(self, - catalog_template=None, - collection_template=None, - item_template=None, - fallback_strategy=None): + catalog_template: Optional[str] = None, + collection_template: Optional[str] = None, + item_template: Optional[str] = None, + fallback_strategy: Optional[HrefLayoutStrategy] = None): self.catalog_template = LayoutTemplate( catalog_template) if catalog_template is not None else None self.collection_template = LayoutTemplate( @@ -315,9 +320,9 @@ def __init__(self, if fallback_strategy is None: fallback_strategy = BestPracticesLayoutStrategy() - self.fallback_strategy = fallback_strategy + self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat, parent_dir, is_root): + def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: if is_root or self.catalog_template is None: return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) else: @@ -327,7 +332,7 @@ def get_catalog_href(self, cat, parent_dir, is_root): return os.path.join(parent_dir, template_path) - def get_collection_href(self, col, parent_dir, is_root): + def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: if is_root or self.collection_template is None: return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) else: @@ -337,7 +342,7 @@ def get_collection_href(self, col, parent_dir, is_root): return os.path.join(parent_dir, template_path) - def get_item_href(self, item, parent_dir): + def get_item_href(self, item: Item, parent_dir: str) -> str: if self.item_template is None: return self.fallback_strategy.get_item_href(item, parent_dir) else: @@ -362,7 +367,7 @@ class BestPracticesLayoutStrategy(HrefLayoutStrategy): All paths are appended to the parent directory. """ - def get_catalog_href(self, cat, parent_dir, is_root): + def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: if is_root: cat_root = parent_dir else: @@ -370,7 +375,7 @@ def get_catalog_href(self, cat, parent_dir, is_root): return os.path.join(cat_root, cat.DEFAULT_FILE_NAME) - def get_collection_href(self, col, parent_dir, is_root): + def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: if is_root: col_root = parent_dir else: @@ -378,7 +383,7 @@ def get_collection_href(self, col, parent_dir, is_root): return os.path.join(col_root, col.DEFAULT_FILE_NAME) - def get_item_href(self, item, parent_dir): + def get_item_href(self, item: Item, parent_dir: str) -> str: item_root = os.path.join(parent_dir, '{}'.format(item.id)) return os.path.join(item_root, '{}.json'.format(item.id)) diff --git a/pystac/link.py b/pystac/link.py index 67bde52d7..b7a45515e 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -1,6 +1,11 @@ from copy import copy +from pystac.item import Item +from pystac.catalog import Catalog +from pystac.collection import Collection +from typing import Any, Dict, Optional, Union, cast import pystac +from pystac.stac_object import STACObject from pystac import STACError from pystac.stac_io import STAC_IO from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) @@ -50,15 +55,20 @@ class Link: to resolve objects, and will create absolute HREFs from relative HREFs against the owner's self HREF. """ - def __init__(self, rel, target, media_type=None, title=None, properties=None): + def __init__(self, + rel: str, + target: Union[str, STACObject], + media_type: Optional[str] = None, + title: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None) -> None: self.rel = rel - self.target = target # An object or an href + self.target: Union[str, STACObject] = target # An object or an href self.media_type = media_type self.title = title self.properties = properties self.owner = None - def set_owner(self, owner): + def set_owner(self, owner: STACObject) -> "Link": """Sets the owner of this link. Args: @@ -67,7 +77,7 @@ def set_owner(self, owner): self.owner = owner return self - def get_href(self): + def get_href(self) -> Optional[str]: """Gets the HREF for this link. Returns: @@ -78,9 +88,9 @@ def get_href(self): """ # get the self href if self.is_resolved(): - href = self.target.get_self_href() + href = cast(STACObject, self.target).get_self_href() else: - href = self.target + href = cast(Optional[str], self.target) if href and is_absolute_href(href) and self.owner and self.owner.get_root(): root = self.owner.get_root() @@ -88,11 +98,13 @@ def get_href(self): pystac.STAC_EXTENSIONS.get_extended_object_links(self.owner) # if a hierarchical link with an owner and root, and relative catalog if root.is_relative() and self.rel in rel_links: - href = make_relative_href(href, self.owner.get_self_href()) + owner_href = self.owner.get_self_href() + if owner_href is not None: + href = make_relative_href(href, owner_href) return href - def get_absolute_href(self): + def get_absolute_href(self) -> Optional[str]: """Gets the absolute href for this link, if possible. Returns: @@ -101,11 +113,11 @@ def get_absolute_href(self): and has an unresolved target, this will return a relative HREF. """ if self.is_resolved(): - href = self.target.get_self_href() + href = cast(STACObject, self.target).get_self_href() else: - href = self.target + href = cast(Optional[str], self.target) - if self.owner is not None: + if href is not None and self.owner is not None: href = make_absolute_href(href, self.owner.get_self_href()) return href @@ -113,7 +125,7 @@ def get_absolute_href(self): def __repr__(self): return ''.format(self.rel, self.target) - def resolve_stac_object(self, root=None): + def resolve_stac_object(self, root: Optional[Catalog]=None) -> "Link": """Resolves a STAC object from the HREF of this link, if the link is not already resolved. @@ -153,7 +165,7 @@ def resolve_stac_object(self, root=None): self.target = obj - if self.owner and self.rel in ['child', 'item']: + if self.owner and self.rel in ['child', 'item'] and isinstance(self.owner, Catalog): self.target.set_parent(self.owner) return self @@ -166,14 +178,14 @@ def is_resolved(self): """ return not isinstance(self.target, str) - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this serialized Link. Returns: - dict: A serializion of the Link that can be written out as JSON. + dict: A serialization of the Link that can be written out as JSON. """ - d = {'rel': self.rel} + d: Dict[str, Any] = {'rel': self.rel} d['href'] = self.get_href() @@ -202,7 +214,7 @@ def clone(self): return Link(rel=self.rel, target=self.target, media_type=self.media_type, title=self.title) @staticmethod - def from_dict(d): + def from_dict(d: Dict[str, Any]) -> "Link": """Deserializes a Link from a dict. Args: @@ -224,31 +236,31 @@ def from_dict(d): return Link(rel=rel, target=href, media_type=media_type, title=title, properties=properties) @staticmethod - def root(c): + def root(c: Catalog) -> "Link": """Creates a link to a root Catalog or Collection.""" return Link('root', c, media_type='application/json') @staticmethod - def parent(c): + def parent(c: Catalog) -> "Link": """Creates a link to a parent Catalog or Collection.""" return Link('parent', c, media_type='application/json') @staticmethod - def collection(c): + def collection(c: Collection) -> "Link": """Creates a link to an item's Collection.""" return Link('collection', c, media_type='application/json') @staticmethod - def self_href(href): + def self_href(href: str) -> "Link": """Creates a self link to a file's location.""" return Link('self', href, media_type='application/json') @staticmethod - def child(c, title=None): + def child(c: Catalog, title: Optional[str]=None) -> "Link": """Creates a link to a child Catalog or Collection.""" return Link('child', c, title=title, media_type='application/json') @staticmethod - def item(item, title=None): + def item(item: Item, title: Optional[str]=None) -> "Link": """Creates a link to an Item.""" return Link('item', item, title=title, media_type='application/json') diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index 89ac837c8..9ada41486 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -1,13 +1,15 @@ # flake8: noqa -from pystac import (Catalog, Collection, Item, Extensions, STACObjectType) +from pystac.stac_object import STACObject +from typing import Any, Dict, Optional +from pystac import (Catalog, Collection, Item, STACObjectType) -from pystac.serialization.identify import (STACJSONDescription, STACVersionRange, STACVersionID, +from pystac.serialization.identify import (STACJSONDescription, STACVersionRange, STACVersionID, # type:ignore identify_stac_object, identify_stac_object_type) from pystac.serialization.common_properties import merge_common_properties from pystac.serialization.migrate import migrate_to_latest -def stac_object_from_dict(d, href=None, root=None): +def stac_object_from_dict(d: Dict[str, Any], href: Optional[str]=None, root: Optional[Catalog]=None) -> STACObject: """Determines how to deserialize a dictionary into a STAC object. Args: @@ -41,3 +43,5 @@ def stac_object_from_dict(d, href=None, root=None): if info.object_type == STACObjectType.ITEM: return Item.from_dict(d, href=href, root=root) + + raise ValueError(f"Unknown STAC object type {info.object_type}") diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index ea430228f..4c3677084 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -1,10 +1,14 @@ +from pystac.cache import CollectionCache +from typing import Any, Dict, Optional, Union, cast from pystac import Collection from pystac.utils import make_absolute_href from pystac.stac_io import STAC_IO from pystac.serialization.identify import STACVersionID -def merge_common_properties(item_dict, collection_cache=None, json_href=None): +def merge_common_properties(item_dict: Dict[str, Any], + collection_cache: Optional[CollectionCache] = None, + json_href: Optional[str] = None) -> bool: """Merges Collection properties into an Item. Args: @@ -20,15 +24,15 @@ def merge_common_properties(item_dict, collection_cache=None, json_href=None): """ properties_merged = False - collection = None - collection_id = None - collection_href = None + collection: Optional[Union[Collection, Dict[str, Any]]] = None + collection_id: Optional[str] = None + collection_href: Optional[str] = None stac_version = item_dict.get('stac_version') # The commons extension was removed in 1.0.0-beta.1, so if this is an earlier STAC # item we don't have to bother with merging. - if stac_version is not None and STACVersionID(stac_version) > '0.9.0': + if stac_version is not None and STACVersionID(stac_version) > '0.9.0': # type:ignore return False # Check to see if this is a 0.9.0 item that @@ -43,8 +47,8 @@ def merge_common_properties(item_dict, collection_cache=None, json_href=None): return False # Try the cache if we have a collection ID. - if 'collection' in item_dict: - collection_id = item_dict['collection'] + collection_id = item_dict.get('collection') + if collection_id is not None: if collection_cache is not None: collection = collection_cache.get_by_id(collection_id) @@ -53,27 +57,29 @@ def merge_common_properties(item_dict, collection_cache=None, json_href=None): links = item_dict['links'] # Account for 0.5 links, which were dicts - if isinstance(links, dict): + if isinstance(links, Dict[str, Dict[str, Any]]): links = list(links.values()) collection_link = next((link for link in links if link['rel'] == 'collection'), None) if collection_link is not None: - collection_href = collection_link['href'] - if json_href is not None: - collection_href = make_absolute_href(collection_href, json_href) - if collection_cache is not None: - collection = collection_cache.get_by_href(collection_href) + collection_href = cast(Dict[str, Any], collection_link).get('href') + if collection_href is not None: + if json_href is not None: + collection_href = make_absolute_href(collection_href, json_href) + if collection_cache is not None: + collection = collection_cache.get_by_href(collection_href) - if collection is None: - collection = STAC_IO.read_json(collection_href) + if collection is None: + collection = STAC_IO.read_json(collection_href) + # TODO: Remove properties from Collection, it would be in extra_fields if collection is not None: collection_id = None - collection_props = None + collection_props: Optional[Dict[str, Any]] = None if isinstance(collection, Collection): collection_id = collection.id collection_props = collection.properties - elif type(collection) is dict: + elif isinstance(collection, dict): collection_id = collection['id'] if 'properties' in collection: collection_props = collection['properties'] @@ -87,7 +93,8 @@ def merge_common_properties(item_dict, collection_cache=None, json_href=None): properties_merged = True item_dict['properties'][k] = collection_props[k] - if collection_cache is not None and not collection_cache.contains_id(collection_id): + if (collection_cache is not None and collection_id is not None + and not collection_cache.contains_id(collection_id)): collection_cache.cache(collection, href=collection_href) return properties_merged diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 86efdffda..641366725 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,40 +1,17 @@ from functools import total_ordering +from typing import Any, Dict, List, Optional, Tuple, Union, cast from pystac import STACObjectType from pystac.version import STACVersion from pystac.extensions import Extensions -class STACJSONDescription: - """Describes the STAC object information for a STAC object represented in JSON - - Attributes: - object_type (str): Describes the STAC object type. One of :class:`~pystac.STACObjectType`. - version_range (STACVersionRange): The STAC version range that describes what - has been identified as potential valid versions of the stac object. - common_extensions (List[str]): List of common extension IDs implemented by this - STAC object. - custom_extensions (List[str]): List of custom extensions (URIs to JSON Schemas) - used by this STAC Object. - """ - def __init__(self, object_type, version_range, common_extensions, custom_extensions): - self.object_type = object_type - self.version_range = version_range - self.common_extensions = common_extensions - self.custom_extensions = custom_extensions - - def __repr__(self): - return '<{} {} common_ext={} custom_ext={}>'.format(self.object_type, self.version_range, - ','.join(self.common_extensions), - ','.join(self.custom_extensions)) - - @total_ordering class STACVersionID: """Defines STAC versions in an object that is orderable based on version number. For instance, ``1.0.0-beta.2 < 1.0.0`` """ - def __init__(self, version_string): + def __init__(self, version_string: str) -> None: self.version_string = version_string # Account for RC or beta releases in version @@ -45,18 +22,18 @@ def __init__(self, version_string): else: self.version_prerelease = '-'.join(version_parts[1:]) - def __str__(self): + def __str__(self) -> str: return self.version_string - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if type(other) is str: other = STACVersionID(other) return self.version_string == other.version_string - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: return not self.__eq__(other) - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: if type(other) is str: other = STACVersionID(other) if self.version_core < other.version_core: @@ -71,8 +48,10 @@ def __lt__(self, other): class STACVersionRange: """Defines a range of STAC versions.""" - def __init__(self, min_version='0.4.0', max_version=None): - if type(min_version) is str: + def __init__(self, + min_version: Union[str, STACVersionID] = '0.4.0', + max_version: Optional[Union[str, STACVersionID]] = None): + if isinstance(min_version, str): self.min_version = STACVersionID(min_version) else: self.min_version = min_version @@ -80,47 +59,47 @@ def __init__(self, min_version='0.4.0', max_version=None): if max_version is None: self.max_version = STACVersionID(STACVersion.DEFAULT_STAC_VERSION) else: - if type(max_version) is str: + if isinstance(max_version, str): self.max_version = STACVersionID(max_version) else: self.max_version = max_version - def set_min(self, v): + def set_min(self, v: STACVersionID) -> None: if self.min_version < v: if v < self.max_version: self.min_version = v else: self.min_version = self.max_version - def set_max(self, v): + def set_max(self, v: STACVersionID) -> None: if v < self.max_version: if self.min_version < v: self.max_version = v else: self.max_version = self.min_version - def set_to_single(self, v): + def set_to_single(self, v: STACVersionID) -> None: self.set_min(v) self.set_max(v) - def latest_valid_version(self): + def latest_valid_version(self) -> STACVersionID: return self.max_version - def contains(self, v): - if type(v) is str: + def contains(self, v: Union[str, STACVersionID]) -> bool: + if isinstance(v, str): v = STACVersionID(v) - return self.min_version <= v and v <= self.max_version + return self.min_version <= v and v <= self.max_version # type:ignore - def is_single_version(self): - return self.min_version >= self.max_version + def is_single_version(self) -> bool: + return self.min_version >= self.max_version # type:ignore - def is_earlier_than(self, v): - if type(v) is str: + def is_earlier_than(self, v: Union[str, STACVersionID]) -> bool: + if isinstance(v, str): v = STACVersionID(v) return self.max_version < v - def is_later_than(self, v): - if type(v) is str: + def is_later_than(self, v: Union[str, STACVersionID]) -> bool: + if isinstance(v, str): v = STACVersionID(v) return v < self.min_version @@ -128,7 +107,32 @@ def __repr__(self): return ''.format(self.min_version, self.max_version) -def _identify_stac_extensions(object_type, d, version_range): +class STACJSONDescription: + """Describes the STAC object information for a STAC object represented in JSON + + Attributes: + object_type (str): Describes the STAC object type. One of :class:`~pystac.STACObjectType`. + version_range (STACVersionRange): The STAC version range that describes what + has been identified as potential valid versions of the stac object. + common_extensions (List[str]): List of common extension IDs implemented by this + STAC object. + custom_extensions (List[str]): List of custom extensions (URIs to JSON Schemas) + used by this STAC Object. + """ + def __init__(self, object_type: str, version_range: STACVersionRange, + common_extensions: List[str], custom_extensions: List[str]) -> None: + self.object_type = object_type + self.version_range = version_range + self.common_extensions = common_extensions + self.custom_extensions = custom_extensions + + def __repr__(self) -> str: + return '<{} {} common_ext={} custom_ext={}>'.format(self.object_type, self.version_range, + ','.join(self.common_extensions), + ','.join(self.custom_extensions)) + + +def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range: STACVersionRange) -> List[str]: """Identifies extensions for STAC Objects that don't list their extensions in a 'stac_extensions' property. @@ -142,69 +146,71 @@ def _identify_stac_extensions(object_type, d, version_range): if object_type == STACObjectType.ITEMCOLLECTION: if 'assets' in d: stac_extensions.add('assets') - version_range.set_min('0.8.0') + version_range.set_min(STACVersionID('0.8.0')) # checksum if 'links' in d: found_checksum = False for link in d['links']: - if any(filter(lambda p: p.startswith('checksum:'), link)): + link_props = cast(Dict[str, Any], link).keys() + if any(prop.startswith('checksum:') for prop in link_props): found_checksum = True stac_extensions.add(Extensions.CHECKSUM) if not found_checksum: if 'assets' in d: - for asset in d['assets']: - if any(filter(lambda p: p.startswith('checksum:'), link)): + for asset in d['assets'].values(): + asset_props = cast(Dict[str, Any], asset).keys() + if any(prop.startswith('checksum:') for prop in asset_props): found_checksum = True stac_extensions.add(Extensions.CHECKSUM) if found_checksum: - version_range.set_min('0.6.2') + version_range.set_min(STACVersionID('0.6.2')) # datacube if object_type == STACObjectType.ITEM: - if any(filter(lambda k: k.startswith('cube:'), d['properties'])): + if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.DATACUBE) - version_range.set_min('0.6.1') + version_range.set_min(STACVersionID('0.6.1')) # datetime-range (old extension) if object_type == STACObjectType.ITEM: if 'dtr:start_datetime' in d['properties']: stac_extensions.add('datetime-range') - version_range.set_min('0.6.0') + version_range.set_min(STACVersionID('0.6.0')) # eo if object_type == STACObjectType.ITEM: - if any(filter(lambda k: k.startswith('eo:'), d['properties'])): + if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.EO) if 'eo:epsg' in d['properties']: if d['properties']['eo:epsg'] is None: - version_range.set_min('0.6.1') + version_range.set_min(STACVersionID('0.6.1')) if 'eo:crs' in d['properties']: - version_range.set_max('0.4.1') + version_range.set_max(STACVersionID('0.4.1')) if 'eo:constellation' in d['properties']: - version_range.set_min('0.6.0') + version_range.set_min(STACVersionID('0.6.0')) if 'eo:bands' in d: stac_extensions.add(Extensions.EO) - version_range.set_max('0.5.2') + version_range.set_max(STACVersionID('0.5.2')) # pointcloud if object_type == STACObjectType.ITEM: - if any(filter(lambda k: k.startswith('pc:'), d['properties'])): + if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.POINTCLOUD) - version_range.set_min('0.6.2') + version_range.set_min(STACVersionID('0.6.2')) # sar if object_type == STACObjectType.ITEM: - if any(filter(lambda k: k.startswith('sar:'), d['properties'])): + if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.SAR) - version_range.set_min('0.6.2') + version_range.set_min(STACVersionID('0.6.2')) if version_range.contains('0.6.2'): for prop in [ 'sar:absolute_orbit', 'sar:resolution', 'sar:pixel_spacing', 'sar:looks' ]: if prop in d['properties']: if isinstance(d['properties'][prop], list): - version_range.set_max('0.6.2') + version_range.set_max(STACVersionID('0.6.2')) if version_range.contains('0.7.0'): for prop in [ 'sar:incidence_angle', 'sar:relative_orbit', 'sar:observation_direction', @@ -213,36 +219,37 @@ def _identify_stac_extensions(object_type, d, version_range): 'sar:looks_equivalent_number' ]: if prop in d['properties']: - version_range.set_min('0.7.0') + version_range.set_min(STACVersionID('0.7.0')) if 'sar:absolute_orbit' in d['properties'] and not isinstance( d['properties']['sar:absolute_orbit'], list): - version_range.set_min('0.7.0') + version_range.set_min(STACVersionID('0.7.0')) if 'sar:off_nadir' in d['properties']: - version_range.set_max('0.6.2') + version_range.set_max(STACVersionID('0.6.2')) # scientific if object_type == STACObjectType.ITEM or object_type == STACObjectType.COLLECTION: if 'properties' in d: - if any(filter(lambda k: k.startswith('sci:'), d['properties'])): + prop_keys = cast(Dict[str, Any], d['properties']).keys() + if any(k.startswith('sci:') for k in prop_keys): stac_extensions.add(Extensions.SCIENTIFIC) - version_range.set_min('0.6.0') + version_range.set_min(STACVersionID('0.6.0')) # Single File STAC if object_type == STACObjectType.ITEMCOLLECTION: if 'collections' in d: stac_extensions.add(Extensions.SINGLE_FILE_STAC) - version_range.set_min('0.8.0') + version_range.set_min(STACVersionID('0.8.0')) if 'stac_extensions' not in d: - version_range.set_max('0.8.1') + version_range.set_max(STACVersionID('0.8.1')) return list(stac_extensions) -def _split_extensions(stac_extensions): +def _split_extensions(stac_extensions: List[str]) -> Tuple[List[str], List[str]]: """Split extensions into common_extensions and custom_extensions""" - common_extensions = [] - custom_extensions = [] + common_extensions: List[str] = [] + custom_extensions: List[str] = [] for ext in stac_extensions: # Custom extensions are a URI if ext.endswith('.json') or '/' in ext: @@ -253,7 +260,7 @@ def _split_extensions(stac_extensions): return (common_extensions, custom_extensions) -def identify_stac_object_type(json_dict): +def identify_stac_object_type(json_dict: Dict[str, Any]): """Determines the STACObjectType of the provided JSON dict. Args: @@ -266,7 +273,7 @@ def identify_stac_object_type(json_dict): # Identify pre-1.0 ITEMCOLLECTION (since removed) if 'type' in json_dict and 'assets' not in json_dict: - if 'stac_version' in json_dict and json_dict['stac_version'].startswith('0'): + if 'stac_version' in json_dict and cast(str, json_dict['stac_version']).startswith('0'): if json_dict['type'] == 'FeatureCollection': object_type = STACObjectType.ITEMCOLLECTION @@ -280,7 +287,7 @@ def identify_stac_object_type(json_dict): return object_type -def identify_stac_object(json_dict): +def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: """Determines the STACJSONDescription of the provided JSON dict. Args: @@ -299,16 +306,16 @@ def identify_stac_object(json_dict): if stac_version is None: if object_type == STACObjectType.CATALOG or object_type == STACObjectType.COLLECTION: - version_range.set_max('0.5.2') + version_range.set_max(STACVersionID('0.5.2')) elif object_type == STACObjectType.ITEM: - version_range.set_max('0.7.0') + version_range.set_max(STACVersionID('0.7.0')) else: # ItemCollection - version_range.set_min('0.8.0') + version_range.set_min(STACVersionID('0.8.0')) else: version_range.set_to_single(stac_version) if stac_extensions is not None: - version_range.set_min('0.8.0') + version_range.set_min(STACVersionID('0.8.0')) if stac_extensions is None: # If this is post-0.8, we can assume there are no extensions @@ -328,13 +335,13 @@ def identify_stac_object(json_dict): if 'links' in json_dict: # links were a dictionary only in 0.5 if 'links' in json_dict and isinstance(json_dict['links'], dict): - version_range.set_to_single('0.5.2') + version_range.set_to_single(STACVersionID('0.5.2')) # self links became non-required in 0.7.0 if not version_range.is_earlier_than('0.7.0') and \ - not any(filter(lambda l: l['rel'] == 'self', + not any(filter(lambda l: cast(Dict[str, Any], l)['rel'] == 'self', json_dict['links'])): - version_range.set_min('0.7.0') + version_range.set_min(STACVersionID('0.7.0')) common_extensions, custom_extensions = _split_extensions(stac_extensions) return STACJSONDescription(object_type, version_range, common_extensions, custom_extensions) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index ed0945a03..e08bea976 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -1,40 +1,41 @@ import re from copy import deepcopy +from typing import Any, Callable, Dict, List, Optional, Set, Tuple from pystac import STACObjectType from pystac.version import STACVersion from pystac.extensions import Extensions -from pystac.serialization.identify import (STACJSONDescription, STACVersionRange) +from pystac.serialization.identify import (STACJSONDescription, STACVersionID, STACVersionRange) -# STAC Object Types - -def _migrate_links(d, version): +def _migrate_links(d: Dict[str, Any], version: STACVersionID) -> None: if version < '0.6': if 'links' in d: if isinstance(d['links'], dict): d['links'] = list(d['links'].values()) -def _migrate_catalog(d, version, info): +def _migrate_catalog(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: _migrate_links(d, version) if version < '0.8': d['stac_extensions'] = info.common_extensions + info.custom_extensions -def _migrate_collection(d, version, info): +def _migrate_collection(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: _migrate_catalog(d, version, info) -def _migrate_item(d, version, info): +def _migrate_item(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: _migrate_links(d, version) if version < '0.8': d['stac_extensions'] = info.common_extensions + info.custom_extensions -def _migrate_itemcollection(d, version, info): +def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: if version < '0.9.0': d['stac_extensions'] = info.common_extensions + info.custom_extensions @@ -42,7 +43,8 @@ def _migrate_itemcollection(d, version, info): # Extensions -def _migrate_item_assets(d, version, info): +def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: if version < '1.0.0-beta.2': if info.object_type == STACObjectType.COLLECTION: if 'assets' in d: @@ -50,15 +52,18 @@ def _migrate_item_assets(d, version, info): del d['assets'] -def _migrate_checksum(d, version, info): +def _migrate_checksum(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: pass -def _migrate_datacube(d, version, info): +def _migrate_datacube(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: pass -def _migrate_datetime_range(d, version, info): +def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: if version < '0.9': # Datetime range was removed if 'dtr:start_datetime' in d['properties'] and 'start_datetime' not in d['properties']: @@ -70,8 +75,9 @@ def _migrate_datetime_range(d, version, info): del d['properties']['dtr:end_datetime'] -def _migrate_eo(d, version, info): - added_extensions = set([]) +def _migrate_eo(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: + added_extensions: Set[str] = set([]) if version < '0.5': if 'eo:crs' in d['properties']: # Try to pull out the EPSG code. @@ -87,8 +93,8 @@ def _migrate_eo(d, version, info): # Change eo:bands from a dict to a list. eo:bands on an asset # is an index instead of a dict key. eo:bands is in properties. bands_dict = d['eo:bands'] - keys_to_indices = {} - bands = [] + keys_to_indices: Dict[str, int] = {} + bands: List[Dict[str, Any]] = [] for i, (k, band) in enumerate(bands_dict.items()): keys_to_indices[k] = i bands.append(band) @@ -97,7 +103,7 @@ def _migrate_eo(d, version, info): d['properties']['eo:bands'] = bands for k, asset in d['assets'].items(): if 'eo:bands' in asset: - asset_band_indices = [] + asset_band_indices: List[int] = [] for bk in asset['eo:bands']: asset_band_indices.append(keys_to_indices[bk]) asset['eo:bands'] = sorted(asset_band_indices) @@ -145,7 +151,7 @@ def _migrate_eo(d, version, info): bands = d['properties']['eo:bands'] for asset in d['assets'].values(): if 'eo:bands' in asset: - new_bands = [] + new_bands: List[Dict[str, Any]] = [] for band_index in asset['eo:bands']: new_bands.append(bands[band_index]) asset['eo:bands'] = new_bands @@ -153,7 +159,7 @@ def _migrate_eo(d, version, info): return added_extensions -def _migrate_label(d, version, info): +def _migrate_label(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: if info.object_type == STACObjectType.ITEM and version < '1.0.0': props = d['properties'] # Migrate 0.8.0-rc1 non-pluralized forms @@ -175,11 +181,13 @@ def _migrate_label(d, version, info): del props['label:method'] -def _migrate_pointcloud(d, version, info): +def _migrate_pointcloud(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: pass -def _migrate_sar(d, version, info): +def _migrate_sar(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: if version < '0.9': # Some sar fields became common_metadata if 'sar:platform' in d['properties'] and 'platform' not in d['properties']: @@ -195,50 +203,57 @@ def _migrate_sar(d, version, info): del d['properties']['sar:constellation'] -def _migrate_scientific(d, version, info): +def _migrate_scientific(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: pass -def _migrate_single_file_stac(d, version, info): +def _migrate_single_file_stac(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: pass -_object_migrations = { - STACObjectType.CATALOG: _migrate_catalog, - STACObjectType.COLLECTION: _migrate_collection, - STACObjectType.ITEM: _migrate_item, - STACObjectType.ITEMCOLLECTION: _migrate_itemcollection -} - -_extension_migrations = { - Extensions.CHECKSUM: _migrate_checksum, - Extensions.DATACUBE: _migrate_datacube, - Extensions.EO: _migrate_eo, - Extensions.ITEM_ASSETS: _migrate_item_assets, - Extensions.LABEL: _migrate_label, - Extensions.POINTCLOUD: _migrate_pointcloud, - Extensions.SAR: _migrate_sar, - Extensions.SCIENTIFIC: _migrate_scientific, - Extensions.SINGLE_FILE_STAC: _migrate_single_file_stac -} - -_removed_extension_migrations = { - # Removed in 0.9.0 - 'dtr': _migrate_datetime_range, - 'datetime-range': _migrate_datetime_range, - 'commons': lambda a, b, c: None # No changes needed, just remove the extension_id -} - -_extension_renames = {'asset': 'item-assets'} - - -def migrate_to_latest(json_dict, info): +_object_migrations: Dict[str, + Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]] = { + STACObjectType.CATALOG: _migrate_catalog, + STACObjectType.COLLECTION: _migrate_collection, + STACObjectType.ITEM: _migrate_item, + STACObjectType.ITEMCOLLECTION: _migrate_itemcollection + } + +_extension_migrations: Dict[str, + Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], + Optional[Set[str]]]] = { + Extensions.CHECKSUM: _migrate_checksum, + Extensions.DATACUBE: _migrate_datacube, + Extensions.EO: _migrate_eo, + Extensions.ITEM_ASSETS: _migrate_item_assets, + Extensions.LABEL: _migrate_label, + Extensions.POINTCLOUD: _migrate_pointcloud, + Extensions.SAR: _migrate_sar, + Extensions.SCIENTIFIC: _migrate_scientific, + Extensions.SINGLE_FILE_STAC: _migrate_single_file_stac + } + +_removed_extension_migrations: Dict[str, Callable[ + [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]] = { + # Removed in 0.9.0 + 'dtr': _migrate_datetime_range, + 'datetime-range': _migrate_datetime_range, + 'commons': lambda a, b, c: None # No changes needed, just remove the extension_id + } + +_extension_renames: Dict[str, str] = {'asset': 'item-assets'} + + +def migrate_to_latest(json_dict: Dict[str, Any], + info: STACJSONDescription) -> Tuple[Dict[str, Any], STACJSONDescription]: """Migrates the STAC JSON to the latest version Args: json_dict (dict): The dict of STAC JSON to identify. info (STACJSONDescription): The info from - :func:`~pystac.serialzation.identify.identify_stac_object` that describes + :func:`~pystac.serialization.identify.identify_stac_object` that describes the STAC object contained in the JSON dict. Returns: diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 5e1e5a7ea..49956df6a 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -1,5 +1,8 @@ import os import json +from pystac.stac_object import STACObject +from pystac.catalog import Catalog +from typing import Any, Callable, Dict, Optional from urllib.parse import urlparse from urllib.request import urlopen @@ -11,7 +14,8 @@ class STAC_IO: Allows users of the library to set their own methods (e.g. for reading and writing from cloud storage) """ - def default_read_text_method(uri): + @staticmethod + def default_read_text_method(uri: str) -> str: """Default method for reading text. Only handles local file paths.""" parsed = urlparse(uri) if parsed.scheme != '': @@ -24,7 +28,8 @@ def default_read_text_method(uri): with open(uri) as f: return f.read() - def default_write_text_method(uri, txt): + @staticmethod + def default_write_text_method(uri: str, txt: str) -> None: """Default method for writing text. Only handles local file paths.""" dirname = os.path.dirname(uri) if dirname != '' and not os.path.isdir(dirname): @@ -32,7 +37,7 @@ def default_write_text_method(uri, txt): with open(uri, 'w') as f: f.write(txt) - read_text_method = default_read_text_method + read_text_method: Callable[[str], str] = default_read_text_method """Users of PySTAC can replace the read_text_method in order to expand the ability of PySTAC to read different file systems. For example, a client of the library might replace this class @@ -40,7 +45,7 @@ def default_write_text_method(uri, txt): cloud storage. """ - write_text_method = default_write_text_method + write_text_method: Callable[[str, str], None] = default_write_text_method """Users of PySTAC can replace the write_text_method in order to expand the ability of PySTAC to write to different file systems. For example, a client of the library might replace this class @@ -49,13 +54,13 @@ def default_write_text_method(uri, txt): """ # Replaced in __init__ to account for extension objects. - _stac_object_from_dict = None + stac_object_from_dict: Optional[Callable[[Dict[str, Any], Optional[str], Optional[Catalog]], STACObject]] = None # This is set in __init__.py _STAC_OBJECT_CLASSES = None @classmethod - def read_text(cls, uri): + def read_text(cls, uri: str) -> str: """Read text from the given URI. Args: @@ -73,7 +78,7 @@ def read_text(cls, uri): return cls.read_text_method(uri) @classmethod - def write_text(cls, uri, txt): + def write_text(cls, uri: str, txt: str) -> None: """Write the given text to a file at the given URI. Args: @@ -89,7 +94,7 @@ def write_text(cls, uri, txt): cls.write_text_method(uri, txt) @classmethod - def read_json(cls, uri): + def read_json(cls, uri: str) -> Dict[str, Any]: """Read a dict from the given URI. Args: @@ -108,7 +113,7 @@ def read_json(cls, uri): return json.loads(STAC_IO.read_text(uri)) @classmethod - def read_stac_object(cls, uri, root=None): + def read_stac_object(cls, uri: str, root: Optional[Catalog]=None) -> STACObject: """Read a STACObject from a JSON file at the given URI. Args: @@ -128,10 +133,10 @@ def read_stac_object(cls, uri, root=None): with your own implementation. """ d = cls.read_json(uri) - return cls.stac_object_from_dict(d, href=uri, root=root) + return cls.stac_object_from_dict(d, uri, root) @classmethod - def save_json(cls, uri, json_dict): + def save_json(cls, uri: str, json_dict: Dict[str, Any]) -> None: """Write a dict to the given URI as JSON. Args: diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 0f5eebaf5..cc578e24d 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,12 +1,16 @@ from abc import (ABC, abstractmethod) from enum import Enum +from pystac.catalog import Catalog +from typing import Any, Dict, Generator, List, Optional, cast import pystac +import pystac.validation from pystac import STACError from pystac.link import Link from pystac.stac_io import STAC_IO from pystac.utils import (is_absolute_href, make_absolute_href) from pystac.extensions import ExtensionError +from pystac.extensions.base import STACObjectExtension class STACObjectType(str, Enum): @@ -19,95 +23,26 @@ def __str__(self): ITEMCOLLECTION = 'ITEMCOLLECTION' -class ExtensionIndex: - """Defines methods for accessing extension functionality. - - To access a specific extension, use the __getitem__ on this class with the - extension ID:: - - # Access the "bands" property on the eo extension. - item.ext['eo'].bands - """ - def __init__(self, stac_object): - self.stac_object = stac_object - - def __getitem__(self, extension_id): - """Gets the extension object for the given extension. - - Returns: - CatalogExtension or CollectionExtension or ItemExtension: The extension object - through which you can access the extension functionality for the extension represented - by the extension_id. - """ - # Check to make sure this is a registered extension. - if not pystac.STAC_EXTENSIONS.is_registered_extension(extension_id): - raise ExtensionError("'{}' is not an extension " - "registered with PySTAC".format(extension_id)) - - if not self.implements(extension_id): - raise ExtensionError("{} does not implement the {} extension. " - "Use the 'ext.enable' method to enable this extension " - "first.".format(self.stac_object, extension_id)) - - return pystac.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) - - def __getattr__(self, extension_id): - """Gets an extension based on a dynamic attribute. - - This takes the attribute name and passes it to __getitem__. - - This allows the following two lines to be equivalent:: - - item.ext["label"].label_properties - item.ext.label.label_properties - """ - if extension_id.startswith('__') and hasattr(ExtensionIndex, extension_id): - return self.__getattribute__(extension_id) - return self[extension_id] - - def enable(self, extension_id): - """Enables a stac extension for the given object. If the object already - enables the extension, no action is taken. If it does not, the extension ID is - added to the object's stac_extension property. - - Args: - extension_id (str): The extension ID representing the extension - the object should implement - - """ - pystac.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) - - def implements(self, extension_id): - """Returns true if the associated object implements the given extension. - - Args: - extension_id (str): The extension ID to check - - Returns: - [bool]: True if the object implements the extensions - i.e. if - the extension ID is in the "stac_extensions" property. - """ - return (self.stac_object.stac_extensions is not None - and extension_id in self.stac_object.stac_extensions) - - class LinkMixin: """Mixin class for adding and accessing links. Implementing classes must have a `links` attribute that is a list of links. """ - def add_link(self, link): + + links: List[Link] + + def add_link(self, link: Link): """Add a link to this object's set of links. Args: link (Link): The link to add. """ - link.set_owner(self) + link.set_owner(cast(STACObject, self)) self.links.append(link) return self - def add_links(self, links): + def add_links(self, links: List[Link]) -> "LinkMixin": """Add links to this object's set of links. Args: @@ -118,7 +53,7 @@ def add_links(self, links): self.add_link(link) return self - def remove_links(self, rel): + def remove_links(self, rel: str) -> "LinkMixin": """Remove links to this object's set of links that match the given ``rel``. Args: @@ -128,7 +63,7 @@ def remove_links(self, rel): self.links = [link for link in self.links if link.rel != rel] return self - def get_single_link(self, rel): + def get_single_link(self, rel: str) -> Optional[Link]: """Get single link that match the given ``rel``. Args: @@ -137,7 +72,7 @@ def get_single_link(self, rel): return next((link for link in self.links if link.rel == rel), None) - def get_links(self, rel=None): + def get_links(self, rel: Optional[str] = None): """Gets the :class:`~pystac.Link` instances associated with this object. Args: @@ -153,7 +88,7 @@ def get_links(self, rel=None): else: return [link for link in self.links if link.rel == rel] - def clear_links(self, rel=None): + def clear_links(self, rel: Optional[str] = None): """Clears all :class:`~pystac.Link` instances associated with this object. Args: @@ -175,7 +110,7 @@ def get_root_link(self): """ return self.get_single_link('root') - def get_self_href(self): + def get_self_href(self) -> Optional[str]: """Gets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -192,11 +127,11 @@ def get_self_href(self): """ self_link = self.get_single_link('self') if self_link: - return self_link.target + return cast(str, self_link.target) else: return None - def set_self_href(self, href): + def set_self_href(self, href: str) -> "LinkMixin": """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -208,14 +143,14 @@ def set_self_href(self, href): """ root_link = self.get_root_link() if root_link is not None and root_link.is_resolved(): - root_link.target._resolved_objects.remove(self) + cast(Catalog, root_link.target)._resolved_objects.remove(cast(STACObject, self)) self.remove_links('self') if href is not None: self.add_link(Link.self_href(href)) if root_link is not None and root_link.is_resolved(): - root_link.target._resolved_objects.cache(self) + cast(Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) return self @@ -230,10 +165,11 @@ class STACObject(LinkMixin, ABC): links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this STACObject. """ + id: str STAC_OBJECT_TYPE = None # Overridden by the child classes with their type. - def __init__(self, stac_extensions): + def __init__(self, stac_extensions: List[str]): self.links = [] self.stac_extensions = stac_extensions @@ -245,7 +181,7 @@ def validate(self): """ return pystac.validation.validate(self) - def get_root(self): + def get_root(self) -> Optional[Catalog]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the root for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'root'``. @@ -259,12 +195,12 @@ def get_root(self): if not root_link.is_resolved(): root_link.resolve_stac_object() # Use set_root, so Catalogs can merge ResolvedObjectCache instances. - self.set_root(root_link.target) - return root_link.target + self.set_root(cast(Catalog, root_link.target)) + return cast(Catalog, root_link.target) else: return None - def set_root(self, root): + def set_root(self, root: Optional[Catalog]) -> "STACObject": """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -279,7 +215,7 @@ def set_root(self, root): if root_link_index is not None: root_link = self.links[root_link_index] if root_link.is_resolved(): - root_link.target._resolved_objects.remove(self) + cast(Catalog, root_link.target)._resolved_objects.remove(self) if root is None: self.remove_links('root') @@ -294,7 +230,7 @@ def set_root(self, root): return self - def get_parent(self): + def get_parent(self) -> Optional["STACObject"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the parent for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'parent'``. @@ -306,11 +242,11 @@ def get_parent(self): """ parent_link = self.get_single_link('parent') if parent_link: - return parent_link.resolve_stac_object().target + return cast(Catalog, parent_link.resolve_stac_object().target) else: return None - def set_parent(self, parent): + def set_parent(self, parent: Optional[Catalog]) -> "STACObject": """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -324,7 +260,7 @@ def set_parent(self, parent): self.add_link(Link.parent(parent)) return self - def get_stac_objects(self, rel): + def get_stac_objects(self, rel: str) -> Generator["STACObject", None, None]: """Gets the :class:`~pystac.STACObject` instances that are linked to by links with their ``rel`` property matching the passed in argument. @@ -341,9 +277,9 @@ def get_stac_objects(self, rel): link = links[i] if link.rel == rel: link.resolve_stac_object(root=self.get_root()) - yield link.target + yield cast("STACObject", link.target) - def save_object(self, include_self_link=True, dest_href=None): + def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = None) -> None: """Saves this STAC Object to it's 'self' HREF. Args: @@ -369,7 +305,9 @@ def save_object(self, include_self_link=True, dest_href=None): STAC_IO.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) - def full_copy(self, root=None, parent=None): + def full_copy(self, + root: Optional[Catalog] = None, + parent: Optional[Catalog] = None) -> "STACObject": """Create a full copy of this STAC object and any stac objects linked to by this object. @@ -385,9 +323,10 @@ def full_copy(self, root=None, parent=None): """ clone = self.clone() - if root is None: + if root is None and isinstance(clone, Catalog): root = clone - clone.set_root(root) + + clone.set_root(cast(Catalog, root)) if parent: clone.set_parent(parent) @@ -395,22 +334,27 @@ def full_copy(self, root=None, parent=None): for link in clone.links: if link.rel in link_rels: link.resolve_stac_object() - target = link.target + target = cast("STACObject", link.target) if target in root._resolved_objects: target = root._resolved_objects.get(target) + assert target is not None else: - copied_target = target.full_copy(root=root, parent=clone) + target_parent = None + if link.rel in ['child', 'item'] and isinstance(clone, Catalog): + target_parent = clone + copied_target = target.full_copy(root=root, parent=target_parent) root._resolved_objects.cache(copied_target) target = copied_target if link.rel in ['child', 'item']: target.set_root(root) - target.set_parent(clone) + if isinstance(clone, Catalog): + target.set_parent(clone) link.target = target return clone @property - def ext(self): + def ext(self) -> "ExtensionIndex": """Access extensions for this STACObject. Example: @@ -441,7 +385,7 @@ def resolve_links(self): link.resolve_stac_object(root=self.get_root()) @abstractmethod - def _object_links(self): + def _object_links(self) -> List[str]: """Inherited classes return a list of link 'rel' types that represent STACObjects linked to by this object (not including root, parent or self). This can include optional relations (which may not be present). @@ -449,20 +393,19 @@ def _object_links(self): pass @abstractmethod - def to_dict(self, include_self_link=True): + def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this serialized object. Args: include_self_link (bool): If True, the dict will contain a self link to this object. If False, the self link will be omitted. - Returns: - dict: A serializion of the object that can be written out as JSON. + dict: A serialization of the object that can be written out as JSON. """ pass @abstractmethod - def clone(self): + def clone(self) -> "STACObject": """Clones this object. Cloning an object will make a copy of all properties and links of the object; @@ -476,7 +419,7 @@ def clone(self): pass @classmethod - def from_file(cls, href): + def from_file(cls, href: str) -> "STACObject": """Reads a STACObject implementation from a file. Args: @@ -491,9 +434,9 @@ def from_file(cls, href): d = STAC_IO.read_json(href) if cls == STACObject: - o = STAC_IO.stac_object_from_dict(d, href=href) + o = STAC_IO.stac_object_from_dict(d, href, None) else: - o = cls.from_dict(d, href=href) + o = cls.from_dict(d, href, None) # Set the self HREF, if it's not already set to something else. if o.get_self_href() is None: @@ -504,12 +447,15 @@ def from_file(cls, href): if root_link is not None: if not root_link.is_resolved(): if root_link.get_absolute_href() == href: - o.set_root(o) + o.set_root(cast(Catalog, o)) return o @classmethod @abstractmethod - def from_dict(cls, d, href=None, root=None): + def from_dict(cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None) -> "STACObject": """Parses this STACObject from the passed in dictionary. Args: @@ -524,3 +470,77 @@ def from_dict(cls, d, href=None, root=None): STACObject: The STACObject parsed from this dict. """ pass + + +class ExtensionIndex: + """Defines methods for accessing extension functionality. + + To access a specific extension, use the __getitem__ on this class with the + extension ID:: + + # Access the "bands" property on the eo extension. + item.ext['eo'].bands + """ + def __init__(self, stac_object: STACObject) -> None: + self.stac_object = stac_object + + def __getitem__( + self, extension_id: str) -> STACObjectExtension: + """Gets the extension object for the given extension. + + Returns: + CatalogExtension or CollectionExtension or ItemExtension: The extension object + through which you can access the extension functionality for the extension represented + by the extension_id. + """ + # Check to make sure this is a registered extension. + if not pystac.STAC_EXTENSIONS.is_registered_extension(extension_id): + raise ExtensionError("'{}' is not an extension " + "registered with PySTAC".format(extension_id)) + + if not self.implements(extension_id): + raise ExtensionError("{} does not implement the {} extension. " + "Use the 'ext.enable' method to enable this extension " + "first.".format(self.stac_object, extension_id)) + + return pystac.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) + + def __getattr__( + self, extension_id: str) -> STACObjectExtension: + """Gets an extension based on a dynamic attribute. + + This takes the attribute name and passes it to __getitem__. + + This allows the following two lines to be equivalent:: + + item.ext["label"].label_properties + item.ext.label.label_properties + """ + if extension_id.startswith('__') and hasattr(ExtensionIndex, extension_id): + return self.__getattribute__(extension_id) + return self[extension_id] + + def enable(self, extension_id: str) -> None: + """Enables a stac extension for the given object. If the object already + enables the extension, no action is taken. If it does not, the extension ID is + added to the object's stac_extension property. + + Args: + extension_id (str): The extension ID representing the extension + the object should implement + + """ + pystac.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) + + def implements(self, extension_id: str) -> bool: + """Returns true if the associated object implements the given extension. + + Args: + extension_id (str): The extension ID to check + + Returns: + [bool]: True if the object implements the extensions - i.e. if + the extension ID is in the "stac_extensions" property. + """ + return (self.stac_object.stac_extensions is not None + and extension_id in self.stac_object.stac_extensions) diff --git a/pystac/utils.py b/pystac/utils.py index 534e3bfae..4dc627972 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -1,7 +1,8 @@ import os import posixpath +from typing import Any, Dict, List, Optional, Union from urllib.parse import (urlparse, ParseResult as URLParseResult) -from datetime import timezone +from datetime import datetime, timezone import dateutil.parser # Allow for modifying the path library for testability @@ -9,7 +10,7 @@ _pathlib = os.path -def _urlparse(href): +def _urlparse(href: str) -> URLParseResult: """Version of URL parse that takes into account windows paths. A windows absolute path will be parsed with a scheme from urllib.parse.urlparse. @@ -27,7 +28,7 @@ def _urlparse(href): return parsed -def _join(is_path, *args): +def _join(is_path: bool, *args: str) -> str: """Version of os.path.join that takes into account whether or not we are working with a URL. @@ -39,7 +40,7 @@ def _join(is_path, *args): return posixpath.join(*args) -def make_relative_href(source_href, start_href, start_is_dir=False): +def make_relative_href(source_href: str, start_href: str, start_is_dir: bool=False) -> str: """Makes a given HREF relative to the given starting HREF. Args: @@ -75,7 +76,9 @@ def make_relative_href(source_href, start_href, start_is_dir=False): return relpath -def make_absolute_href(source_href, start_href=None, start_is_dir=False): +def make_absolute_href(source_href: str, + start_href: Optional[str] = None, + start_is_dir: bool = False) -> str: """Makes a given HREF absolute based on the given starting HREF. Args: @@ -92,7 +95,7 @@ def make_absolute_href(source_href, start_href=None, start_is_dir=False): return None. """ if source_href is None: - return None + return None # TODO: Remove the None case if start_href is None: start_href = os.getcwd() @@ -128,7 +131,7 @@ def make_absolute_href(source_href, start_href=None, start_is_dir=False): return source_href -def is_absolute_href(href): +def is_absolute_href(href: str) -> bool: """Determines if an HREF is absolute or not. Args: @@ -141,7 +144,7 @@ def is_absolute_href(href): return parsed.scheme != '' or _pathlib.isabs(parsed.path) -def datetime_to_str(dt): +def datetime_to_str(dt: datetime) -> str: """Convert a python datetime to an ISO8601 string Args: @@ -161,11 +164,11 @@ def datetime_to_str(dt): return timestamp -def str_to_datetime(s): +def str_to_datetime(s: str) -> datetime: return dateutil.parser.parse(s) -def geometry_to_bbox(geometry): +def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]: """Extract the bounding box from a geojson geometry Args: @@ -177,22 +180,22 @@ def geometry_to_bbox(geometry): """ coords = geometry['coordinates'] - lats = [] - lons = [] + lats: List[float] = [] + lons: List[float] = [] - def extract_coords(coords): + def extract_coords(coords: List[Union[List[float], List[List[Any]]]]): for x in coords: # This handles points if isinstance(x, float): - lats.append(coords[0]) - lons.append(coords[1]) + lats.append(coords[0]) # type:ignore + lons.append(coords[1]) # type:ignore return if isinstance(x[0], list): - extract_coords(x) + extract_coords(x) # type:ignore else: lat, lon = x - lats.append(lat) - lons.append(lon) + lats.append(lat) # type:ignore + lons.append(lon) # type:ignore extract_coords(coords) diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 52166db19..4d4e80215 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -1,10 +1,10 @@ # flake8: noqa +from typing import Dict, List, Any, Optional, cast +from pystac.stac_object import STACObject import pystac from pystac.serialization.identify import identify_stac_object from pystac.utils import make_absolute_href -from pystac.validation.schema_uri_map import (SchemaUriMap) - class STACValidationError(Exception): """Represents a validation error. Thrown by validation calls if the STAC JSON @@ -15,7 +15,7 @@ class STACValidationError(Exception): validation implementation. For the default JsonSchemaValidator this will a the ``jsonschema.ValidationError``. """ - def __init__(self, message, source=None): + def __init__(self, message: str, source: Optional[Any]=None): super().__init__(message) self.source = source @@ -24,7 +24,7 @@ def __init__(self, message, source=None): from pystac.validation.stac_validator import (STACValidator, JsonSchemaSTACValidator) -def validate(stac_object): +def validate(stac_object: STACObject) -> List[Any]: """Validates a :class:`~pystac.STACObject`. Args: @@ -38,14 +38,14 @@ def validate(stac_object): Raises: STACValidationError """ - validate_dict(stac_dict=stac_object.to_dict(), + return validate_dict(stac_dict=stac_object.to_dict(), stac_object_type=stac_object.STAC_OBJECT_TYPE, stac_version=pystac.get_stac_version(), extensions=stac_object.stac_extensions, href=stac_object.get_self_href()) -def validate_dict(stac_dict, stac_object_type=None, stac_version=None, extensions=None, href=None): +def validate_dict(stac_dict: Dict[str, Any], stac_object_type: Optional[str]=None, stac_version: Optional[str]=None, extensions: Optional[List[str]]=None, href: Optional[str]=None) -> List[Any]: """Validate a stac object serialized as JSON into a dict. This method delegates to the call to :meth:`pystac.validation.STACValidator.validate` @@ -78,7 +78,7 @@ def validate_dict(stac_dict, stac_object_type=None, stac_version=None, extension if stac_version is None: if info is None: info = identify_stac_object(stac_dict) - stac_version = info.version_range.latest_valid_version() + stac_version = str(info.version_range.latest_valid_version()) if extensions is None: if info is None: info = identify_stac_object(stac_dict) @@ -88,7 +88,7 @@ def validate_dict(stac_dict, stac_object_type=None, stac_version=None, extension extensions, href) -def validate_all(stac_dict, href): +def validate_all(stac_dict: Dict[str, Any], href: str) -> None: """Validate STAC JSON and all contained catalogs, collections and items. If this stac_dict represents a catalog or collection, this method will @@ -109,34 +109,34 @@ def validate_all(stac_dict, href): # Validate this object validate_dict(stac_dict, stac_object_type=info.object_type, - stac_version=info.version_range.latest_valid_version(), + stac_version=str(info.version_range.latest_valid_version()), extensions=info.common_extensions, href=href) if info.object_type != pystac.STACObjectType.ITEM: - links = stac_dict.get('links') - if links is not None: + if 'links' in stac_dict: # Account for 0.6 links - if isinstance(links, dict): - links = list(links.values()) - + if isinstance(stac_dict['links'], Dict[str, Dict[str, Any]]): + links: List[Dict[str, Any]] = list(stac_dict['links'].values()) + else: + links: List[Dict[str, Any]] = cast(List[Dict[str, Any]], stac_dict.get('links')) for link in links: rel = link.get('rel') if rel in ['item', 'child']: - link_href = make_absolute_href(link.get('href'), start_href=href) + link_href = make_absolute_href(cast(str, link.get('href')), start_href=href) if link_href is not None: d = pystac.STAC_IO.read_json(link_href) validate_all(d, link_href) class RegisteredValidator: - _validator = None + _validator: Optional[STACValidator] = None @classmethod - def get_validator(cls): + def get_validator(cls) -> STACValidator: if cls._validator is None: try: - import jsonschema + import jsonschema # type:ignore except ImportError: raise Exception( 'Cannot validate with default validator because package "jsonschema" ' @@ -147,19 +147,17 @@ def get_validator(cls): return cls._validator @classmethod - def set_validator(cls, validator): + def set_validator(cls, validator: STACValidator) -> None: if not issubclass(type(validator), STACValidator): raise Exception('Validator must be a subclass of {}'.format(STACValidator)) cls._validator = validator -def set_validator(validator): +def set_validator(validator: STACValidator): """Sets the STACValidator to use in PySTAC. - TKTK - Args: - validator (STACValidator): The STACVlidator implementation to use for + validator (STACValidator): The STACValidator implementation to use for validation. """ RegisteredValidator.set_validator(validator) diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 27b5d0782..13a247c27 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,4 +1,5 @@ from abc import (ABC, abstractmethod) +from typing import Any, Callable, Dict, List, Tuple import pystac from pystac import (STACObjectType, Extensions) @@ -12,11 +13,11 @@ def __init__(self): pass @abstractmethod - def get_core_schema_uri(self, object_type, stac_version): + def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> str: """Get the schema URI for the given object type and stac version. Args: - object_type (str): STAC object type. One of :class:`~pystac.STACObjectType` + object_type (STACObjectType): STAC object type. One of :class:`~pystac.STACObjectType` stac_version (str): The STAC version of the schema to return. Returns: @@ -25,7 +26,8 @@ def get_core_schema_uri(self, object_type, stac_version): pass @abstractmethod - def get_extension_schema_uri(self, extension_id, object_type, stac_version): + def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, + stac_version: str) -> str: """Get the extension's schema URI for the given object type, stac version. Args: @@ -51,10 +53,12 @@ class DefaultSchemaUriMap(SchemaUriMap): # for a particular version uses the base URI associated with the version range which # contains it. If the version it outside of any VersionRange, there is no URI for the # schema. - BASE_URIS = [(STACVersionRange(min_version='1.0.0-beta.1'), - lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), - (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: - 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] + BASE_URIS: List[Tuple[STACVersionRange, Callable[[str], str]]] = [ + (STACVersionRange(min_version='1.0.0-beta.1'), + lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), + (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: + 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version)) + ] # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema URIs # based on the stac object type and the stac version, using a similar technique as BASE_URIS. @@ -63,7 +67,7 @@ class DefaultSchemaUriMap(SchemaUriMap): # is the latest version. If it's a previous version, the stac_version that matches # the listed version range is used, or else the URI from the latest version is used if # there are no overrides for previous versions. - DEFAULT_SCHEMA_MAP = { + DEFAULT_SCHEMA_MAP: Dict[str, Dict[str, Any]] = { 'core': { STACObjectType.CATALOG: ('catalog-spec/json-schema/catalog.json', None), STACObjectType.COLLECTION: ('collection-spec/json-schema/collection.json', None), @@ -158,7 +162,7 @@ class DefaultSchemaUriMap(SchemaUriMap): } @classmethod - def _append_base_uri_if_needed(cls, uri, stac_version): + def _append_base_uri_if_needed(cls, uri: str, stac_version: str): # Only append the base URI if it's not already an absolute URI if '://' not in uri: base_uri = None @@ -172,7 +176,7 @@ def _append_base_uri_if_needed(cls, uri, stac_version): else: return uri - def get_core_schema_uri(self, object_type, stac_version): + def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str): uri = None is_latest = stac_version == pystac.get_stac_version() @@ -189,7 +193,7 @@ def get_core_schema_uri(self, object_type, stac_version): return self._append_base_uri_if_needed(uri, stac_version) - def get_extension_schema_uri(self, extension_id, object_type, stac_version): + def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, stac_version: str): uri = None is_latest = stac_version == pystac.get_stac_version() diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 08669a45d..dfdb7e43c 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -1,12 +1,16 @@ import json from abc import (ABC, abstractmethod) +from pystac.stac_object import STACObjectType +from typing import Any, Dict, List, Optional, Tuple from pystac import STAC_IO from pystac.validation import STACValidationError -from pystac.validation.schema_uri_map import DefaultSchemaUriMap +from pystac.validation.schema_uri_map import DefaultSchemaUriMap, SchemaUriMap try: import jsonschema + import jsonschema.validators + import jsonschema.exceptions except ImportError: jsonschema = None @@ -19,7 +23,11 @@ class STACValidator(ABC): pystac by using the :func:`~pystac.validation.set_validator` method. """ @abstractmethod - def validate_core(self, stac_dict, stac_object_type, stac_version, href=None): + def validate_core(self, + stac_dict: Dict[str, Any], + stac_object_type: str, + stac_version: str, + href: Optional[str] = None): """Validate a core stac object. Return value can be None or specific to the implementation. @@ -35,11 +43,11 @@ def validate_core(self, stac_dict, stac_object_type, stac_version, href=None): @abstractmethod def validate_extension(self, - stac_dict, - stac_object_type, - stac_version, - extension_id, - href=None): + stac_dict: Dict[str, Any], + stac_object_type: str, + stac_version: str, + extension_id: str, + href: Optional[str] = None): """Validate an extension stac object. Return value can be None or specific to the implementation. @@ -54,7 +62,12 @@ def validate_extension(self, """ pass - def validate(self, stac_dict, stac_object_type, stac_version, extensions, href=None): + def validate(self, + stac_dict: Dict[str, Any], + stac_object_type: str, + stac_version: str, + extensions: List[str], + href: Optional[str] = None): """Validate a STAC object JSON. Args: @@ -66,11 +79,11 @@ def validate(self, stac_dict, stac_object_type, stac_version, extensions, href=N href (str): Optional href of the STAC object being validated. Returns: - List[Object]: List of return values from the validation calls for the + List[Any]: List of return values from the validation calls for the core object and any extensions. Element type is specific to the STACValidator implementation. """ - results = [] + results: List[Any] = [] # Pass the dict through JSON serialization and parsing, otherwise # some valid properties can be marked as invalid (e.g. tuples in @@ -105,7 +118,7 @@ class JsonSchemaSTACValidator(STACValidator): Note: This class requires the ``jsonschema`` library to be installed. """ - def __init__(self, schema_uri_map=None): + def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None): if jsonschema is None: raise Exception('Cannot instantiate, requires jsonschema package') @@ -114,9 +127,9 @@ def __init__(self, schema_uri_map=None): else: self.schema_uri_map = DefaultSchemaUriMap() - self.schema_cache = {} + self.schema_cache: Dict[str, Dict[str, Any]] = {} - def get_schema_from_uri(self, schema_uri): + def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: if schema_uri not in self.schema_cache: s = json.loads(STAC_IO.read_text(schema_uri)) self.schema_cache[schema_uri] = s @@ -129,14 +142,16 @@ def get_schema_from_uri(self, schema_uri): return (schema, resolver) - def _validate_from_uri(self, stac_dict, schema_uri): + def _validate_from_uri(self, stac_dict: Dict[str, Any], schema_uri: str) -> None: schema, resolver = self.get_schema_from_uri(schema_uri) - jsonschema.validate(instance=stac_dict, schema=schema, resolver=resolver) + jsonschema.validate(instance=stac_dict, schema=schema, resolver=resolver) # type:ignore for uri in resolver.store: if uri not in self.schema_cache: self.schema_cache[uri] = resolver.store[uri] - def _get_error_message(self, schema_uri, stac_object_type, extension_id, href, stac_id): + def _get_error_message(self, schema_uri: str, stac_object_type: STACObjectType, + extension_id: Optional[str], href: Optional[str], + stac_id: Optional[str]) -> str: s = 'Validation failed for {} '.format(stac_object_type) if href is not None: s += 'at {} '.format(href) @@ -148,7 +163,11 @@ def _get_error_message(self, schema_uri, stac_object_type, extension_id, href, s return s - def validate_core(self, stac_dict, stac_object_type, stac_version, href=None): + def validate_core(self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + href: Optional[str] = None): """Validate a core stac object. Return value can be None or specific to the implementation. @@ -178,11 +197,11 @@ def validate_core(self, stac_dict, stac_object_type, stac_version, href=None): raise STACValidationError(msg, source=e) from e def validate_extension(self, - stac_dict, - stac_object_type, - stac_version, - extension_id, - href=None): + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + extension_id: str, + href: Optional[str]=None): """Validate an extension stac object. Return value can be None or specific to the implementation. diff --git a/pystac/version.py b/pystac/version.py index 096f0ebc7..ab69c70f3 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -1,4 +1,5 @@ import os +from typing import Optional __version__ = '0.5.6' """Library version""" @@ -9,12 +10,12 @@ class STACVersion: """Latest STAC version supported by PySTAC""" # Version that holds a user-set STAC version to use. - _override_version = None + _override_version: Optional[str] = None OVERRIDE_VERSION_ENV_VAR = 'PYSTAC_STAC_VERSION_OVERRIDE' @classmethod - def get_stac_version(cls): + def get_stac_version(cls) -> str: if cls._override_version is not None: return cls._override_version @@ -25,11 +26,11 @@ def get_stac_version(cls): return cls.DEFAULT_STAC_VERSION @classmethod - def set_stac_version(cls, stac_version): + def set_stac_version(cls, stac_version: str) -> None: cls._override_version = stac_version -def get_stac_version(): +def get_stac_version() -> str: """Returns the STAC version PySTAC writes as the "stac_version" property for any object it serializes into JSON. @@ -43,7 +44,7 @@ def get_stac_version(): return STACVersion.get_stac_version() -def set_stac_version(stac_version): +def set_stac_version(stac_version: str): """Sets the STAC version that PySTAC should use. This is the version that will be set as the "stac_version" property From 4bb702aad35dba9261d5108a93b92a4fcc42f1b4 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sat, 24 Apr 2021 23:17:31 -0400 Subject: [PATCH 03/51] Update code and tests to pass with type annotations --- pystac/__init__.py | 16 ++- pystac/cache.py | 60 +++++---- pystac/catalog.py | 93 ++++++++----- pystac/collection.py | 49 ++++--- pystac/extensions/base.py | 28 ++-- pystac/extensions/file.py | 14 +- pystac/extensions/label.py | 28 ++-- pystac/extensions/projection.py | 8 +- pystac/extensions/sar.py | 10 +- pystac/extensions/single_file_stac.py | 1 - pystac/item.py | 35 +++-- pystac/layout.py | 86 ++++++------ pystac/link.py | 75 +++++++---- pystac/serialization/__init__.py | 33 +++-- pystac/serialization/common_properties.py | 24 ++-- pystac/serialization/identify.py | 43 +++--- pystac/serialization/migrate.py | 87 ++++++------ pystac/stac_io.py | 13 +- pystac/stac_object.py | 90 ++++++++----- pystac/utils.py | 5 +- pystac/validation/__init__.py | 26 ++-- pystac/validation/schema_uri_map.py | 3 +- pystac/validation/stac_validator.py | 2 +- tests/data-files/examples/example-info.csv | 146 +++++++++++++++++++-- tests/extensions/test_label.py | 23 ++-- tests/serialization/test_identify.py | 57 ++++---- tests/serialization/test_migrate.py | 19 ++- tests/test_catalog.py | 99 +++++++------- tests/test_collection.py | 88 ++----------- tests/test_item.py | 67 ++++------ tests/test_layout.py | 131 +++++++++--------- tests/test_writing.py | 47 ++++--- tests/utils/test_cases.py | 26 ++-- tests/validation/test_validate.py | 56 ++++---- 34 files changed, 905 insertions(+), 683 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index 7dc8b13fb..c4f4c54e4 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -13,6 +13,14 @@ class STACError(Exception): pass +class STACTypeError(Exception): + """A STACTypeError is raised when encountering a representation of + a STAC entity that is not correct for the context; for example, if + a Catalog JSON was read in as an Item. + """ + pass + + from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore from pystac.stac_io import STAC_IO @@ -21,8 +29,12 @@ class STACError(Exception): from pystac.media_type import MediaType # type:ignore from pystac.link import (Link, HIERARCHICAL_LINKS) # type:ignore from pystac.catalog import (Catalog, CatalogType) # type:ignore -from pystac.collection import (Collection, Extent, SpatialExtent, TemporalExtent, # type:ignore - Provider) # type:ignore +from pystac.collection import ( + Collection, # type:ignore + Extent, # type:ignore + SpatialExtent, # type:ignore + TemporalExtent, # type:ignore + Provider) # type:ignore from pystac.item import (Item, Asset, CommonMetadata) # type:ignore from pystac.serialization import stac_object_from_dict diff --git a/pystac/cache.py b/pystac/cache.py index fabe4b18d..c8ea88277 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -1,13 +1,15 @@ from collections import ChainMap from copy import copy -from pystac.collection import Collection -from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pystac.stac_object import STACObject +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast -import pystac +import pystac as ps +if TYPE_CHECKING: + from pystac.stac_object import STACObject + from pystac.collection import Collection -def get_cache_key(stac_object: STACObject) -> Tuple[str, bool]: + +def get_cache_key(stac_object: "STACObject") -> Tuple[str, bool]: """Produce a cache key for the given STAC object. If a self href is set, use that as the cache key. @@ -56,16 +58,16 @@ class ResolvedObjectCache: ids_to_collections (Dict[str, Collection]): Map of collection IDs to collections. """ def __init__(self, - id_keys_to_objects: Optional[Dict[str, STACObject]] = None, - hrefs_to_objects: Optional[Dict[str, STACObject]] = None, - ids_to_collections: Dict[str, Collection] = None): + id_keys_to_objects: Optional[Dict[str, "STACObject"]] = None, + hrefs_to_objects: Optional[Dict[str, "STACObject"]] = None, + ids_to_collections: Dict[str, "Collection"] = None): self.id_keys_to_objects = id_keys_to_objects or {} self.hrefs_to_objects = hrefs_to_objects or {} self.ids_to_collections = ids_to_collections or {} self._collection_cache = None - def get_or_cache(self, obj: STACObject) -> STACObject: + def get_or_cache(self, obj: "STACObject") -> "STACObject": """Gets the STACObject that is the cached version of the given STACObject; or, if none exists, sets the cached object to the given object. @@ -91,7 +93,7 @@ def get_or_cache(self, obj: STACObject) -> STACObject: self.cache(obj) return obj - def get(self, obj: STACObject) -> Optional[STACObject]: + def get(self, obj: "STACObject") -> Optional["STACObject"]: """Get the cached object that has the same cache key as the given object. Args: @@ -107,7 +109,7 @@ def get(self, obj: STACObject) -> Optional[STACObject]: else: return self.id_keys_to_objects.get(key) - def get_by_href(self, href: str) -> Optional[STACObject]: + def get_by_href(self, href: str) -> Optional["STACObject"]: """Gets the cached object at href. Args: @@ -118,7 +120,7 @@ def get_by_href(self, href: str) -> Optional[STACObject]: """ return self.hrefs_to_objects.get(href) - def get_collection_by_id(self, id: str) -> Optional[Collection]: + def get_collection_by_id(self, id: str) -> Optional["Collection"]: """Retrieved a cached Collection by its ID. Args: @@ -130,7 +132,7 @@ def get_collection_by_id(self, id: str) -> Optional[Collection]: """ return self.ids_to_collections.get(id) - def cache(self, obj: STACObject) -> None: + def cache(self, obj: "STACObject") -> None: """Set the given object into the cache. Args: @@ -142,10 +144,10 @@ def cache(self, obj: STACObject) -> None: else: self.id_keys_to_objects[key] = obj - if isinstance(obj, Collection): + if isinstance(obj, ps.Collection): self.ids_to_collections[obj.id] = obj - def remove(self, obj: STACObject) -> None: + def remove(self, obj: "STACObject") -> None: """Removes any cached object that matches the given object's cache key. Args: @@ -158,10 +160,10 @@ def remove(self, obj: STACObject) -> None: else: self.id_keys_to_objects.pop(key, None) - if obj.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION: + if obj.STAC_OBJECT_TYPE == ps.STACObjectType.COLLECTION: self.id_keys_to_objects.pop(obj.id, None) - def __contains__(self, obj: STACObject) -> bool: + def __contains__(self, obj: "STACObject") -> bool: key, is_href = get_cache_key(obj) return key in self.hrefs_to_objects if is_href else key in self.id_keys_to_objects @@ -213,23 +215,25 @@ class CollectionCache: and will set Collection JSON that it reads in order to merge in common properties. """ def __init__(self, - cached_ids: Dict[str, Union[Collection, Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union[Collection, Dict[str, Any]]] = None): + cached_ids: Dict[str, Union["Collection", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection", Dict[str, Any]]] = None): self.cached_ids = cached_ids or {} self.cached_hrefs = cached_hrefs or {} - def get_by_id(self, collection_id: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Optional[Union["Collection", Dict[str, Any]]]: return self.cached_ids.get(collection_id) - def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_href(self, href: str) -> Optional[Union["Collection", Dict[str, Any]]]: return self.cached_hrefs.get(href) def contains_id(self, collection_id: str) -> bool: return collection_id in self.cached_ids - def cache(self, collection: Union[Collection, Dict[str, Any]], href: Optional[str] = None) -> None: + def cache(self, + collection: Union["Collection", Dict[str, Any]], + href: Optional[str] = None) -> None: """Caches a collection JSON.""" - if isinstance(collection, Collection): + if isinstance(collection, ps.Collection): self.cached_ids[collection.id] = collection else: self.cached_ids[collection['id']] = collection @@ -241,24 +245,24 @@ def cache(self, collection: Union[Collection, Dict[str, Any]], href: Optional[st class ResolvedObjectCollectionCache(CollectionCache): def __init__(self, resolved_object_cache: ResolvedObjectCache, - cached_ids: Dict[str, Union[Collection, Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union[Collection, Dict[str, Any]]] = None): + cached_ids: Dict[str, Union["Collection", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection", Dict[str, Any]]] = None): super().__init__(cached_ids, cached_hrefs) self.resolved_object_cache = resolved_object_cache - def get_by_id(self, collection_id: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Optional[Union["Collection", Dict[str, Any]]]: result = self.resolved_object_cache.get_collection_by_id(collection_id) if result is None: return super().get_by_id(collection_id) else: return result - def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_href(self, href: str) -> Optional[Union["Collection", Dict[str, Any]]]: result = self.resolved_object_cache.get_by_href(href) if result is None: return super().get_by_href(href) else: - return cast(Collection, result) + return cast(ps.Collection, result) def contains_id(self, collection_id: str) -> bool: return (self.resolved_object_cache.contains_collection_id(collection_id) diff --git a/pystac/catalog.py b/pystac/catalog.py index 89bbe4f4b..ac91592d9 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -1,16 +1,16 @@ import os from copy import deepcopy from enum import Enum -from pystac.item import Asset, Item -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast +from typing import Any, Callable, Dict, Iterable, List, Optional, TYPE_CHECKING, Tuple, Union, cast -import pystac -from pystac import STACError +import pystac as ps from pystac.stac_object import STACObject from pystac.layout import (BestPracticesLayoutStrategy, HrefLayoutStrategy, LayoutTemplate) from pystac.link import Link from pystac.cache import ResolvedObjectCache from pystac.utils import (is_absolute_href, make_absolute_href) +if TYPE_CHECKING: + from pystac.item import Asset as AssetType, Item as ItemType class CatalogType(str, Enum): @@ -108,7 +108,7 @@ class Catalog(STACObject): catalog_type (str): The catalog type. Defaults to ABSOLUTE_PUBLISHED """ - STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG + STAC_OBJECT_TYPE = ps.STACObjectType.CATALOG DEFAULT_FILE_NAME = "catalog.json" """Default file name that will be given to this STAC object in a canonical format.""" @@ -169,8 +169,8 @@ def add_child(self, """ # Prevent typo confusion - if isinstance(child, pystac.Item): - raise STACError('Cannot add item as child. Use add_item instead.') + if isinstance(child, ps.Item): + raise ps.STACError('Cannot add item as child. Use add_item instead.') if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -198,7 +198,7 @@ def add_children(self, children: Iterable["Catalog"]) -> None: self.add_child(child) def add_item(self, - item: Item, + item: "ItemType", title: Optional[str] = None, strategy: Optional[HrefLayoutStrategy] = None) -> None: """Adds a link to an :class:`~pystac.Item`. @@ -211,8 +211,8 @@ def add_item(self, """ # Prevent typo confusion - if isinstance(item, pystac.Catalog): - raise STACError('Cannot add catalog as item. Use add_child instead.') + if isinstance(item, ps.Catalog): + raise ps.STACError('Cannot add catalog as item. Use add_child instead.') if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -228,7 +228,7 @@ def add_item(self, self.add_link(Link.item(item, title=title)) - def add_items(self, items: Iterable[Item]) -> None: + def add_items(self, items: Iterable["ItemType"]) -> None: """Adds links to multiple :class:`~pystac.Item` s. This method will set each item's parent to this object, and their root to this Catalog's root. @@ -266,7 +266,7 @@ def get_children(self) -> Iterable["Catalog"]: Iterable[Catalog]: Generator of children who's parent is this catalog. """ - return map(lambda x: cast(Catalog, x), self.get_stac_objects('child')) + return map(lambda x: cast(ps.Catalog, x), self.get_stac_objects('child')) def get_child_links(self): """Return all child links of this catalog. @@ -293,7 +293,7 @@ def remove_child(self, child_id: str) -> None: Args: child_id (str): The ID of the child to remove. """ - new_links: List[Link] = [] + new_links: List[ps.Link] = [] root = self.get_root() for link in self.links: if link.rel != 'child': @@ -308,7 +308,7 @@ def remove_child(self, child_id: str) -> None: child.set_root(None) self.links = new_links - def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: + def get_item(self, id: str, recursive: bool = False) -> Optional["ItemType"]: """Returns an item with a given ID. Args: @@ -328,13 +328,13 @@ def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: return item return None - def get_items(self) -> Iterable[Item]: + def get_items(self) -> Iterable["ItemType"]: """Return all items of this catalog. Return: Iterable[Item]: Generator of items who's parent is this catalog. """ - return map(lambda x: cast(Item, x), self.get_stac_objects('item')) + return map(lambda x: cast(ps.Item, x), self.get_stac_objects('item')) def clear_items(self) -> "Catalog": """Removes all items from this catalog. @@ -344,7 +344,7 @@ def clear_items(self) -> "Catalog": """ for link in self.get_item_links(): if link.is_resolved(): - item = cast(Item, link.target) + item = cast(ps.Item, link.target) item.set_parent(None) item.set_root(None) @@ -357,14 +357,14 @@ def remove_item(self, item_id: str) -> None: Args: item_id (str): The ID of the item to remove. """ - new_links: List[Link] = [] + new_links: List[ps.Link] = [] root = self.get_root() for link in self.links: if link.rel != 'item': new_links.append(link) else: link.resolve_stac_object(root=root) - item = cast(Item, link.target) + item = cast(ps.Item, link.target) if item.id != item_id: new_links.append(link) else: @@ -372,7 +372,7 @@ def remove_item(self, item_id: str) -> None: item.set_root(None) self.links = new_links - def get_all_items(self) -> Iterable[Item]: + def get_all_items(self) -> Iterable["ItemType"]: """Get all items from this catalog and all subcatalogs. Will traverse any subcatalogs recursively. @@ -400,7 +400,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d: Dict[str, Any] = { 'id': self.id, - 'stac_version': pystac.get_stac_version(), + 'stac_version': ps.get_stac_version(), 'description': self.description, 'links': [link.to_dict() for link in links] } @@ -498,7 +498,7 @@ def normalize_hrefs(self, root_href: str, strategy: Optional[HrefLayoutStrategy] if not is_absolute_href(root_href): root_href = make_absolute_href(root_href, os.getcwd(), start_is_dir=True) - def process_item(item: Item, _root_href: str) -> Callable[[], None]: + def process_item(item: "ItemType", _root_href: str) -> Callable[[], None]: item.resolve_links() new_self_href = strategy.get_href(item, _root_href) @@ -579,7 +579,7 @@ def generate_subcatalogs(self, item_links = [lk for lk in self.links if lk.rel == 'item'] for link in item_links: link.resolve_stac_object(root=self.get_root()) - item = cast(Item, link.target) + item = cast(ps.Item, link.target) item_parts = layout_template.get_template_values(item) id_iter = reversed(parent_ids) if all(['{}'.format(id) == next(id_iter, None) @@ -596,7 +596,7 @@ def generate_subcatalogs(self, 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) + subcat = ps.Catalog(id=subcat_id, description=subcat_desc) curr_parent.add_child(subcat) result.append(subcat) curr_parent = subcat @@ -647,7 +647,8 @@ def save(self, catalog_type: CatalogType = None) -> None: for item_link in self.get_item_links(): if item_link.is_resolved(): - cast(Item, item_link.target).save_object(include_self_link=items_include_self_link) + cast(ps.Item, + item_link.target).save_object(include_self_link=items_include_self_link) include_self_link = False # include a self link if this is the root catalog or if ABSOLUTE_PUBLISHED catalog @@ -660,7 +661,7 @@ def save(self, catalog_type: CatalogType = None) -> None: self.catalog_type = catalog_type - def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable[Item]]]: + def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["ItemType"]]]: """Walks through children and items of catalogs. For each catalog in the STAC's tree rooted at this catalog (including this catalog @@ -699,9 +700,9 @@ def validate_all(self) -> None: item.validate() def _object_links(self) -> List[str]: - return ['child', 'item'] + (pystac.STAC_EXTENSIONS.get_extended_object_links(self)) + return ['child', 'item'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) - def map_items(self, item_mapper: Callable[[Item], Union[Item, List[Item]]]): + def map_items(self, item_mapper: Callable[["ItemType"], Union["ItemType", List["ItemType"]]]): """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -724,10 +725,10 @@ def process_catalog(catalog: Catalog): item_links: List[Link] = [] for item_link in catalog.get_item_links(): item_link.resolve_stac_object(root=self.get_root()) - mapped = item_mapper(cast(Item, item_link.target)) + mapped = item_mapper(cast(ps.Item, item_link.target)) if mapped is None: raise Exception('item_mapper cannot return None.') - if isinstance(mapped, Item): + if isinstance(mapped, ps.Item): item_link.target = mapped item_links.append(item_link) else: @@ -741,8 +742,9 @@ def process_catalog(catalog: Catalog): process_catalog(new_cat) return new_cat - def map_assets(self, asset_mapper: Callable[[str, Asset], Union[Asset, Tuple[str, Asset], - Dict[str, Asset]]]): + def map_assets(self, asset_mapper: Callable[[str, "AssetType"], + Union["AssetType", Tuple[str, "AssetType"], + Dict[str, "AssetType"]]]): """Creates a copy of a catalog, with each Asset for each Item passed through the asset_mapper function. @@ -756,12 +758,12 @@ def map_assets(self, asset_mapper: Callable[[str, Asset], Union[Asset, Tuple[str Catalog: A full copy of this catalog, with assets manipulated according to the asset_mapper function. """ - def apply_asset_mapper(tup: Tuple[str, Asset]): + def apply_asset_mapper(tup: Tuple[str, "AssetType"]): k, v = tup result = asset_mapper(k, v) if result is None: raise Exception('asset_mapper cannot return None.') - if isinstance(result, pystac.Asset): + if isinstance(result, ps.Asset): return [(k, result)] elif isinstance(result, tuple): return [result] @@ -771,7 +773,7 @@ def apply_asset_mapper(tup: Tuple[str, Asset]): raise Exception('asset_mapper must return a non-empty list') return assets - def item_mapper(item: Item): + def item_mapper(item: ps.Item): new_assets = [ x for result in map(apply_asset_mapper, item.assets.items()) for x in result ] @@ -804,7 +806,14 @@ def describe(self, include_hrefs: bool = False, _indent: int = 0): def from_dict(cls, d: Dict[str, Any], href: Optional[str] = None, - root: Optional["Catalog"] = None) -> "Catalog": + root: Optional["Catalog"] = None, + migrate: bool = False) -> "Catalog": + if migrate: + result = ps.read_dict(d, href=href, root=root) + if not isinstance(result, Catalog): + raise ps.STACError(f"{result} is not a Catalog") + return result + catalog_type = CatalogType.determine_type(d) d = deepcopy(d) @@ -834,3 +843,15 @@ def from_dict(cls, cat.add_link(Link.from_dict(link)) return cat + + def full_copy(self, + root: Optional["Catalog"] = None, + parent: Optional["Catalog"] = None) -> "Catalog": + return cast(Catalog, super().full_copy(root, parent)) + + @classmethod + def from_file(cls, href: str) -> "Catalog": + result = super().from_file(href) + if not isinstance(result, Catalog): + raise ps.STACTypeError(f"{result} is not a {Catalog}.") + return result diff --git a/pystac/collection.py b/pystac/collection.py index e9cb73b44..2f8fbf7a0 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,14 +1,19 @@ +from copy import (copy, deepcopy) from datetime import datetime as Datetime -from pystac.item import Item -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast +from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING, Tuple, Union, cast + import dateutil.parser from dateutil import tz -from copy import (copy, deepcopy) + +import pystac as ps from pystac import (STACObjectType, CatalogType) from pystac.catalog import Catalog from pystac.link import Link from pystac.utils import datetime_to_str +if TYPE_CHECKING: + from pystac.item import Item as ItemType + class SpatialExtent: """Describes the spatial extent of a Collection. @@ -251,7 +256,7 @@ def from_dict(d: Dict[str, Any]) -> "Extent": TemporalExtent.from_dict(temporal_extent_dict)) @staticmethod - def from_items(items: Iterable[Item]) -> "Extent": + def from_items(items: Iterable["ItemType"]) -> "Extent": """Create an Extent based on the datetimes and bboxes of a list of items. Args: @@ -261,11 +266,11 @@ def from_items(items: Iterable[Item]) -> "Extent": Extent: An Extent that spatially and temporally covers all of the given items. """ - def extract_extent_props(item: Item) -> List[Any]: + def extract_extent_props(item: ps.Item) -> List[Any]: return item.bbox + [ item.datetime, item.common_metadata.start_datetime, item.common_metadata.end_datetime - ] # type:ignore + ] # type:ignore xmins, ymins, xmaxs, ymaxs, datetimes, starts, ends = zip(*map(extract_extent_props, items)) @@ -418,23 +423,21 @@ def __init__(self, license: str = 'proprietary', keywords: Optional[List[str]] = None, providers: Optional[List[Provider]] = None, - properties: Optional[Dict[str, Any]] = None, summaries: Optional[Dict[str, Any]] = None): super(Collection, self).__init__(id, description, title, stac_extensions, extra_fields, href, catalog_type or CatalogType.ABSOLUTE_PUBLISHED) self.extent = extent self.license = license - self.stac_extensions = stac_extensions + self.stac_extensions: List[str] = stac_extensions or [] self.keywords = keywords self.providers = providers - self.properties = properties self.summaries = summaries def __repr__(self) -> str: return ''.format(self.id) - def add_item(self, item: Item, title: Optional[str] = None) -> None: + def add_item(self, item: "ItemType", title: Optional[str] = None) -> None: super(Collection, self).add_item(item, title) item.set_collection(self) @@ -448,8 +451,6 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d['keywords'] = self.keywords if self.providers is not None: d['providers'] = list(map(lambda x: x.to_dict(), self.providers)) - if self.properties is not None: - d['properties'] = self.properties if self.summaries is not None: d['summaries'] = self.summaries @@ -466,7 +467,6 @@ def clone(self): license=self.license, keywords=self.keywords, providers=self.providers, - properties=self.properties, summaries=self.summaries) clone._resolved_objects.cache(clone) @@ -488,7 +488,14 @@ def clone(self): def from_dict(cls, d: Dict[str, Any], href: Optional[str] = None, - root: Optional[Catalog] = None) -> "Collection": + root: Optional[Catalog] = None, + migrate: bool = False) -> "Collection": + if migrate: + result = ps.read_dict(d, href=href, root=root) + if not isinstance(result, Collection): + raise ps.STACError(f"{result} is not a Catalog") + return result + catalog_type = CatalogType.determine_type(d) d = deepcopy(d) @@ -502,7 +509,6 @@ def from_dict(cls, providers = d.get('providers') if providers is not None: providers = list(map(lambda x: Provider.from_dict(x), providers)) - properties = d.get('properties') summaries = d.get('summaries') links = d.pop('links') @@ -517,7 +523,6 @@ def from_dict(cls, license=license, keywords=keywords, providers=providers, - properties=properties, summaries=summaries, href=href, catalog_type=catalog_type) @@ -537,3 +542,15 @@ def update_extent_from_items(self): Update datetime and bbox based on all items to a single bbox and time window. """ self.extent = Extent.from_items(self.get_all_items()) + + def full_copy(self, + root: Optional["Catalog"] = None, + parent: Optional["Catalog"] = None) -> "Collection": + return cast(Collection, super().full_copy(root, parent)) + + @classmethod + def from_file(cls, href: str) -> "Collection": + result = super().from_file(href) + if not isinstance(result, Collection): + raise ps.STACTypeError(f"{result} is not a {Collection}.") + return result diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index a2fd8513d..e647464f6 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,16 +1,18 @@ from abc import (ABC, abstractmethod) -from typing import Any, Iterable, List, Optional, Type -from pystac.stac_object import STACObject +from typing import Any, Iterable, List, Optional, TYPE_CHECKING, Type from pystac.catalog import Catalog from pystac.collection import Collection from pystac.item import Asset, Item from pystac.extensions import ExtensionError +if TYPE_CHECKING: + from pystac.stac_object import STACObject + class STACObjectExtension(ABC): @classmethod - def _from_object(cls, stac_object: STACObject) -> "STACObjectExtension": + def _from_object(cls, stac_object: "STACObject") -> "STACObjectExtension": ... @classmethod @@ -19,7 +21,7 @@ def _object_links(cls) -> List[str]: raise NotImplementedError("_object_links") @classmethod - def enable_extension(cls, stac_object: STACObject) -> None: + def enable_extension(cls, stac_object: "STACObject") -> None: """Enables the extension for the given stac_object. Child classes can choose to override this method in order to modify the stac_object when an extension is enabled. @@ -39,7 +41,7 @@ class ExtendedObject: stac_object_class: The STAC object class that is being extended. extension_class: The class of the extension, e.g. LabelItemExt """ - def __init__(self, stac_object_class: Type[STACObject], + def __init__(self, stac_object_class: Type["STACObject"], extension_class: Type[STACObjectExtension]): if stac_object_class is Catalog: if not issubclass(extension_class, CatalogExtension): @@ -74,7 +76,7 @@ def __init__(self, extension_id: str, extended_objects: List[ExtendedObject]): class CatalogExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object: STACObject) -> "CatalogExtension": + def _from_object(cls, stac_object: "STACObject") -> "CatalogExtension": if not isinstance(stac_object, Catalog): raise ValueError(f"This extension applies to Catalogs, not {cls}") return cls.from_catalog(stac_object) @@ -87,7 +89,7 @@ def from_catalog(cls, catalog: Catalog) -> "CatalogExtension": class CollectionExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object: STACObject) -> "CollectionExtension": + def _from_object(cls, stac_object: "STACObject") -> "CollectionExtension": if not isinstance(stac_object, Collection): raise ValueError(f"This extension applies to Collections, not {cls}") return cls.from_collection(stac_object) @@ -102,7 +104,7 @@ class ItemExtension(STACObjectExtension): item: Item @classmethod - def _from_object(cls, stac_object: STACObject) -> "ItemExtension": + def _from_object(cls, stac_object: "STACObject") -> "ItemExtension": if not isinstance(stac_object, Item): raise ValueError(f"This extension applies to Items, not {cls}") return cls.from_item(stac_object) @@ -174,7 +176,7 @@ def remove_extension(self, extension_id: str) -> None: def get_extension_class( self, extension_id: str, - stac_object_class: Type[STACObject]) -> Optional[Type[STACObjectExtension]]: + stac_object_class: Type["STACObject"]) -> Optional[Type[STACObjectExtension]]: """Gets the extension class for a given stac object class if one exists, otherwise returns None """ @@ -209,7 +211,7 @@ def get_extension_class( return ext_class - def extend_object(self, extension_id: str, stac_object: STACObject) -> STACObjectExtension: + def extend_object(self, extension_id: str, stac_object: "STACObject") -> STACObjectExtension: """Returns the extension object for the given STACObject and the given extension_id """ @@ -221,7 +223,7 @@ def extend_object(self, extension_id: str, stac_object: STACObject) -> STACObjec return ext_class._from_object(stac_object) - def get_extended_object_links(self, stac_object: STACObject) -> List[str]: + def get_extended_object_links(self, stac_object: "STACObject") -> List[str]: if stac_object.stac_extensions is None: return [] return [ @@ -231,7 +233,7 @@ def get_extended_object_links(self, stac_object: STACObject) -> List[str]: for link_rel in e_obj.extension_class._object_links() ] - def can_extend(self, extension_id: str, stac_object_class: Type[STACObject]) -> bool: + def can_extend(self, extension_id: str, stac_object_class: Type["STACObject"]) -> bool: """Returns True if the extension can extend the given object type. Args: @@ -254,7 +256,7 @@ def can_extend(self, extension_id: str, stac_object_class: Type[STACObject]) -> if issubclass(stac_object_class, e.stac_object_class) ]) - def enable_extension(self, extension_id: str, stac_object: STACObject) -> None: + def enable_extension(self, extension_id: str, stac_object: "STACObject") -> None: """Enables the extension for the given object. This will at least ensure the extension ID is in the object's "stac_extensions" diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 35cf94b00..c86861035 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -126,7 +126,7 @@ def size(self) -> Optional[int]: def size(self, v: Optional[int]) -> None: self.set_size(v) - def get_size(self, asset: Optional[Asset]=None) -> Optional[int]: + def get_size(self, asset: Optional[Asset] = None) -> Optional[int]: """Gets an Item or an Asset file size. If an Asset is supplied and the Item property exists on the Asset, @@ -140,7 +140,7 @@ def get_size(self, asset: Optional[Asset]=None) -> Optional[int]: else: return asset.properties.get('file:size') - def set_size(self, size: Optional[int], asset: Optional[Asset]=None) -> None: + def set_size(self, size: Optional[int], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset size. If an Asset is supplied, sets the property on the Asset. @@ -154,10 +154,10 @@ def nodata(self) -> Optional[List[Any]]: return self.get_nodata() @nodata.setter - def nodata(self, v: Optional[List[Any]])-> None: + def nodata(self, v: Optional[List[Any]]) -> None: self.set_nodata(v) - def get_nodata(self, asset: Optional[Asset]=None) -> Optional[List[Any]]: + def get_nodata(self, asset: Optional[Asset] = None) -> Optional[List[Any]]: """Gets an Item or an Asset nodata values. If an Asset is supplied and the Item property exists on the Asset, @@ -171,7 +171,7 @@ def get_nodata(self, asset: Optional[Asset]=None) -> Optional[List[Any]]: else: return asset.properties.get('file:nodata') - def set_nodata(self, nodata: Optional[List[Any]], asset: Optional[Asset]=None) -> None: + def set_nodata(self, nodata: Optional[List[Any]], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset nodata values. If an Asset is supplied, sets the property on the Asset. @@ -192,7 +192,7 @@ def checksum(self) -> Optional[str]: def checksum(self, v: Optional[str]) -> None: self.set_checksum(v) - def get_checksum(self, asset: Optional[Asset]=None) -> Optional[str]: + def get_checksum(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset checksum. If an Asset is supplied and the Item property exists on the Asset, @@ -203,7 +203,7 @@ def get_checksum(self, asset: Optional[Asset]=None) -> Optional[str]: else: return asset.properties.get('file:checksum') - def set_checksum(self, checksum: Optional[str], asset: Optional[Asset]=None) -> None: + def set_checksum(self, checksum: Optional[str], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset checksum. If an Asset is supplied, sets the property on the Asset. diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 6abfa3e2a..92d33b123 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -257,7 +257,7 @@ def __init__(self, properties: Dict[str, Any]): self.properties = properties def apply(self, - property_key: str, + property_key: Optional[str], counts: Optional[List[LabelCount]] = None, statistics: Optional[List[LabelStatistics]] = None): """Sets the properties for this LabelOverview. @@ -266,7 +266,9 @@ def apply(self, at least one is required. Args: - property_key (str): The property key within the asset corresponding to class labels. + property_key (str): The property key within the asset corresponding to class labels + that these counts or statistics are referencing. If the label data is raster data, + this should be None. counts: Optional list of LabelCounts containing counts for categorical data. statistics: Optional list of statistics containing statistics for @@ -278,7 +280,7 @@ def apply(self, @classmethod def create(cls, - property_key: str, + property_key: Optional[str], counts: Optional[List[LabelCount]] = None, statistics: Optional[List[LabelStatistics]] = None) -> "LabelOverview": """Creates a new LabelOverview. @@ -298,19 +300,16 @@ def create(cls, return x @property - def property_key(self) -> str: + def property_key(self) -> Optional[str]: """Get or sets the property key within the asset corresponding to class labels. Returns: str """ - result = self.properties.get('property_key') - if result is None: - raise STACError(f"Label overview has no property_key: {self.properties}") - return result + return self.properties.get('property_key') @property_key.setter - def property_key(self, v: str) -> None: + def property_key(self, v: Optional[str]) -> None: self.properties['property_key'] = v @property @@ -651,7 +650,11 @@ def get_sources(self) -> Iterable[Item]: """ return map(lambda x: cast(Item, x), self.item.get_stac_objects('source')) - def add_labels(self, href: str, title: Optional[str]=None, media_type: Optional[str]=None, properties: Optional[Dict[str, Any]]=None): + def add_labels(self, + href: str, + title: Optional[str] = None, + media_type: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None): """Adds a label asset to this LabelItem. Args: @@ -667,7 +670,10 @@ def add_labels(self, href: str, title: Optional[str]=None, media_type: Optional[ self.item.add_asset( "labels", Asset(href=href, title=title, media_type=media_type, properties=properties)) - def add_geojson_labels(self, href: str, title: Optional[str]=None, properties:Optional[Dict[str, Any]]=None): + def add_geojson_labels(self, + href: str, + title: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None): """Adds a GeoJSON label asset to this LabelItem. Args: diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 9984796b3..39e28af83 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -314,7 +314,9 @@ def get_centroid(self, asset: Optional[Asset] = None) -> Optional[Dict[str, floa else: return asset.properties.get('proj:centroid') - def set_centroid(self, centroid: Optional[Dict[str, float]], asset: Optional[Asset] = None) -> None: + def set_centroid(self, + centroid: Optional[Dict[str, float]], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset centroid. If an Asset is supplied, sets the property on the Asset. @@ -397,7 +399,9 @@ def get_transform(self, asset: Optional[Asset] = None) -> Optional[List[float]]: else: return asset.properties.get('proj:transform') - def set_transform(self, transform: Optional[List[float]], asset: Optional[Asset] = None) -> None: + def set_transform(self, + transform: Optional[List[float]], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset transform. If an Asset is supplied, sets the property on the Asset. diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 56c40a071..97f81b297 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -154,9 +154,8 @@ def instrument_mode(self) -> str: """ result = self.item.properties.get(INSTRUMENT_MODE) if result is None: - raise STACError( - f"Item with sar extension does not have property {INSTRUMENT_MODE}, id {self.item.id}" - ) + raise STACError(f"Item with sar extension does not have property {INSTRUMENT_MODE}, " + f"id {self.item.id}") return result @instrument_mode.setter @@ -172,9 +171,8 @@ def frequency_band(self) -> FrequencyBand: """ result = self.item.properties.get(FREQUENCY_BAND) if result is None: - raise STACError( - f"Item with sar extension does not have property {FREQUENCY_BAND}, id {self.item.id}" - ) + raise STACError(f"Item with sar extension does not have property {FREQUENCY_BAND}, " + f"id {self.item.id}") return FrequencyBand(result) @frequency_band.setter diff --git a/pystac/extensions/single_file_stac.py b/pystac/extensions/single_file_stac.py index f940e67b4..ecc211a2a 100644 --- a/pystac/extensions/single_file_stac.py +++ b/pystac/extensions/single_file_stac.py @@ -2,7 +2,6 @@ from typing import List, Optional, cast from pystac.catalog import Catalog -import pystac from pystac import (STACError, Extensions) from pystac.collection import Collection from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) diff --git a/pystac/item.py b/pystac/item.py index affa49ce2..d0c881c4b 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -5,7 +5,7 @@ import dateutil.parser -import pystac +import pystac as ps from pystac import (STACError, STACObjectType) from pystac.link import Link from pystac.stac_object import STACObject @@ -747,8 +747,8 @@ class Item(STACObject): def __init__(self, id: str, - geometry: Dict[str, Any], - bbox: List[float], + geometry: Optional[Dict[str, Any]], + bbox: Optional[List[float]], datetime: Optional[Datetime], properties: Dict[str, Any], stac_extensions: Optional[List[str]] = None, @@ -913,7 +913,7 @@ def make_asset_hrefs_absolute(self) -> "Item": return self - def set_collection(self, collection: Collection) -> "Item": + def set_collection(self, collection: Optional[Collection]) -> "Item": """Set the collection of this item. This method will replace any existing Collection link and attribute for @@ -961,7 +961,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d: Dict[str, Any] = { 'type': 'Feature', - 'stac_version': pystac.get_stac_version(), + 'stac_version': ps.get_stac_version(), 'id': self.id, 'properties': self.properties, 'geometry': self.geometry, @@ -1000,18 +1000,25 @@ def clone(self) -> "Item": return clone def _object_links(self) -> List[str]: - return ['collection'] + (pystac.STAC_EXTENSIONS.get_extended_object_links(self)) + return ['collection'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) @classmethod def from_dict(cls, d: Dict[str, Any], href: Optional[str] = None, - root: Optional[Catalog] = None) -> "Item": + root: Optional[Catalog] = None, + migrate: bool = False) -> "Item": + if migrate: + result = ps.read_dict(d, href=href, root=root) + if not isinstance(result, Item): + raise ps.STACError(f"{result} is not a Catalog") + return result + d = deepcopy(d) id = d.pop('id') geometry = d.pop('geometry') properties = d.pop('properties') - bbox = d.pop('bbox') # TODO: Ensure this shouldn't pop with a default + bbox = d.pop('bbox', None) stac_extensions = d.get('stac_extensions') collection_id = d.pop('collection', None) @@ -1056,3 +1063,15 @@ def common_metadata(self) -> CommonMetadata: CommonMetada: contains all common metadata fields in the items properties """ return CommonMetadata(self.properties) + + def full_copy(self, + root: Optional["Catalog"] = None, + parent: Optional["Catalog"] = None) -> "Item": + return cast(Item, super().full_copy(root, parent)) + + @classmethod + def from_file(cls, href: str) -> "Item": + result = super().from_file(href) + if not isinstance(result, Item): + raise ps.STACTypeError(f"{result} is not a {Item}.") + return result diff --git a/pystac/layout.py b/pystac/layout.py index d97aff543..01cc9ef99 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -1,14 +1,16 @@ from abc import (abstractmethod, ABC) from collections import OrderedDict import os -from pystac.collection import Collection from string import Formatter -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union -import pystac -from pystac.catalog import Catalog -from pystac.item import Item -from pystac.stac_object import STACObject +import pystac as ps + +if TYPE_CHECKING: + from pystac.stac_object import STACObject + from pystac.catalog import Catalog + from pystac.collection import Collection + from pystac.item import Item class TemplateError(Exception): @@ -85,9 +87,9 @@ def __init__(self, template: str, defaults: Dict[str, str] = None) -> None: template_vars.append(v) self.template_vars = template_vars - def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any: + def _get_template_value(self, stac_object: "STACObject", template_var: str) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: - if isinstance(stac_object, Item): + if isinstance(stac_object, ps.Item): # Datetime dt = stac_object.datetime if dt is None: @@ -119,25 +121,29 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any # Allow dot-notation properties for arbitrary object values. props = template_var.split('.') - prop_location = None + prop_source: Optional[Union[STACObject, Dict[str, Any]]] = None error = TemplateError('Cannot find property {} on {} for template {}'.format( template_var, stac_object, self.template)) try: + if hasattr(stac_object, props[0]): - prop_location = stac_object - elif hasattr(stac_object, "properties"): + prop_source = stac_object + + if prop_source is None and hasattr(stac_object, "properties"): obj_props: Optional[Dict[str, Any]] = stac_object.properties # type:ignore if obj_props is not None and props[0] in obj_props: - prop_location = obj_props - elif hasattr(stac_object, "extra_fields"): + prop_source = obj_props + + if prop_source is None and hasattr(stac_object, "extra_fields"): extra_fields: Optional[Dict[str, Any]] = stac_object.extra_fields # type:ignore if extra_fields is not None and props[0] in extra_fields: - prop_location = extra_fields - else: + prop_source = extra_fields + + if prop_source is None: raise error - v: Any = prop_location + v: Any = prop_source for prop in template_var.split('.'): if type(v) is dict: if prop not in v: @@ -154,7 +160,7 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any return v - def get_template_values(self, stac_object: STACObject) -> Dict[str, Any]: + def get_template_values(self, stac_object: "STACObject") -> Dict[str, Any]: """Gets a dictionary of template variables to values derived from the given stac_object. If the template vars cannot be found in the stac object, and defaults was supplied to this template, a default @@ -177,7 +183,7 @@ def get_template_values(self, stac_object: STACObject) -> Dict[str, Any]: return OrderedDict([(k, self._get_template_value(stac_object, k)) for k in self.template_vars]) - def substitute(self, stac_object: STACObject) -> str: + def substitute(self, stac_object: "STACObject") -> str: """Substitutes the values derived from :meth:`~pystac.layout.LayoutTemplate.get_template_values` into the template string for this template. @@ -206,26 +212,26 @@ def substitute(self, stac_object: STACObject) -> str: class HrefLayoutStrategy(ABC): """Base class for HREF Layout strategies.""" - def get_href(self, stac_object: STACObject, parent_dir: str, is_root: bool = False) -> str: - if isinstance(stac_object, Catalog): - return self.get_catalog_href(stac_object, parent_dir, is_root) - elif isinstance(stac_object, Collection): - return self.get_collection_href(stac_object, parent_dir, is_root) - elif isinstance(stac_object, Item): + def get_href(self, stac_object: "STACObject", parent_dir: str, is_root: bool = False) -> str: + if isinstance(stac_object, ps.Item): return self.get_item_href(stac_object, parent_dir) + elif isinstance(stac_object, ps.Collection): + return self.get_collection_href(stac_object, parent_dir, is_root) + elif isinstance(stac_object, ps.Catalog): + return self.get_catalog_href(stac_object, parent_dir, is_root) else: - raise pystac.STACError('Unknown STAC object type {}'.format(stac_object)) + raise ps.STACError('Unknown STAC object type {}'.format(stac_object)) @abstractmethod - def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_item_href(self, item: Item, parent_dir: str) -> str: + def get_item_href(self, item: "Item", parent_dir: str) -> str: pass @@ -250,9 +256,9 @@ class CustomLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ def __init__(self, - catalog_func: Optional[Callable[[Catalog, str, bool], str]] = None, - collection_func: Optional[Callable[[Collection, str, bool], str]] = None, - item_func: Optional[Callable[[Item, str], str]] = None, + catalog_func: Optional[Callable[["Catalog", str, bool], str]] = None, + collection_func: Optional[Callable[["Collection", str, bool], str]] = None, + item_func: Optional[Callable[["Item", str], str]] = None, fallback_strategy: Optional[HrefLayoutStrategy] = None): self.item_func = item_func self.collection_func = collection_func @@ -261,21 +267,21 @@ def __init__(self, fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: if self.catalog_func is not None: result = self.catalog_func(cat, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) - def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: if self.collection_func is not None: result = self.collection_func(col, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) - def get_item_href(self, item: Item, parent_dir: str) -> str: + def get_item_href(self, item: "Item", parent_dir: str) -> str: if self.item_func is not None: result = self.item_func(item, parent_dir) if result is not None: @@ -322,7 +328,7 @@ def __init__(self, fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: if is_root or self.catalog_template is None: return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) else: @@ -332,7 +338,7 @@ def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: return os.path.join(parent_dir, template_path) - def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: if is_root or self.collection_template is None: return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) else: @@ -342,7 +348,7 @@ def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) - return os.path.join(parent_dir, template_path) - def get_item_href(self, item: Item, parent_dir: str) -> str: + def get_item_href(self, item: "Item", parent_dir: str) -> str: if self.item_template is None: return self.fallback_strategy.get_item_href(item, parent_dir) else: @@ -367,7 +373,7 @@ class BestPracticesLayoutStrategy(HrefLayoutStrategy): All paths are appended to the parent directory. """ - def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: if is_root: cat_root = parent_dir else: @@ -375,7 +381,7 @@ def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: return os.path.join(cat_root, cat.DEFAULT_FILE_NAME) - def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: if is_root: col_root = parent_dir else: @@ -383,7 +389,7 @@ def get_collection_href(self, col: Collection, parent_dir: str, is_root: bool) - return os.path.join(col_root, col.DEFAULT_FILE_NAME) - def get_item_href(self, item: Item, parent_dir: str) -> str: + def get_item_href(self, item: "Item", parent_dir: str) -> str: item_root = os.path.join(parent_dir, '{}'.format(item.id)) return os.path.join(item_root, '{}.json'.format(item.id)) diff --git a/pystac/link.py b/pystac/link.py index b7a45515e..0490ecaa7 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -1,15 +1,16 @@ from copy import copy -from pystac.item import Item -from pystac.catalog import Catalog -from pystac.collection import Collection -from typing import Any, Dict, Optional, Union, cast - -import pystac -from pystac.stac_object import STACObject -from pystac import STACError +from typing import Any, Dict, Optional, TYPE_CHECKING, Union, cast + +import pystac as ps from pystac.stac_io import STAC_IO from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) +if TYPE_CHECKING: + from pystac.stac_object import STACObject + from pystac.item import Item + from pystac.catalog import Catalog + from pystac.collection import Collection + HIERARCHICAL_LINKS = ['root', 'child', 'parent', 'collection', 'item', 'items'] @@ -57,18 +58,18 @@ class Link: """ def __init__(self, rel: str, - target: Union[str, STACObject], + target: Union[str, "STACObject"], media_type: Optional[str] = None, title: Optional[str] = None, properties: Optional[Dict[str, Any]] = None) -> None: self.rel = rel - self.target: Union[str, STACObject] = target # An object or an href + self.target: Union[str, "STACObject"] = target # An object or an href self.media_type = media_type self.title = title self.properties = properties self.owner = None - def set_owner(self, owner: STACObject) -> "Link": + def set_owner(self, owner: "STACObject") -> "Link": """Sets the owner of this link. Args: @@ -77,6 +78,18 @@ def set_owner(self, owner: STACObject) -> "Link": self.owner = owner return self + @property + def href(self) -> str: + """Returns the HREF for this link. + + If the href is None, this will throw an exception. + Use get_href if there may not be an href. + """ + result = self.get_href() + if result is None: + raise ValueError(f'{self} does not have an HREF set.') + return result + def get_href(self) -> Optional[str]: """Gets the HREF for this link. @@ -88,14 +101,14 @@ def get_href(self) -> Optional[str]: """ # get the self href if self.is_resolved(): - href = cast(STACObject, self.target).get_self_href() + href = cast(ps.STACObject, self.target).get_self_href() else: href = cast(Optional[str], self.target) if href and is_absolute_href(href) and self.owner and self.owner.get_root(): root = self.owner.get_root() rel_links = HIERARCHICAL_LINKS + \ - pystac.STAC_EXTENSIONS.get_extended_object_links(self.owner) + ps.STAC_EXTENSIONS.get_extended_object_links(self.owner) # if a hierarchical link with an owner and root, and relative catalog if root.is_relative() and self.rel in rel_links: owner_href = self.owner.get_self_href() @@ -104,6 +117,18 @@ def get_href(self) -> Optional[str]: return href + @property + def absolute_href(self) -> str: + """Returns the absolute HREF for this link. + + If the href is None, this will throw an exception. + Use get_absolute_href if there may not be an href set. + """ + result = self.get_absolute_href() + if result is None: + raise ValueError(f'{self} does not have an HREF set.') + return result + def get_absolute_href(self) -> Optional[str]: """Gets the absolute href for this link, if possible. @@ -113,7 +138,7 @@ def get_absolute_href(self) -> Optional[str]: and has an unresolved target, this will return a relative HREF. """ if self.is_resolved(): - href = cast(STACObject, self.target).get_self_href() + href = cast(ps.STACObject, self.target).get_self_href() else: href = cast(Optional[str], self.target) @@ -125,7 +150,7 @@ def get_absolute_href(self) -> Optional[str]: def __repr__(self): return ''.format(self.rel, self.target) - def resolve_stac_object(self, root: Optional[Catalog]=None) -> "Link": + def resolve_stac_object(self, root: Optional["Catalog"] = None) -> "Link": """Resolves a STAC object from the HREF of this link, if the link is not already resolved. @@ -140,13 +165,13 @@ def resolve_stac_object(self, root: Optional[Catalog]=None) -> "Link": # If it's a relative link, base it off the parent. if not is_absolute_href(target_href): if self.owner is None: - raise STACError('Relative path {} encountered ' - 'without owner or start_href.'.format(target_href)) + raise ps.STACError('Relative path {} encountered ' + 'without owner or start_href.'.format(target_href)) start_href = self.owner.get_self_href() if start_href is None: - raise STACError('Relative path {} encountered ' - 'without owner "self" link set.'.format(target_href)) + raise ps.STACError('Relative path {} encountered ' + 'without owner "self" link set.'.format(target_href)) target_href = make_absolute_href(target_href, start_href) obj = None @@ -165,7 +190,7 @@ def resolve_stac_object(self, root: Optional[Catalog]=None) -> "Link": self.target = obj - if self.owner and self.rel in ['child', 'item'] and isinstance(self.owner, Catalog): + if self.owner and self.rel in ['child', 'item'] and isinstance(self.owner, ps.Catalog): self.target.set_parent(self.owner) return self @@ -236,17 +261,17 @@ def from_dict(d: Dict[str, Any]) -> "Link": return Link(rel=rel, target=href, media_type=media_type, title=title, properties=properties) @staticmethod - def root(c: Catalog) -> "Link": + def root(c: "Catalog") -> "Link": """Creates a link to a root Catalog or Collection.""" return Link('root', c, media_type='application/json') @staticmethod - def parent(c: Catalog) -> "Link": + def parent(c: "Catalog") -> "Link": """Creates a link to a parent Catalog or Collection.""" return Link('parent', c, media_type='application/json') @staticmethod - def collection(c: Collection) -> "Link": + def collection(c: "Collection") -> "Link": """Creates a link to an item's Collection.""" return Link('collection', c, media_type='application/json') @@ -256,11 +281,11 @@ def self_href(href: str) -> "Link": return Link('self', href, media_type='application/json') @staticmethod - def child(c: Catalog, title: Optional[str]=None) -> "Link": + def child(c: "Catalog", title: Optional[str] = None) -> "Link": """Creates a link to a child Catalog or Collection.""" return Link('child', c, title=title, media_type='application/json') @staticmethod - def item(item: Item, title: Optional[str]=None) -> "Link": + def item(item: "Item", title: Optional[str] = None) -> "Link": """Creates a link to an Item.""" return Link('item', item, title=title, media_type='application/json') diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index 9ada41486..96b33ceff 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -1,15 +1,22 @@ # flake8: noqa -from pystac.stac_object import STACObject -from typing import Any, Dict, Optional -from pystac import (Catalog, Collection, Item, STACObjectType) +from typing import Any, Dict, Optional, TYPE_CHECKING -from pystac.serialization.identify import (STACJSONDescription, STACVersionRange, STACVersionID, # type:ignore - identify_stac_object, identify_stac_object_type) +import pystac as ps +from pystac.serialization.identify import ( + STACVersionRange, # type:ignore + identify_stac_object, + identify_stac_object_type) from pystac.serialization.common_properties import merge_common_properties from pystac.serialization.migrate import migrate_to_latest +if TYPE_CHECKING: + from pystac.stac_object import STACObject + from pystac.catalog import Catalog -def stac_object_from_dict(d: Dict[str, Any], href: Optional[str]=None, root: Optional[Catalog]=None) -> STACObject: + +def stac_object_from_dict(d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog"] = None) -> "STACObject": """Determines how to deserialize a dictionary into a STAC object. Args: @@ -23,7 +30,7 @@ def stac_object_from_dict(d: Dict[str, Any], href: Optional[str]=None, root: Opt Note: This is used internally in STAC_IO to deserialize STAC Objects. It is in the top level __init__ in order to avoid circular dependencies. """ - if identify_stac_object_type(d) == STACObjectType.ITEM: + if identify_stac_object_type(d) == ps.STACObjectType.ITEM: collection_cache = None if root is not None: collection_cache = root._resolved_objects.as_collection_cache() @@ -35,13 +42,13 @@ def stac_object_from_dict(d: Dict[str, Any], href: Optional[str]=None, root: Opt d, info = migrate_to_latest(d, info) - if info.object_type == STACObjectType.CATALOG: - return Catalog.from_dict(d, href=href, root=root) + if info.object_type == ps.STACObjectType.CATALOG: + return ps.Catalog.from_dict(d, href=href, root=root, migrate=False) - if info.object_type == STACObjectType.COLLECTION: - return Collection.from_dict(d, href=href, root=root) + if info.object_type == ps.STACObjectType.COLLECTION: + return ps.Collection.from_dict(d, href=href, root=root, migrate=False) - if info.object_type == STACObjectType.ITEM: - return Item.from_dict(d, href=href, root=root) + if info.object_type == ps.STACObjectType.ITEM: + return ps.Item.from_dict(d, href=href, root=root, migrate=False) raise ValueError(f"Unknown STAC object type {info.object_type}") diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index 4c3677084..05a249762 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -1,10 +1,11 @@ -from pystac.cache import CollectionCache -from typing import Any, Dict, Optional, Union, cast -from pystac import Collection +from typing import Any, Dict, Iterable, Optional, Union, cast from pystac.utils import make_absolute_href from pystac.stac_io import STAC_IO from pystac.serialization.identify import STACVersionID +import pystac as ps +from pystac.cache import CollectionCache + def merge_common_properties(item_dict: Dict[str, Any], collection_cache: Optional[CollectionCache] = None, @@ -24,7 +25,7 @@ def merge_common_properties(item_dict: Dict[str, Any], """ properties_merged = False - collection: Optional[Union[Collection, Dict[str, Any]]] = None + collection: Optional[Union[ps.Collection, Dict[str, Any]]] = None collection_id: Optional[str] = None collection_href: Optional[str] = None @@ -40,7 +41,7 @@ def merge_common_properties(item_dict: Dict[str, Any], # we don't have to merge. if stac_version is not None and stac_version == '0.9.0': stac_extensions = item_dict.get('stac_extensions') - if type(stac_extensions) is list: + if isinstance(stac_extensions, list): if 'commons' not in stac_extensions: return False else: @@ -54,11 +55,11 @@ def merge_common_properties(item_dict: Dict[str, Any], # Next, try the collection link. if collection is None: - links = item_dict['links'] - # Account for 0.5 links, which were dicts - if isinstance(links, Dict[str, Dict[str, Any]]): - links = list(links.values()) + if isinstance(item_dict['links'], dict): + links = list(cast(Iterable[Dict[str, Any]], item_dict['links'].values())) + else: + links = cast(Iterable[Dict[str, Any]], item_dict['links']) collection_link = next((link for link in links if link['rel'] == 'collection'), None) if collection_link is not None: @@ -72,13 +73,12 @@ def merge_common_properties(item_dict: Dict[str, Any], if collection is None: collection = STAC_IO.read_json(collection_href) - # TODO: Remove properties from Collection, it would be in extra_fields if collection is not None: collection_id = None collection_props: Optional[Dict[str, Any]] = None - if isinstance(collection, Collection): + if isinstance(collection, ps.Collection): collection_id = collection.id - collection_props = collection.properties + collection_props = collection.extra_fields.get("properties") elif isinstance(collection, dict): collection_id = collection['id'] if 'properties' in collection: diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 641366725..8c4216830 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,7 +1,7 @@ from functools import total_ordering from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pystac import STACObjectType +import pystac from pystac.version import STACVersion from pystac.extensions import Extensions @@ -132,7 +132,8 @@ def __repr__(self) -> str: ','.join(self.custom_extensions)) -def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range: STACVersionRange) -> List[str]: +def _identify_stac_extensions(object_type: str, d: Dict[str, Any], + version_range: STACVersionRange) -> List[str]: """Identifies extensions for STAC Objects that don't list their extensions in a 'stac_extensions' property. @@ -143,7 +144,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range # assets (collection assets) - if object_type == STACObjectType.ITEMCOLLECTION: + if object_type == pystac.STACObjectType.ITEMCOLLECTION: if 'assets' in d: stac_extensions.add('assets') version_range.set_min(STACVersionID('0.8.0')) @@ -152,7 +153,12 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range if 'links' in d: found_checksum = False for link in d['links']: - link_props = cast(Dict[str, Any], link).keys() + # Account for old links as dicts + if isinstance(link, str): + link_props = cast(Dict[str, Any], d['links'][link]).keys() + else: + link_props = cast(Dict[str, Any], link).keys() + if any(prop.startswith('checksum:') for prop in link_props): found_checksum = True stac_extensions.add(Extensions.CHECKSUM) @@ -167,19 +173,19 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range version_range.set_min(STACVersionID('0.6.2')) # datacube - if object_type == STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.DATACUBE) version_range.set_min(STACVersionID('0.6.1')) # datetime-range (old extension) - if object_type == STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if 'dtr:start_datetime' in d['properties']: stac_extensions.add('datetime-range') version_range.set_min(STACVersionID('0.6.0')) # eo - if object_type == STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.EO) if 'eo:epsg' in d['properties']: @@ -194,13 +200,13 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range version_range.set_max(STACVersionID('0.5.2')) # pointcloud - if object_type == STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.POINTCLOUD) version_range.set_min(STACVersionID('0.6.2')) # sar - if object_type == STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.SAR) version_range.set_min(STACVersionID('0.6.2')) @@ -227,7 +233,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range version_range.set_max(STACVersionID('0.6.2')) # scientific - if object_type == STACObjectType.ITEM or object_type == STACObjectType.COLLECTION: + if object_type == pystac.STACObjectType.ITEM or object_type == pystac.STACObjectType.COLLECTION: if 'properties' in d: prop_keys = cast(Dict[str, Any], d['properties']).keys() if any(k.startswith('sci:') for k in prop_keys): @@ -235,7 +241,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range version_range.set_min(STACVersionID('0.6.0')) # Single File STAC - if object_type == STACObjectType.ITEMCOLLECTION: + if object_type == pystac.STACObjectType.ITEMCOLLECTION: if 'collections' in d: stac_extensions.add(Extensions.SINGLE_FILE_STAC) version_range.set_min(STACVersionID('0.8.0')) @@ -275,14 +281,14 @@ def identify_stac_object_type(json_dict: Dict[str, Any]): if 'type' in json_dict and 'assets' not in json_dict: if 'stac_version' in json_dict and cast(str, json_dict['stac_version']).startswith('0'): if json_dict['type'] == 'FeatureCollection': - object_type = STACObjectType.ITEMCOLLECTION + object_type = pystac.STACObjectType.ITEMCOLLECTION if 'extent' in json_dict: - object_type = STACObjectType.COLLECTION + object_type = pystac.STACObjectType.COLLECTION elif 'assets' in json_dict: - object_type = STACObjectType.ITEM + object_type = pystac.STACObjectType.ITEM else: - object_type = STACObjectType.CATALOG + object_type = pystac.STACObjectType.CATALOG return object_type @@ -305,9 +311,10 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: stac_extensions = json_dict.get('stac_extensions', None) if stac_version is None: - if object_type == STACObjectType.CATALOG or object_type == STACObjectType.COLLECTION: + if (object_type == pystac.STACObjectType.CATALOG + or object_type == pystac.STACObjectType.COLLECTION): version_range.set_max(STACVersionID('0.5.2')) - elif object_type == STACObjectType.ITEM: + elif object_type == pystac.STACObjectType.ITEM: version_range.set_max(STACVersionID('0.7.0')) else: # ItemCollection version_range.set_min(STACVersionID('0.8.0')) @@ -323,7 +330,7 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: # but ItemCollection (except after 0.9.0, when ItemCollection also got # the stac_extensions property). if version_range.is_earlier_than('0.8.0') or \ - (object_type == STACObjectType.ITEMCOLLECTION and not version_range.is_later_than( + (object_type == pystac.STACObjectType.ITEMCOLLECTION and not version_range.is_later_than( '0.8.1')): stac_extensions = _identify_stac_extensions(object_type, json_dict, version_range) else: diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index e08bea976..3442be738 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -2,7 +2,7 @@ from copy import deepcopy from typing import Any, Callable, Dict, List, Optional, Set, Tuple -from pystac import STACObjectType +import pystac as ps from pystac.version import STACVersion from pystac.extensions import Extensions from pystac.serialization.identify import (STACJSONDescription, STACVersionID, STACVersionRange) @@ -46,7 +46,7 @@ def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: if version < '1.0.0-beta.2': - if info.object_type == STACObjectType.COLLECTION: + if info.object_type == ps.STACObjectType.COLLECTION: if 'assets' in d: d['item_assets'] = d['assets'] del d['assets'] @@ -138,7 +138,7 @@ def _migrate_eo(d: Dict[str, Any], version: STACVersionID, d['properties']['eo:{}'.format(field)] del d['properties']['eo:{}'.format(field)] - if version < '1.0.0-beta.1' and info.object_type == STACObjectType.ITEM: + if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: # gsd moved from eo to common metadata if 'eo:gsd' in d['properties']: d['properties']['gsd'] = d['properties']['eo:gsd'] @@ -160,7 +160,7 @@ def _migrate_eo(d: Dict[str, Any], version: STACVersionID, def _migrate_label(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - if info.object_type == STACObjectType.ITEM and version < '1.0.0': + if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': props = d['properties'] # Migrate 0.8.0-rc1 non-pluralized forms # As it's a common mistake, convert for any pre-1.0.0 version. @@ -213,37 +213,43 @@ def _migrate_single_file_stac(d: Dict[str, Any], version: STACVersionID, pass -_object_migrations: Dict[str, - Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]] = { - STACObjectType.CATALOG: _migrate_catalog, - STACObjectType.COLLECTION: _migrate_collection, - STACObjectType.ITEM: _migrate_item, - STACObjectType.ITEMCOLLECTION: _migrate_itemcollection - } - -_extension_migrations: Dict[str, - Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], - Optional[Set[str]]]] = { - Extensions.CHECKSUM: _migrate_checksum, - Extensions.DATACUBE: _migrate_datacube, - Extensions.EO: _migrate_eo, - Extensions.ITEM_ASSETS: _migrate_item_assets, - Extensions.LABEL: _migrate_label, - Extensions.POINTCLOUD: _migrate_pointcloud, - Extensions.SAR: _migrate_sar, - Extensions.SCIENTIFIC: _migrate_scientific, - Extensions.SINGLE_FILE_STAC: _migrate_single_file_stac - } - -_removed_extension_migrations: Dict[str, Callable[ - [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]] = { +def _get_object_migrations( +) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]]: + return { + ps.STACObjectType.CATALOG: _migrate_catalog, + ps.STACObjectType.COLLECTION: _migrate_collection, + ps.STACObjectType.ITEM: _migrate_item, + ps.STACObjectType.ITEMCOLLECTION: _migrate_itemcollection + } + + +def _get_extension_migrations( +) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: + return { + Extensions.CHECKSUM: _migrate_checksum, + Extensions.DATACUBE: _migrate_datacube, + Extensions.EO: _migrate_eo, + Extensions.ITEM_ASSETS: _migrate_item_assets, + Extensions.LABEL: _migrate_label, + Extensions.POINTCLOUD: _migrate_pointcloud, + Extensions.SAR: _migrate_sar, + Extensions.SCIENTIFIC: _migrate_scientific, + Extensions.SINGLE_FILE_STAC: _migrate_single_file_stac + } + + +def _get_removed_extension_migrations( +) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: + return { # Removed in 0.9.0 'dtr': _migrate_datetime_range, 'datetime-range': _migrate_datetime_range, 'commons': lambda a, b, c: None # No changes needed, just remove the extension_id } -_extension_renames: Dict[str, str] = {'asset': 'item-assets'} + +def _get_extension_renames() -> Dict[str, str]: + return {'asset': 'item-assets'} def migrate_to_latest(json_dict: Dict[str, Any], @@ -263,23 +269,28 @@ def migrate_to_latest(json_dict: Dict[str, Any], result = deepcopy(json_dict) version = info.version_range.latest_valid_version() + object_migrations = _get_object_migrations() + extension_migrations = _get_extension_migrations() + extension_renames = _get_extension_renames() + removed_extension_migrations = _get_removed_extension_migrations() + if version != STACVersion.DEFAULT_STAC_VERSION: - _object_migrations[info.object_type](result, version, info) + object_migrations[info.object_type](result, version, info) extensions_to_add = set([]) for ext in info.common_extensions: - if ext in _extension_renames: + if ext in extension_renames: result['stac_extensions'].remove(ext) - ext = _extension_renames[ext] + ext = extension_renames[ext] extensions_to_add.add(ext) - if ext in _extension_migrations: - added_extensions = _extension_migrations[ext](result, version, info) + if ext in extension_migrations: + added_extensions = extension_migrations[ext](result, version, info) if added_extensions: extensions_to_add |= added_extensions - if ext in _removed_extension_migrations: - _removed_extension_migrations[ext](result, version, info) + if ext in removed_extension_migrations: + removed_extension_migrations[ext](result, version, info) result['stac_extensions'].remove(ext) for ext in extensions_to_add: @@ -287,8 +298,8 @@ def migrate_to_latest(json_dict: Dict[str, Any], migrated_extensions = set(info.common_extensions) migrated_extensions = migrated_extensions | set(extensions_to_add) - migrated_extensions = migrated_extensions - set(_removed_extension_migrations.keys()) - migrated_extensions = migrated_extensions - set(_extension_renames.keys()) + migrated_extensions = migrated_extensions - set(removed_extension_migrations.keys()) + migrated_extensions = migrated_extensions - set(extension_renames.keys()) common_extensions = list(migrated_extensions) else: common_extensions = info.common_extensions diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 49956df6a..c213f0a0d 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -1,13 +1,15 @@ import os import json -from pystac.stac_object import STACObject -from pystac.catalog import Catalog -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, Optional, TYPE_CHECKING from urllib.parse import urlparse from urllib.request import urlopen from urllib.error import HTTPError +if TYPE_CHECKING: + from pystac.stac_object import STACObject + from pystac.catalog import Catalog + class STAC_IO: """Methods used to read and save STAC json. @@ -54,7 +56,8 @@ def default_write_text_method(uri: str, txt: str) -> None: """ # Replaced in __init__ to account for extension objects. - stac_object_from_dict: Optional[Callable[[Dict[str, Any], Optional[str], Optional[Catalog]], STACObject]] = None + stac_object_from_dict: Optional[Callable[[Dict[str, Any], Optional[str], Optional["Catalog"]], + "STACObject"]] = None # This is set in __init__.py _STAC_OBJECT_CLASSES = None @@ -113,7 +116,7 @@ def read_json(cls, uri: str) -> Dict[str, Any]: return json.loads(STAC_IO.read_text(uri)) @classmethod - def read_stac_object(cls, uri: str, root: Optional[Catalog]=None) -> STACObject: + def read_stac_object(cls, uri: str, root: Optional["Catalog"] = None) -> "STACObject": """Read a STACObject from a JSON file at the given URI. Args: diff --git a/pystac/stac_object.py b/pystac/stac_object.py index cc578e24d..ed0f8d9fb 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,16 +1,17 @@ from abc import (ABC, abstractmethod) from enum import Enum -from pystac.catalog import Catalog -from typing import Any, Dict, Generator, List, Optional, cast +from typing import Any, Dict, Generator, List, Optional, cast, TYPE_CHECKING -import pystac -import pystac.validation +import pystac as ps from pystac import STACError from pystac.link import Link from pystac.stac_io import STAC_IO from pystac.utils import (is_absolute_href, make_absolute_href) from pystac.extensions import ExtensionError -from pystac.extensions.base import STACObjectExtension + +if TYPE_CHECKING: + from pystac.catalog import Catalog as CatalogType + from pystac.extensions.base import STACObjectExtension as STACObjectExtensionType class STACObjectType(str, Enum): @@ -110,6 +111,20 @@ def get_root_link(self): """ return self.get_single_link('root') + @property + def self_href(self) -> str: + """Gets the absolute HREF that is represented by the ``rel == 'self'`` + :class:`~pystac.Link`. + + Raises: + ValueError: If the self_href is not set, this method will throw a ValueError. + Use get_self_href if there may not be an href set. + """ + result = self.get_self_href() + if result is None: + raise ValueError(f"{self} does not have a self_href set.") + return result + def get_self_href(self) -> Optional[str]: """Gets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -143,14 +158,14 @@ def set_self_href(self, href: str) -> "LinkMixin": """ root_link = self.get_root_link() if root_link is not None and root_link.is_resolved(): - cast(Catalog, root_link.target)._resolved_objects.remove(cast(STACObject, self)) + cast(ps.Catalog, root_link.target)._resolved_objects.remove(cast(STACObject, self)) self.remove_links('self') if href is not None: self.add_link(Link.self_href(href)) if root_link is not None and root_link.is_resolved(): - cast(Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) + cast(ps.Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) return self @@ -173,15 +188,20 @@ def __init__(self, stac_extensions: List[str]): self.links = [] self.stac_extensions = stac_extensions - def validate(self): + def validate(self) -> List[Any]: """Validate this STACObject. + Returns a list of validation results, which depends on the validation + implementation. For JSON Schema validation, this will be a list + of schema URIs that were used during validation. + Raises: STACValidationError """ - return pystac.validation.validate(self) + import pystac.validation + return pystac.validation.validate(self) # type:ignore - def get_root(self) -> Optional[Catalog]: + def get_root(self) -> Optional["CatalogType"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the root for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'root'``. @@ -195,12 +215,12 @@ def get_root(self) -> Optional[Catalog]: if not root_link.is_resolved(): root_link.resolve_stac_object() # Use set_root, so Catalogs can merge ResolvedObjectCache instances. - self.set_root(cast(Catalog, root_link.target)) - return cast(Catalog, root_link.target) + self.set_root(cast(ps.Catalog, root_link.target)) + return cast("CatalogType", root_link.target) else: return None - def set_root(self, root: Optional[Catalog]) -> "STACObject": + def set_root(self, root: Optional["CatalogType"]) -> "STACObject": """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -215,7 +235,7 @@ def set_root(self, root: Optional[Catalog]) -> "STACObject": if root_link_index is not None: root_link = self.links[root_link_index] if root_link.is_resolved(): - cast(Catalog, root_link.target)._resolved_objects.remove(self) + cast(ps.Catalog, root_link.target)._resolved_objects.remove(self) if root is None: self.remove_links('root') @@ -230,7 +250,7 @@ def set_root(self, root: Optional[Catalog]) -> "STACObject": return self - def get_parent(self) -> Optional["STACObject"]: + def get_parent(self) -> Optional["CatalogType"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the parent for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'parent'``. @@ -242,11 +262,11 @@ def get_parent(self) -> Optional["STACObject"]: """ parent_link = self.get_single_link('parent') if parent_link: - return cast(Catalog, parent_link.resolve_stac_object().target) + return cast(ps.Catalog, parent_link.resolve_stac_object().target) else: return None - def set_parent(self, parent: Optional[Catalog]) -> "STACObject": + def set_parent(self, parent: Optional["CatalogType"]) -> "STACObject": """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -306,8 +326,8 @@ def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = STAC_IO.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) def full_copy(self, - root: Optional[Catalog] = None, - parent: Optional[Catalog] = None) -> "STACObject": + root: Optional["CatalogType"] = None, + parent: Optional["CatalogType"] = None) -> "STACObject": """Create a full copy of this STAC object and any stac objects linked to by this object. @@ -323,10 +343,10 @@ def full_copy(self, """ clone = self.clone() - if root is None and isinstance(clone, Catalog): + if root is None and isinstance(clone, ps.Catalog): root = clone - clone.set_root(cast(Catalog, root)) + clone.set_root(cast(ps.Catalog, root)) if parent: clone.set_parent(parent) @@ -340,14 +360,14 @@ def full_copy(self, assert target is not None else: target_parent = None - if link.rel in ['child', 'item'] and isinstance(clone, Catalog): + if link.rel in ['child', 'item'] and isinstance(clone, ps.Catalog): target_parent = clone copied_target = target.full_copy(root=root, parent=target_parent) root._resolved_objects.cache(copied_target) target = copied_target if link.rel in ['child', 'item']: target.set_root(root) - if isinstance(clone, Catalog): + if isinstance(clone, ps.Catalog): target.set_parent(clone) link.target = target @@ -433,10 +453,7 @@ def from_file(cls, href: str) -> "STACObject": href = make_absolute_href(href) d = STAC_IO.read_json(href) - if cls == STACObject: - o = STAC_IO.stac_object_from_dict(d, href, None) - else: - o = cls.from_dict(d, href, None) + o = STAC_IO.stac_object_from_dict(d, href, None) # Set the self HREF, if it's not already set to something else. if o.get_self_href() is None: @@ -447,7 +464,7 @@ def from_file(cls, href: str) -> "STACObject": if root_link is not None: if not root_link.is_resolved(): if root_link.get_absolute_href() == href: - o.set_root(cast(Catalog, o)) + o.set_root(cast(ps.Catalog, o)) return o @classmethod @@ -455,7 +472,8 @@ def from_file(cls, href: str) -> "STACObject": def from_dict(cls, d: Dict[str, Any], href: Optional[str] = None, - root: Optional[Catalog] = None) -> "STACObject": + root: Optional["CatalogType"] = None, + migrate: bool = False) -> "STACObject": """Parses this STACObject from the passed in dictionary. Args: @@ -465,6 +483,8 @@ def from_dict(cls, root (Catalog or Collection): Optional root of the catalog for this object. If provided, the root's resolved object cache can be used to search for previously resolved instances of the STAC object. + migrate: Use True if this dict represents JSON from an older STAC object, + so that migrations are run against it. Returns: STACObject: The STACObject parsed from this dict. @@ -484,8 +504,7 @@ class ExtensionIndex: def __init__(self, stac_object: STACObject) -> None: self.stac_object = stac_object - def __getitem__( - self, extension_id: str) -> STACObjectExtension: + def __getitem__(self, extension_id: str) -> "STACObjectExtensionType": """Gets the extension object for the given extension. Returns: @@ -494,7 +513,7 @@ def __getitem__( by the extension_id. """ # Check to make sure this is a registered extension. - if not pystac.STAC_EXTENSIONS.is_registered_extension(extension_id): + if not ps.STAC_EXTENSIONS.is_registered_extension(extension_id): raise ExtensionError("'{}' is not an extension " "registered with PySTAC".format(extension_id)) @@ -503,10 +522,9 @@ def __getitem__( "Use the 'ext.enable' method to enable this extension " "first.".format(self.stac_object, extension_id)) - return pystac.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) + return ps.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) - def __getattr__( - self, extension_id: str) -> STACObjectExtension: + def __getattr__(self, extension_id: str) -> "STACObjectExtensionType": """Gets an extension based on a dynamic attribute. This takes the attribute name and passes it to __getitem__. @@ -530,7 +548,7 @@ def enable(self, extension_id: str) -> None: the object should implement """ - pystac.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) + ps.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) def implements(self, extension_id: str) -> bool: """Returns true if the associated object implements the given extension. diff --git a/pystac/utils.py b/pystac/utils.py index 4dc627972..f0b30f709 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -40,7 +40,7 @@ def _join(is_path: bool, *args: str) -> str: return posixpath.join(*args) -def make_relative_href(source_href: str, start_href: str, start_is_dir: bool=False) -> str: +def make_relative_href(source_href: str, start_href: str, start_is_dir: bool = False) -> str: """Makes a given HREF relative to the given starting HREF. Args: @@ -94,9 +94,6 @@ def make_absolute_href(source_href: str, then it will be returned unchanged. If the source_href it None, it will return None. """ - if source_href is None: - return None # TODO: Remove the None case - if start_href is None: start_href = os.getcwd() start_is_dir = True diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 4d4e80215..3f504ad2f 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -1,10 +1,12 @@ # flake8: noqa -from typing import Dict, List, Any, Optional, cast -from pystac.stac_object import STACObject +from typing import Dict, List, Any, Optional, cast, TYPE_CHECKING import pystac from pystac.serialization.identify import identify_stac_object from pystac.utils import make_absolute_href +if TYPE_CHECKING: + from pystac.stac_object import STACObject + class STACValidationError(Exception): """Represents a validation error. Thrown by validation calls if the STAC JSON @@ -15,7 +17,7 @@ class STACValidationError(Exception): validation implementation. For the default JsonSchemaValidator this will a the ``jsonschema.ValidationError``. """ - def __init__(self, message: str, source: Optional[Any]=None): + def __init__(self, message: str, source: Optional[Any] = None): super().__init__(message) self.source = source @@ -24,7 +26,7 @@ def __init__(self, message: str, source: Optional[Any]=None): from pystac.validation.stac_validator import (STACValidator, JsonSchemaSTACValidator) -def validate(stac_object: STACObject) -> List[Any]: +def validate(stac_object: "STACObject") -> List[Any]: """Validates a :class:`~pystac.STACObject`. Args: @@ -39,13 +41,17 @@ def validate(stac_object: STACObject) -> List[Any]: STACValidationError """ return validate_dict(stac_dict=stac_object.to_dict(), - stac_object_type=stac_object.STAC_OBJECT_TYPE, - stac_version=pystac.get_stac_version(), - extensions=stac_object.stac_extensions, - href=stac_object.get_self_href()) + stac_object_type=stac_object.STAC_OBJECT_TYPE, + stac_version=pystac.get_stac_version(), + extensions=stac_object.stac_extensions, + href=stac_object.get_self_href()) -def validate_dict(stac_dict: Dict[str, Any], stac_object_type: Optional[str]=None, stac_version: Optional[str]=None, extensions: Optional[List[str]]=None, href: Optional[str]=None) -> List[Any]: +def validate_dict(stac_dict: Dict[str, Any], + stac_object_type: Optional[str] = None, + stac_version: Optional[str] = None, + extensions: Optional[List[str]] = None, + href: Optional[str] = None) -> List[Any]: """Validate a stac object serialized as JSON into a dict. This method delegates to the call to :meth:`pystac.validation.STACValidator.validate` @@ -116,7 +122,7 @@ def validate_all(stac_dict: Dict[str, Any], href: str) -> None: if info.object_type != pystac.STACObjectType.ITEM: if 'links' in stac_dict: # Account for 0.6 links - if isinstance(stac_dict['links'], Dict[str, Dict[str, Any]]): + if isinstance(stac_dict['links'], dict): links: List[Dict[str, Any]] = list(stac_dict['links'].values()) else: links: List[Dict[str, Any]] = cast(List[Dict[str, Any]], stac_dict.get('links')) diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 13a247c27..c9f9a7d1f 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -193,7 +193,8 @@ def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str): return self._append_base_uri_if_needed(uri, stac_version) - def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, stac_version: str): + def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, + stac_version: str): uri = None is_latest = stac_version == pystac.get_stac_version() diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index dfdb7e43c..2106040fd 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -201,7 +201,7 @@ def validate_extension(self, stac_object_type: STACObjectType, stac_version: str, extension_id: str, - href: Optional[str]=None): + href: Optional[str] = None): """Validate an extension stac object. Return value can be None or specific to the implementation. diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index 6c26c82e5..21be594f0 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -1,8 +1,138 @@ -"1.0.0-RC1/catalog.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-RC1/collection-only/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-RC1/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-RC1/collectionless-item.json","COLLECTION","1.0.0-beta.2","eo|view","" -"1.0.0-RC1/core-item.json","ITEM","1.0.0-beta.2","","" -"1.0.0-RC1/extended-item.json","ITEM","1.0.0-beta.2","eo|projection|scientific|view","" -"1.0.0-RC1/extensions-collection/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-RC1/extensions-collection/proj-example/proj-example.json","COLLECTION","1.0.0-beta.2","eo|projection","" \ No newline at end of file +"0.4.1/extensions/examples/landsat8-merged.json","ITEM","0.4.1","eo","" +"0.5.2/extensions/examples/landsat8-merged.json","ITEM","0.5.2","eo","" +"0.7.0/extensions/sar/examples/sentinel1.json","ITEM","0.7.0","sar|datetime-range|checksum","" +"0.8.1/catalog-spec/examples/catalog.json","CATALOG","0.8.1","","" +"0.8.1/catalog-spec/examples/summaries-s2.json","CATALOG","0.8.1","","" +"0.8.1/collection-spec/examples/landsat-collection.json","COLLECTION","0.8.1","","" +"0.8.1/collection-spec/examples/landsat-item.json","ITEM","0.8.1","eo","" +"0.8.1/collection-spec/examples/sentinel2.json","COLLECTION","0.8.1","","" +"0.8.1/extensions/asset/examples/example-landsat8.json","COLLECTION","0.8.1","asset","" +"0.8.1/extensions/checksum/examples/example-sentinel1.json","ITEM","0.8.1","checksum","" +"0.8.1/extensions/datacube/examples/example.json","ITEM","0.8.1","datacube","" +"0.8.1/extensions/datetime-range/examples/example-video.json","ITEM","0.8.1","datetime-range","" +"0.8.1/extensions/eo/examples/example-landsat8.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" +"0.8.1/extensions/label/examples/multidataset/catalog.json","CATALOG","0.8.1","","" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.8.1","","" +"0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.8.1","","" +"0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.8.1","","" +"0.8.1/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.8.1","label","" +"0.8.1/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.8.1","","" +"0.8.1/extensions/pointcloud/examples/example-autzen.json","ITEM","0.8.1","pointcloud","" +"0.8.1/extensions/sar/examples/envisat.json","ITEM","0.8.1","sar|datetime-range","" +"0.8.1/extensions/sar/examples/sentinel1.json","ITEM","0.8.1","checksum|sar|datetime-range","" +"0.8.1/extensions/scientific/examples/collection.json","COLLECTION","0.8.1","scientific","" +"0.8.1/extensions/scientific/examples/item.json","ITEM","0.8.1","datetime-range|checksum|scientific","" +"0.8.1/item-spec/examples/digitalglobe-sample.json","ITEM","0.8.1","eo","https://example.digitalglobe.com/stac/1.0/schema.json" +"0.8.1/item-spec/examples/landsat8-sample.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" +"0.8.1/item-spec/examples/planet-sample.json","ITEM","0.8.1","eo","https://example.planet.com/stac/1.0/schema.json" +"0.8.1/item-spec/examples/sample-full.json","ITEM","0.8.1","eo","https://example.com/cs-extension/1.0/schema.json" +"0.8.1/item-spec/examples/sample.json","ITEM","0.8.1","","" +"0.8.1/item-spec/examples/sentinel2-sample.json","ITEM","0.8.1","eo","" +"0.9.0/catalog-spec/examples/catalog.json","CATALOG","0.9.0","","" +"0.9.0/collection-spec/examples/landsat-collection.json","COLLECTION","0.9.0","commons|view|eo","" +"0.9.0/collection-spec/examples/landsat-item.json","ITEM","0.9.0","commons|eo|view","https://example.com/stac/landsat-extension/1.0/schema.json","INVALID" +"0.9.0/collection-spec/examples/sentinel2.json","COLLECTION","0.9.0","","" +"0.9.0/extensions/asset/examples/example-landsat8.json","COLLECTION","0.9.0","asset|commons","" +"0.9.0/extensions/checksum/examples/sentinel1.json","ITEM","0.9.0","checksum","" +"0.9.0/extensions/commons/examples/landsat-collection.json","COLLECTION","0.9.0","commons","" +"0.9.0/extensions/commons/examples/landsat-item.json","ITEM","0.9.0","commons|eo|sat","https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/extensions/datacube/examples/example-collection.json","COLLECTION","0.9.0","datacube","" +"0.9.0/extensions/datacube/examples/example-item.json","ITEM","0.9.0","datacube","" +"0.9.0/extensions/eo/examples/example-landsat8.json","ITEM","0.9.0","eo|view|commons","https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/extensions/label/examples/multidataset/catalog.json","CATALOG","0.9.0","","" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.9.0","label|version","","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.9.0","label|version","","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.9.0","label|version","","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.9.0","","" +"0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.9.0","","" +"0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.9.0","label|version","","INVALID" +"0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.9.0","","" +"0.9.0/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.9.0","label|version","" +"0.9.0/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.9.0","","" +"0.9.0/extensions/pointcloud/examples/example-autzen.json","ITEM","0.9.0","pointcloud","" +"0.9.0/extensions/projection/examples/example-landsat8.json","ITEM","0.9.0","proj|commons","" +"0.9.0/extensions/sar/examples/envisat.json","ITEM","0.9.0","sat|sar","" +"0.9.0/extensions/sar/examples/sentinel1.json","ITEM","0.9.0","checksum|sar|sat","" +"0.9.0/extensions/sat/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" +"0.9.0/extensions/scientific/examples/collection.json","COLLECTION","0.9.0","scientific","" +"0.9.0/extensions/scientific/examples/item.json","ITEM","0.9.0","scientific|checksum","" +"0.9.0/extensions/version/examples/collection.json","COLLECTION","0.9.0","version","" +"0.9.0/extensions/version/examples/item.json","ITEM","0.9.0","version","","INVALID" +"0.9.0/extensions/view/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" +"0.9.0/item-spec/examples/datetimerange.json","ITEM","0.9.0","","" +"0.9.0/item-spec/examples/digitalglobe-sample.json","ITEM","0.9.0","eo|proj|view","https://example.digitalglobe.com/stac/1.0/schema.json" +"0.9.0/item-spec/examples/landsat8-sample.json","ITEM","0.9.0","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/item-spec/examples/planet-sample.json","ITEM","0.9.0","eo|view","https://example.planet.com/stac/1.0/schema.json" +"0.9.0/item-spec/examples/sample-full.json","ITEM","0.9.0","eo|view","https://example.com/cs-extension/1.0/schema.json" +"0.9.0/item-spec/examples/sample.json","ITEM","0.9.0","","" +"0.9.0/item-spec/examples/sentinel2-sample.json","ITEM","0.9.0","eo|view|proj|commons","" +"1.0.0-beta.2/catalog-spec/examples/catalog-items.json","CATALOG","1.0.0-beta.2","","" +"1.0.0-beta.2/catalog-spec/examples/catalog.json","CATALOG","1.0.0-beta.2","","" +"1.0.0-beta.2/collection-spec/examples/landsat-collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-beta.2/collection-spec/examples/sentinel2.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/checksum/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum","" +"1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json","COLLECTION","1.0.0-beta.2","collection-assets","https://github.com/NCAR/esm-collection-spec/tree/v0.2.0/schema.json" +"1.0.0-beta.2/extensions/datacube/examples/example-collection.json","COLLECTION","1.0.0-beta.2","datacube","" +"1.0.0-beta.2/extensions/datacube/examples/example-item.json","ITEM","1.0.0-beta.2","datacube","" +"1.0.0-beta.2/extensions/eo/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" +"1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json","COLLECTION","1.0.0-beta.2","item-assets","" +"1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json","CATALOG","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","1.0.0-beta.2","label|version","" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","1.0.0-beta.2","","" +"1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json","ITEM","1.0.0-beta.2","pointcloud","" +"1.0.0-beta.2/extensions/projection/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|projection","" +"1.0.0-beta.2/extensions/sar/examples/envisat.json","ITEM","1.0.0-beta.2","sat|sar","" +"1.0.0-beta.2/extensions/sar/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum|sar|sat","" +"1.0.0-beta.2/extensions/sat/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" +"1.0.0-beta.2/extensions/scientific/examples/collection.json","COLLECTION","1.0.0-beta.2","scientific","" +"1.0.0-beta.2/extensions/scientific/examples/item.json","ITEM","1.0.0-beta.2","scientific|checksum","" +"1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json","ITEM","1.0.0-beta.2","datacube|eo|tiled-assets","" +"1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json","ITEM","1.0.0-beta.2","eo|tiled-assets","" +"1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json","ITEM","1.0.0-beta.2","timestamps","" +"1.0.0-beta.2/extensions/version/examples/collection.json","COLLECTION","1.0.0-beta.2","version","" +"1.0.0-beta.2/extensions/version/examples/item.json","ITEM","1.0.0-beta.2","version","" +"1.0.0-beta.2/extensions/view/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" +"1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json","ITEM","1.0.0-beta.2","projection|view","https://example.com/stac/cbers-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/datetimerange.json","ITEM","1.0.0-beta.2","","" +"1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json","ITEM","1.0.0-beta.2","eo|projection|view","https://example.digitalglobe.com/stac/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/landsat8-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/planet-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.planet.com/stac/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/sample-full.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/cs-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/sample.json","ITEM","1.0.0-beta.2","","" +"1.0.0-beta.2/item-spec/examples/sentinel2-sample.json","ITEM","1.0.0-beta.2","view|projection","" +"gee-0.6.2/CIESIN_GPWv411_GPW_National_Identifier_Grid.json","COLLECTION","0.6.2","scientific","" +"gee-0.6.2/LANDSAT_LT05_C01_T1_ANNUAL_NDWI.json","COLLECTION","0.6.2","","" +"gee-0.6.2/catalog.json","CATALOG","0.6.2","","" +"iserv-0.6.1/2013/03/27/IP0201303271418280967S05834W.json","ITEM","0.6.1","eo","" +"iserv-0.6.1/2013/03/27/catalog.json","CATALOG","0.6.1","","" +"iserv-0.6.1/2013/03/catalog.json","CATALOG","0.6.1","","" +"iserv-0.6.1/2013/catalog.json","CATALOG","0.6.1","","" +"iserv-0.6.1/catalog.json","COLLECTION","0.6.1","","" +"landsat-0.6.0/010/117/2015-01-02/LC80101172015002LGN00.json","ITEM","0.6.0","eo","" +"landsat-0.6.0/010/117/catalog.json","CATALOG","0.6.0","","" +"landsat-0.6.0/010/catalog.json","COLLECTION","0.6.0","","" +"landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json","ITEM","0.6.0","eo","" +"landsat-0.6.0/156/029/catalog.json","CATALOG","0.6.0","","" +"landsat-0.6.0/156/catalog.json","COLLECTION","0.6.0","","" +"landsat-0.6.0/catalog.json","CATALOG","0.6.0","","" +"sentinel-0.6.0/catalog.json","CATALOG","0.6.0","","" +"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/2017-10-13/S2B_9VXK_20171013_0.json","ITEM","0.6.0","eo","" +"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/catalog.json","CATALOG","0.6.0","","" +"sentinel-0.6.0/sentinel-2-l1c/9/V/catalog.json","CATALOG","0.6.0","","" +"sentinel-0.6.0/sentinel-2-l1c/9/catalog.json","CATALOG","0.6.0","","" +"sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","","" +"hand-0.9.0/collection.json","COLLECTION","0.9.0","","" +"hand-0.8.1/collection.json","COLLECTION","0.8.1","","" \ No newline at end of file diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index 30603b3c1..b3a007f2a 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -3,9 +3,10 @@ import unittest from tempfile import TemporaryDirectory -import pystac +import pystac as ps from pystac import (Catalog, Item, CatalogType, STAC_IO) from pystac.extensions import label +import pystac.validation from tests.utils import (TestCases, test_to_from_dict) @@ -44,7 +45,7 @@ def test_from_file_pre_081(self): d['properties'].pop('label:methods') d['properties']['label:task'] = d['properties']['label:tasks'] d['properties'].pop('label:tasks') - label_example_1 = STAC_IO.stac_object_from_dict(d) + label_example_1 = ps.Item.from_dict(d, migrate=True) self.assertEqual(len(label_example_1.ext.label.label_tasks), 2) @@ -81,7 +82,7 @@ def test_read_label_item_owns_asset(self): self.assertEqual(item.assets[asset_key].owner, item) def test_label_description(self): - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:description", label_item.properties) @@ -94,7 +95,7 @@ def test_label_description(self): label_item.validate() def test_label_type(self): - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:type", label_item.properties) @@ -107,8 +108,8 @@ def test_label_type(self): label_item.validate() def test_label_properties(self): - label_item = pystac.read_file(self.label_example_1_uri) - label_item2 = pystac.read_file(self.label_example_2_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) + label_item2 = ps.Item.from_file(self.label_example_2_uri) # Get self.assertIn("label:properties", label_item.properties) @@ -124,7 +125,7 @@ def test_label_properties(self): def test_label_classes(self): # Get - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) label_classes = label_item.ext.label.label_classes self.assertEqual(len(label_classes), 2) @@ -145,7 +146,7 @@ def test_label_classes(self): label_item.validate() def test_label_tasks(self): - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:tasks", label_item.properties) @@ -158,7 +159,7 @@ def test_label_tasks(self): label_item.validate() def test_label_methods(self): - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:methods", label_item.properties) @@ -172,10 +173,10 @@ def test_label_methods(self): def test_label_overviews(self): # Get - label_item = pystac.read_file(self.label_example_1_uri) + label_item = ps.Item.from_file(self.label_example_1_uri) label_overviews = label_item.ext.label.label_overviews - label_item2 = pystac.read_file(self.label_example_2_uri) + label_item2 = ps.Item.from_file(self.label_example_2_uri) label_overviews2 = label_item2.ext.label.label_overviews self.assertEqual(len(label_overviews), 2) diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index e854d90b6..d1a61279d 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -1,10 +1,11 @@ import unittest from urllib.error import HTTPError +import pystac as ps from pystac import STAC_IO from pystac.cache import CollectionCache from pystac.serialization import (identify_stac_object, identify_stac_object_type, - merge_common_properties, STACObjectType) + merge_common_properties) from pystac.serialization.identify import (STACVersionRange, STACVersionID) from tests.utils import TestCases @@ -17,30 +18,33 @@ def setUp(self): def test_identify(self): collection_cache = CollectionCache() for example in self.examples: - path = example['path'] - d = STAC_IO.read_json(path) - if identify_stac_object_type(d) == STACObjectType.ITEM: - try: - merge_common_properties(d, json_href=path, collection_cache=collection_cache) - except HTTPError: - pass + with self.subTest(example['path']): + path = example['path'] + d = STAC_IO.read_json(path) + if identify_stac_object_type(d) == ps.STACObjectType.ITEM: + try: + merge_common_properties(d, + json_href=path, + collection_cache=collection_cache) + except HTTPError: + pass - actual = identify_stac_object(d) - # Explicitly cover __repr__ functions in tests - str_info = str(actual) - self.assertIsInstance(str_info, str) + actual = identify_stac_object(d) + # Explicitly cover __repr__ functions in tests + str_info = str(actual) + self.assertIsInstance(str_info, str) - msg = 'Failed {}:'.format(path) + msg = 'Failed {}:'.format(path) - self.assertEqual(actual.object_type, example['object_type'], msg=msg) - version_contained_in_range = actual.version_range.contains(example['stac_version']) - self.assertTrue(version_contained_in_range, msg=msg) - self.assertEqual(set(actual.common_extensions), - set(example['common_extensions']), - msg=msg) - self.assertEqual(set(actual.custom_extensions), - set(example['custom_extensions']), - msg=msg) + self.assertEqual(actual.object_type, example['object_type'], msg=msg) + version_contained_in_range = actual.version_range.contains(example['stac_version']) + self.assertTrue(version_contained_in_range, msg=msg) + self.assertEqual(set(actual.common_extensions), + set(example['common_extensions']), + msg=msg) + self.assertEqual(set(actual.custom_extensions), + set(example['custom_extensions']), + msg=msg) class VersionTest(unittest.TestCase): @@ -50,10 +54,11 @@ def test_version_ordering(self): self.assertFalse(STACVersionID('0.9.0') != STACVersionID('0.9.0')) self.assertFalse(STACVersionID('0.9.0') > STACVersionID('0.9.0')) self.assertTrue(STACVersionID('1.0.0-beta.2') < '1.0.0') - self.assertTrue(STACVersionID('0.9.1') > '0.9.0') - self.assertFalse(STACVersionID('0.9.0') > '0.9.0') - self.assertTrue(STACVersionID('0.9.0') <= '0.9.0') - self.assertTrue(STACVersionID('1.0.0-beta.1') <= STACVersionID('1.0.0-beta.2')) + self.assertTrue(STACVersionID('0.9.1') > '0.9.0') # type:ignore + self.assertFalse(STACVersionID('0.9.0') > '0.9.0') # type:ignore + self.assertTrue(STACVersionID('0.9.0') <= '0.9.0') # type:ignore + self.assertTrue( + STACVersionID('1.0.0-beta.1') <= STACVersionID('1.0.0-beta.2')) # type:ignore self.assertFalse(STACVersionID('1.0.0') < STACVersionID('1.0.0-beta.2')) def test_version_range_ordering(self): diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index 56f9aa05d..faffc2d17 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -1,10 +1,10 @@ import unittest -import pystac +import pystac as ps from pystac import (STAC_IO, STACObject) from pystac.cache import CollectionCache from pystac.serialization import (identify_stac_object, identify_stac_object_type, - merge_common_properties, migrate_to_latest, STACObjectType) + merge_common_properties, migrate_to_latest) from pystac.utils import str_to_datetime from tests.utils import TestCases @@ -21,7 +21,7 @@ def test_migrate(self): path = example['path'] d = STAC_IO.read_json(path) - if identify_stac_object_type(d) == STACObjectType.ITEM: + if identify_stac_object_type(d) == ps.STACObjectType.ITEM: merge_common_properties(d, json_href=path, collection_cache=collection_cache) info = identify_stac_object(d) @@ -32,17 +32,16 @@ def test_migrate(self): self.assertEqual(migrated_info.object_type, info.object_type) self.assertEqual(migrated_info.version_range.latest_valid_version(), - pystac.get_stac_version()) + ps.get_stac_version()) self.assertEqual(set(migrated_info.common_extensions), set(info.common_extensions)) self.assertEqual(set(migrated_info.custom_extensions), set(info.custom_extensions)) # Test that PySTAC can read it without errors. - if info.object_type != STACObjectType.ITEMCOLLECTION: - self.assertIsInstance(STAC_IO.stac_object_from_dict(migrated_d, href=path), - STACObject) + if info.object_type != ps.STACObjectType.ITEMCOLLECTION: + self.assertIsInstance(ps.read_dict(migrated_d, href=path), STACObject) def test_migrates_removed_extension(self): - item = pystac.read_file( + item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.7.0/extensions/sar/' 'examples/sentinel1.json')) self.assertFalse('dtr' in item.stac_extensions) @@ -50,7 +49,7 @@ def test_migrates_removed_extension(self): str_to_datetime("2018-11-03T23:58:55.121559Z")) def test_migrates_added_extension(self): - item = pystac.read_file( + item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.8.1/item-spec/' 'examples/planet-sample.json')) self.assertTrue('view' in item.stac_extensions) @@ -59,7 +58,7 @@ def test_migrates_added_extension(self): self.assertEqual(item.ext.view.off_nadir, 1) def test_migrates_renamed_extension(self): - collection = pystac.read_file( + collection = ps.Collection.from_file( TestCases.get_path('data-files/examples/0.9.0/extensions/asset/' 'examples/example-landsat8.json')) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index d5b9293ed..19caab8b8 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,11 +1,12 @@ import os import json +from typing import Any, Dict, List, Tuple, Union, cast import unittest from tempfile import TemporaryDirectory from datetime import datetime from collections import defaultdict -import pystac +import pystac as ps from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, Extensions, HIERARCHICAL_LINKS) from pystac.extensions.label import LabelClasses @@ -19,7 +20,7 @@ def test_determine_type_for_absolute_published(self): cat = TestCases.test_case_1() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat_json = pystac.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.ABSOLUTE_PUBLISHED) @@ -28,13 +29,13 @@ def test_determine_type_for_relative_published(self): cat = TestCases.test_case_2() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) - cat_json = pystac.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.RELATIVE_PUBLISHED) def test_determine_type_for_self_contained(self): - cat_json = pystac.STAC_IO.read_json( + cat_json = ps.STAC_IO.read_json( TestCases.get_path('data-files/catalogs/test-case-1/catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.SELF_CONTAINED) @@ -164,15 +165,15 @@ def test_clear_children_sets_parent_and_root_to_None(self): def test_add_child_throws_if_item(self): cat = TestCases.test_case_1() - item = next(cat.get_all_items()) - with self.assertRaises(pystac.STACError): - cat.add_child(item) + item = next(iter(cat.get_all_items())) + with self.assertRaises(ps.STACError): + cat.add_child(item) # type:ignore def test_add_item_throws_if_child(self): cat = TestCases.test_case_1() - child = next(cat.get_children()) - with self.assertRaises(pystac.STACError): - cat.add_item(child) + child = next(iter(cat.get_children())) + with self.assertRaises(ps.STACError): + cat.add_item(child) # type:ignore def test_get_child_returns_none_if_not_found(self): cat = TestCases.test_case_1() @@ -190,7 +191,7 @@ def test_sets_catalog_type(self): self.assertEqual(cat.catalog_type, CatalogType.SELF_CONTAINED) def test_walk_iterates_correctly(self): - def test_catalog(cat): + def test_catalog(cat: Catalog): expected_catalog_iterations = 1 actual_catalog_iterations = 0 with self.subTest(title='Testing catalog {}'.format(cat.id)): @@ -212,8 +213,8 @@ def test_clone_generates_correct_links(self): catalogs = TestCases.all_test_catalogs() for catalog in catalogs: - expected_link_types_to_counts = {} - actual_link_types_to_counts = {} + expected_link_types_to_counts: Any = {} + actual_link_types_to_counts: Any = {} for root, _, items in catalog.walk(): expected_link_types_to_counts[root.id] = defaultdict(int) @@ -251,10 +252,10 @@ def test_save_uses_previous_catalog_type(self): assert catalog.catalog_type == CatalogType.SELF_CONTAINED with TemporaryDirectory() as tmp_dir: catalog.normalize_hrefs(tmp_dir) - href = catalog.get_self_href() + href = catalog.self_href catalog.save() - cat2 = pystac.read_file(href) + cat2 = ps.Catalog.from_file(href) self.assertEqual(cat2.catalog_type, CatalogType.SELF_CONTAINED) def test_clone_uses_previous_catalog_type(self): @@ -270,15 +271,15 @@ def test_normalize_hrefs_sets_all_hrefs(self): self.assertTrue(root.get_self_href().startswith('http://example.com')) for link in root.links: if link.is_resolved(): - target_href = link.target.get_self_href() + target_href = cast(ps.STACObject, link.target).self_href else: - target_href = link.get_absolute_href() + target_href = link.absolute_href self.assertTrue( 'http://example.com' in target_href, '[{}] {} does not contain "{}"'.format(link.rel, target_href, 'http://example.com')) for item in items: - self.assertIn('http://example.com', item.get_self_href()) + self.assertIn('http://example.com', item.self_href) def test_normalize_hrefs_makes_absolute_href(self): catalog = TestCases.test_case_1() @@ -314,9 +315,9 @@ def test_generate_subcatalogs_does_not_change_item_count(self): with TemporaryDirectory() as tmp_dir: catalog.normalize_hrefs(tmp_dir) - catalog.save(pystac.CatalogType.SELF_CONTAINED) + catalog.save(ps.CatalogType.SELF_CONTAINED) - cat2 = pystac.read_file(os.path.join(tmp_dir, 'catalog.json')) + cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, 'catalog.json')) for child in cat2.get_children(): actual = len(list(child.get_all_items())) expected = item_counts[child.id] @@ -402,13 +403,13 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self): catalog.normalize_hrefs('/') for item in catalog.get_all_items(): - parent_href = item.get_parent().get_self_href() + parent_href = item.get_parent().self_href path_to_parent, _ = os.path.split(parent_href) subcats = [el for el in path_to_parent.split('/') if el] self.assertEqual(len(subcats), 2, msg=" for item '{}'".format(item.id)) def test_map_items(self): - def item_mapper(item): + def item_mapper(item: ps.Item) -> ps.Item: item.properties['ITEM_MAPPER'] = 'YEP' return item @@ -429,7 +430,7 @@ def item_mapper(item): self.assertFalse('ITEM_MAPPER' in item.properties) def test_map_items_multiple(self): - def item_mapper(item): + def item_mapper(item: ps.Item) -> List[ps.Item]: item2 = item.clone() item2.id = item2.id + '_2' item.properties['ITEM_MAPPER_1'] = 'YEP' @@ -485,11 +486,11 @@ def test_map_items_multiple_2(self): item2.add_asset('ortho', Asset(href='/some/other/ortho.tif')) kitten.add_item(item2) - def modify_item_title(item): + def modify_item_title(item: ps.Item) -> ps.Item: item.title = 'Some new title' return item - def create_label_item(item): + def create_label_item(item: ps.Item) -> List[ps.Item]: # Assumes the GEOJSON labels are in the # same location as the image img_href = item.assets['ortho'].href @@ -522,7 +523,7 @@ def create_label_item(item): def test_map_assets_single(self): changed_asset = 'd43bead8-e3f8-4c51-95d6-e24e750a402b' - def asset_mapper(key, asset): + def asset_mapper(key: str, asset: ps.Asset) -> ps.Asset: if key == changed_asset: asset.title = 'NEW TITLE' @@ -549,10 +550,10 @@ def asset_mapper(key, asset): self.assertTrue(found) def test_map_assets_tup(self): - changed_assets = [] + changed_assets: List[str] = [] - def asset_mapper(key, asset): - if 'geotiff' in asset.media_type: + def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Tuple[str, ps.Asset]]: + if asset.media_type and 'geotiff' in asset.media_type: asset.title = 'NEW TITLE' changed_assets.append(key) return ('{}-modified'.format(key), asset) @@ -586,8 +587,8 @@ def asset_mapper(key, asset): def test_map_assets_multi(self): changed_assets = [] - def asset_mapper(key, asset): - if 'geotiff' in asset.media_type: + def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Dict[str, ps.Asset]]: + if asset.media_type and 'geotiff' in asset.media_type: changed_assets.append(key) mod1 = asset.clone() mod1.title = 'NEW TITLE 1' @@ -649,23 +650,23 @@ def test_make_all_asset_hrefs_relative(self): self.assertEqual(asset.href, original_href) def test_make_all_links_relative_or_absolute(self): - def check_all_relative(cat): + def check_all_relative(cat: Catalog): for root, catalogs, items in cat.walk(): for link in root.links: if link.rel in HIERARCHICAL_LINKS: - self.assertFalse(is_absolute_href(link.get_href())) + self.assertFalse(is_absolute_href(link.href)) for item in items: for link in item.links: if link.rel in HIERARCHICAL_LINKS: - self.assertFalse(is_absolute_href(link.get_href())) + self.assertFalse(is_absolute_href(link.href)) - def check_all_absolute(cat): + def check_all_absolute(cat: Catalog): for root, catalogs, items in cat.walk(): for link in root.links: - self.assertTrue(is_absolute_href(link.get_href())) + self.assertTrue(is_absolute_href(link.href)) for item in items: for link in item.links: - self.assertTrue(is_absolute_href(link.get_href())) + self.assertTrue(is_absolute_href(link.href)) test_cases = TestCases.all_test_catalogs() @@ -704,7 +705,7 @@ def test_extra_fields(self): self.assertTrue('type' in cat_json) self.assertEqual(cat_json['type'], 'FeatureCollection') - read_cat = pystac.read_file(p) + read_cat = ps.Catalog.from_file(p) self.assertTrue('type' in read_cat.extra_fields) self.assertEqual(read_cat.extra_fields['type'], 'FeatureCollection') @@ -722,9 +723,9 @@ def test_validate_all(self): item.geometry = {'type': 'INVALID', 'coordinates': 'NONE'} with TemporaryDirectory() as tmp_dir: cat.normalize_hrefs(tmp_dir) - cat.save(catalog_type=pystac.CatalogType.SELF_CONTAINED) + cat.save(catalog_type=ps.CatalogType.SELF_CONTAINED) - cat2 = pystac.read_file(os.path.join(tmp_dir, 'catalog.json')) + cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, 'catalog.json')) with self.assertRaises(STACValidationError): cat2.validate_all() @@ -748,7 +749,7 @@ def test_set_hrefs_manually(self): if parent is None: root_dir = tmp_dir else: - d = os.path.dirname(parent.get_self_href()) + d = os.path.dirname(parent.self_href) root_dir = os.path.join(d, root.id) root_href = os.path.join(root_dir, root.DEFAULT_FILE_NAME) root.set_self_href(root_href) @@ -768,7 +769,7 @@ def test_set_hrefs_manually(self): if parent is None: self.assertEqual(root.get_self_href(), os.path.join(tmp_dir, 'catalog.json')) else: - d = os.path.dirname(parent.get_self_href()) + d = os.path.dirname(parent.self_href) self.assertEqual(root.get_self_href(), os.path.join(d, root.id, root.DEFAULT_FILE_NAME)) for item in items: @@ -780,7 +781,7 @@ def test_collections_cache_correctly(self): for cat in catalogs: with MockStacIO() as mock_io: expected_collection_reads = set([]) - for root, children, items in cat.walk(): + for root, _, items in cat.walk(): if isinstance(root, Collection) and root != cat: expected_collection_reads.add(root.get_self_href()) @@ -834,7 +835,7 @@ def test_resolve_planet(self): def test_handles_children_with_same_id(self): # This catalog has the root and child collection share an ID. - cat = pystac.read_file(TestCases.get_path('data-files/invalid/shared-id/catalog.json')) + cat = ps.Catalog.from_file(TestCases.get_path('data-files/invalid/shared-id/catalog.json')) items = list(cat.get_all_items()) self.assertEqual(len(items), 1) @@ -849,19 +850,19 @@ def test_catalog_with_href_caches_by_href(self): class FullCopyTest(unittest.TestCase): - def check_link(self, link, tag): + def check_link(self, link: ps.Link, tag: str): if link.is_resolved(): - target_href = link.target.get_self_href() + target_href: str = cast(ps.STACObject, link.target).self_href else: - target_href = link.target + target_href = str(link.target) self.assertTrue(tag in target_href, '[{}] {} does not contain "{}"'.format(link.rel, target_href, tag)) - def check_item(self, item, tag): + def check_item(self, item: Item, tag: str): for link in item.links: self.check_link(link, tag) - def check_catalog(self, c, tag): + def check_catalog(self, c: Catalog, tag: str): self.assertEqual(len(c.get_links('root')), 1) for link in c.links: diff --git a/tests/test_collection.py b/tests/test_collection.py index 80a5519fd..b9588fd72 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -5,11 +5,9 @@ from datetime import datetime from dateutil import tz -import pystac +import pystac as ps 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) @@ -26,82 +24,22 @@ def test_spatial_extent_from_coordinates(self): for x in bbox: self.assertTrue(type(x) is float) - def test_eo_items_are_heritable(self): - item1 = Item(id='test-item-1', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=TEST_DATETIME, - properties={'key': 'one'}, - stac_extensions=['eo', 'commons']) - - item2 = Item(id='test-item-2', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=TEST_DATETIME, - properties={'key': 'two'}, - stac_extensions=['eo', 'commons']) - - wv3_bands = [ - Band.create(name='Coastal', description='Coastal: 400 - 450 nm', common_name='coastal'), - Band.create(name='Blue', description='Blue: 450 - 510 nm', common_name='blue'), - Band.create(name='Green', description='Green: 510 - 580 nm', common_name='green'), - Band.create(name='Yellow', description='Yellow: 585 - 625 nm', common_name='yellow'), - Band.create(name='Red', description='Red: 630 - 690 nm', common_name='red'), - Band.create(name='Red Edge', - description='Red Edge: 705 - 745 nm', - common_name='rededge'), - Band.create(name='Near-IR1', description='Near-IR1: 770 - 895 nm', common_name='nir08'), - Band.create(name='Near-IR2', description='Near-IR2: 860 - 1040 nm', common_name='nir09') - ] - - spatial_extent = SpatialExtent(bboxes=[RANDOM_BBOX]) - temporal_extent = TemporalExtent(intervals=[[item1.datetime, None]]) - - collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) - - common_properties = { - 'eo:bands': [b.to_dict() for b in wv3_bands], - 'gsd': 0.3, - 'eo:platform': 'Maxar', - 'eo:instrument': 'WorldView3' - } - - collection = Collection(id='test', - description='test', - extent=collection_extent, - properties=common_properties, - stac_extensions=['commons'], - license='CC-BY-SA-4.0') - - collection.add_items([item1, item2]) - - with TemporaryDirectory() as tmp_dir: - collection.normalize_hrefs(tmp_dir) - collection.save(catalog_type=CatalogType.SELF_CONTAINED) - - read_col = Collection.from_file('{}/collection.json'.format(tmp_dir)) - items = list(read_col.get_all_items()) - - self.assertEqual(len(items), 2) - self.assertTrue(items[0].ext.implements('eo')) - self.assertTrue(items[1].ext.implements('eo')) - def test_read_eo_items_are_heritable(self): cat = TestCases.test_case_5() - item = next(cat.get_all_items()) + item = next(iter(cat.get_all_items())) self.assertTrue(item.ext.implements('eo')) def test_save_uses_previous_catalog_type(self): collection = TestCases.test_case_8() - assert collection.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION + assert collection.STAC_OBJECT_TYPE == ps.STACObjectType.COLLECTION self.assertEqual(collection.catalog_type, CatalogType.SELF_CONTAINED) with TemporaryDirectory() as tmp_dir: collection.normalize_hrefs(tmp_dir) - href = collection.get_self_href() + href = collection.self_href collection.save() - collection2 = pystac.read_file(href) + collection2 = ps.Collection.from_file(href) self.assertEqual(collection2.catalog_type, CatalogType.SELF_CONTAINED) def test_clone_uses_previous_catalog_type(self): @@ -115,12 +53,12 @@ def test_multiple_extents(self): col1 = cat1.get_child('country-1').get_child('area-1-1') col1.validate() self.assertIsInstance(col1, Collection) - validate_dict(col1.to_dict(), STACObjectType.COLLECTION) + validate_dict(col1.to_dict(), ps.STACObjectType.COLLECTION) multi_ext_uri = TestCases.get_path('data-files/collections/multi-extent.json') with open(multi_ext_uri) as f: multi_ext_dict = json.load(f) - validate_dict(multi_ext_dict, STACObjectType.COLLECTION) + validate_dict(multi_ext_dict, ps.STACObjectType.COLLECTION) self.assertIsInstance(Collection.from_dict(multi_ext_dict), Collection) multi_ext_col = Collection.from_file(multi_ext_uri) @@ -149,7 +87,7 @@ def test_extra_fields(self): self.assertTrue('test' in col_json) self.assertEqual(col_json['test'], 'extra') - read_col = pystac.read_file(p) + read_col = ps.Collection.from_file(p) self.assertTrue('test' in read_col.extra_fields) self.assertEqual(read_col.extra_fields['test'], 'extra') @@ -157,6 +95,7 @@ def test_update_extents(self): catalog = TestCases.test_case_2() base_collection = catalog.get_child('1a8c1632-fa91-4a62-b33e-3a87c2ebdf16') + assert isinstance(base_collection, Collection) base_extent = base_collection.extent collection = base_collection.clone() @@ -205,19 +144,12 @@ def test_supplying_href_in_init_does_not_fail(self): collection = Collection(id='test', description='test desc', extent=collection_extent, - properties={}, href=test_href) self.assertEqual(collection.get_self_href(), test_href) - def test_reading_0_8_1_collection_as_catalog_throws_correct_exception(self): - cat = pystac.Catalog.from_file( - TestCases.get_path('data-files/examples/hand-0.8.1/collection.json')) - with self.assertRaises(ValueError): - list(cat.get_all_items()) - def test_collection_with_href_caches_by_href(self): - collection = pystac.read_file( + collection = ps.Collection.from_file( TestCases.get_path('data-files/examples/hand-0.8.1/collection.json')) cache = collection._resolved_objects diff --git a/tests/test_item.py b/tests/test_item.py index c309adca0..da85a8d90 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -4,12 +4,11 @@ import unittest from tempfile import TemporaryDirectory -import pystac +import pystac as ps from pystac import Asset, Item, Provider from pystac.validation import validate_dict from pystac.item import CommonMetadata from pystac.utils import (str_to_datetime, is_absolute_href) -from pystac.serialization.identify import STACObjectType from tests.utils import (TestCases, test_to_from_dict) @@ -66,7 +65,7 @@ def test_asset_absolute_href(self): self.assertEqual(expected_href, actual_href) def test_extra_fields(self): - item = pystac.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) item.extra_fields['test'] = 'extra' @@ -78,7 +77,7 @@ def test_extra_fields(self): self.assertTrue('test' in item_json) self.assertEqual(item_json['test'], 'extra') - read_item = pystac.read_file(p) + read_item = ps.read_file(p) self.assertTrue('test' in read_item.extra_fields) self.assertEqual(read_item.extra_fields['test'], 'extra') @@ -103,9 +102,9 @@ def test_datetime_ISO8601_format(self): self.assertEqual('2016-05-03T13:22:30.040000Z', formatted_time) def test_null_datetime(self): - item = pystac.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) - with self.assertRaises(pystac.STACError): + with self.assertRaises(ps.STACError): Item('test', geometry=item.geometry, bbox=item.bbox, datetime=None, properties={}) null_dt_item = Item('test', @@ -113,15 +112,14 @@ def test_null_datetime(self): bbox=item.bbox, datetime=None, properties={ - 'start_datetime': pystac.utils.datetime_to_str(item.datetime), - 'end_datetime': pystac.utils.datetime_to_str(item.datetime) + 'start_datetime': ps.utils.datetime_to_str(item.datetime), + 'end_datetime': ps.utils.datetime_to_str(item.datetime) }) null_dt_item.validate() def test_get_set_asset_datetime(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) item_datetime = item.datetime # No property on asset @@ -156,7 +154,7 @@ def test_null_geometry(self): with open(m) as f: item_dict = json.load(f) - validate_dict(item_dict, STACObjectType.ITEM) + validate_dict(item_dict, ps.STACObjectType.ITEM) item = Item.from_dict(item_dict) self.assertIsInstance(item, Item) @@ -168,12 +166,12 @@ def test_null_geometry(self): item_dict['bbox'] def test_0_9_item_with_no_extensions_does_not_read_collection_data(self): - item_json = pystac.STAC_IO.read_json( + item_json = ps.STAC_IO.read_json( TestCases.get_path('data-files/examples/hand-0.9.0/010100/010100.json')) assert item_json.get('stac_extensions') is None assert item_json.get('stac_version') == '0.9.0' - did_merge = pystac.serialization.common_properties.merge_common_properties(item_json) + did_merge = ps.serialization.common_properties.merge_common_properties(item_json) self.assertFalse(did_merge) def test_clone_sets_asset_owner(self): @@ -406,8 +404,7 @@ def test_common_metadata_basics(self): self.assertEqual(x.properties['gsd'], example_gsd) def test_asset_start_datetime(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.start_datetime @@ -428,8 +425,7 @@ def test_asset_start_datetime(self): self.assertEqual(cm.start_datetime, item_value) def test_asset_end_datetime(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.end_datetime @@ -450,8 +446,7 @@ def test_asset_end_datetime(self): self.assertEqual(cm.end_datetime, item_value) def test_asset_license(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.license @@ -472,15 +467,14 @@ def test_asset_license(self): self.assertEqual(cm.license, item_value) def test_asset_providers(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.providers a2_known_value = [ - pystac.Provider(name="USGS", - url="https://landsat.usgs.gov/", - roles=["producer", "licensor"]) + ps.Provider(name="USGS", + url="https://landsat.usgs.gov/", + roles=["producer", "licensor"]) ] # Get @@ -491,17 +485,14 @@ def test_asset_providers(self): self.assertEqual(a2_value[0].to_dict(), a2_known_value[0].to_dict()) # Set - set_value = [ - pystac.Provider(name="John Snow", url="https://cholera.com/", roles=["producer"]) - ] + set_value = [ps.Provider(name="John Snow", url="https://cholera.com/", roles=["producer"])] cm.set_providers(set_value, item.assets['analytic']) new_a1_value = cm.get_providers(item.assets['analytic']) self.assertEqual(new_a1_value[0].to_dict(), set_value[0].to_dict()) self.assertEqual(cm.providers[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.platform @@ -522,8 +513,7 @@ def test_asset_platform(self): self.assertEqual(cm.platform, item_value) def test_asset_instruments(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.instruments @@ -544,8 +534,7 @@ def test_asset_instruments(self): self.assertEqual(cm.instruments, item_value) def test_asset_constellation(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.constellation @@ -566,8 +555,7 @@ def test_asset_constellation(self): self.assertEqual(cm.constellation, item_value) def test_asset_mission(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.mission @@ -588,8 +576,7 @@ def test_asset_mission(self): self.assertEqual(cm.mission, item_value) def test_asset_gsd(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.gsd @@ -610,8 +597,7 @@ def test_asset_gsd(self): self.assertEqual(cm.gsd, item_value) def test_asset_created(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.created @@ -632,8 +618,7 @@ def test_asset_created(self): self.assertEqual(cm.created, item_value) def test_asset_updated(self): - item = pystac.read_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.updated diff --git a/tests/test_layout.py b/tests/test_layout.py index c8995589c..a5a682dd2 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -1,8 +1,10 @@ from datetime import (datetime, timedelta) import os +from typing import Callable +from pystac.collection import Collection import unittest -import pystac +import pystac as ps from pystac.layout import (LayoutTemplate, CustomLayoutStrategy, TemplateLayoutStrategy, BestPracticesLayoutStrategy, TemplateError) from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX) @@ -18,11 +20,7 @@ def test_templates_item_datetime(self): template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') - item = pystac.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=dt, - properties={}) + item = ps.Item('test', geometry=RANDOM_GEOM, bbox=RANDOM_BBOX, datetime=dt, properties={}) parts = template.get_template_values(item) @@ -45,14 +43,14 @@ def test_templates_item_start_datetime(self): template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') - item = pystac.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=None, - properties={ - 'start_datetime': dt.isoformat(), - 'end_datetime': (dt + timedelta(days=1)).isoformat() - }) + item = ps.Item('test', + geometry=RANDOM_GEOM, + bbox=RANDOM_BBOX, + datetime=None, + properties={ + 'start_datetime': dt.isoformat(), + 'end_datetime': (dt + timedelta(days=1)).isoformat() + }) parts = template.get_template_values(item) @@ -70,7 +68,7 @@ def test_templates_item_collection(self): template = LayoutTemplate('${collection}/item.json') collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + item = next(iter(collection.get_all_items())) assert item.collection_id is not None parts = template.get_template_values(item) @@ -85,7 +83,7 @@ def test_throws_for_no_collection(self): template = LayoutTemplate('${collection}/item.json') collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + item = next(iter(collection.get_all_items())) item.set_collection(None) assert item.collection_id is None @@ -97,18 +95,18 @@ def test_nested_properties(self): template = LayoutTemplate('${test.prop}/${ext:extra.test.prop}/item.json') - item = pystac.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=dt, - properties={'test': { - 'prop': 4326 - }}, - extra_fields={'ext:extra': { - 'test': { - 'prop': 3857 - } - }}) + item = ps.Item('test', + geometry=RANDOM_GEOM, + bbox=RANDOM_BBOX, + datetime=dt, + properties={'test': { + 'prop': 4326 + }}, + extra_fields={'ext:extra': { + 'test': { + 'prop': 3857 + } + }}) parts = template.get_template_values(item) @@ -126,11 +124,11 @@ def test_substitute_with_colon_properties(self): template = LayoutTemplate('${ext:prop}/item.json') - item = pystac.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, - datetime=dt, - properties={'ext:prop': 1}) + item = ps.Item('test', + geometry=RANDOM_GEOM, + bbox=RANDOM_BBOX, + datetime=dt, + properties={'ext:prop': 1}) path = template.substitute(item) @@ -140,15 +138,15 @@ def test_defaults(self): template = LayoutTemplate('${doesnotexist}/collection.json', defaults={'doesnotexist': 'yes'}) - collection = TestCases.test_case_4().get_child('acc') - collection.properties = {'up': 'down'} - collection.extra_fields = {'one': 'two'} - path = template.substitute(collection) + catalog = TestCases.test_case_4().get_child('acc') + assert catalog is not None + catalog.extra_fields = {'one': 'two'} + path = template.substitute(catalog) self.assertEqual(path, 'yes/collection.json') def test_docstring_examples(self): - item = pystac.read_file( + item = ps.Item.from_file( TestCases.get_path( "data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json")) item.common_metadata.license = "CC-BY-3.0" @@ -169,27 +167,27 @@ def test_docstring_examples(self): class CustomLayoutStrategyTest(unittest.TestCase): - def get_custom_catalog_func(self): - def fn(cat, parent_dir, is_root): + def get_custom_catalog_func(self) -> Callable[[ps.Catalog, str, bool], str]: + def fn(cat: ps.Catalog, parent_dir: str, is_root: bool): return os.path.join(parent_dir, 'cat/{}/{}.json'.format(is_root, cat.id)) return fn - def get_custom_collection_func(self): - def fn(col, parent_dir, is_root): + def get_custom_collection_func(self) -> Callable[[ps.Collection, str, bool], str]: + def fn(col: ps.Collection, parent_dir: str, is_root: bool): return os.path.join(parent_dir, 'col/{}/{}.json'.format(is_root, col.id)) return fn - def get_custom_item_func(self): - def fn(item, parent_dir): + def get_custom_item_func(self) -> Callable[[ps.Item, str], str]: + def fn(item: ps.Item, parent_dir: str): return os.path.join(parent_dir, 'item/{}.json'.format(item.id)) return fn def test_produces_layout_for_catalog(self): strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func()) - cat = pystac.Catalog(id='test', description='test desc') + cat = ps.Catalog(id='test', description='test desc') href = strategy.get_href(cat, parent_dir='http://example.com', is_root=True) self.assertEqual(href, 'http://example.com/cat/True/test.json') @@ -198,7 +196,7 @@ def test_produces_fallback_layout_for_catalog(self): strategy = CustomLayoutStrategy(collection_func=self.get_custom_collection_func(), item_func=self.get_custom_item_func(), fallback_strategy=fallback) - cat = pystac.Catalog(id='test', description='test desc') + cat = ps.Catalog(id='test', description='test desc') href = strategy.get_href(cat, parent_dir='http://example.com') expected = fallback.get_href(cat, parent_dir='http://example.com') self.assertEqual(href, expected) @@ -222,17 +220,17 @@ def test_produces_fallback_layout_for_collection(self): def test_produces_layout_for_item(self): strategy = CustomLayoutStrategy(item_func=self.get_custom_item_func()) collection = TestCases.test_case_8() - item = next(collection.get_all_items()) + item = next(iter(collection.get_all_items())) href = strategy.get_href(item, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/item/{}.json'.format(item.id)) def test_produces_fallback_layout_for_item(self): fallback = BestPracticesLayoutStrategy() - strategy = CustomLayoutStrategy(catalog_func=self.get_custom_item_func(), + strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func(), collection_func=self.get_custom_collection_func(), fallback_strategy=fallback) collection = TestCases.test_case_8() - item = next(collection.get_all_items()) + item = next(iter(collection.get_all_items())) href = strategy.get_href(item, parent_dir='http://example.com') expected = fallback.get_href(item, parent_dir='http://example.com') self.assertEqual(href, expected) @@ -243,16 +241,21 @@ class TemplateLayoutStrategyTest(unittest.TestCase): TEST_COLLECTION_TEMPLATE = 'col/${id}/${license}' TEST_ITEM_TEMPLATE = 'item/${collection}/${id}.json' + def _get_collection(self) -> Collection: + result = TestCases.test_case_4().get_child('acc') + assert isinstance(result, Collection) + return result + def test_produces_layout_for_catalog(self): strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE) - cat = pystac.Catalog(id='test', description='test-desc') + cat = ps.Catalog(id='test', description='test-desc') href = strategy.get_href(cat, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/cat/test/test-desc/catalog.json') def test_produces_layout_for_catalog_with_filename(self): template = 'cat/${id}/${description}/${id}.json' strategy = TemplateLayoutStrategy(catalog_template=template) - cat = pystac.Catalog(id='test', description='test-desc') + cat = ps.Catalog(id='test', description='test-desc') href = strategy.get_href(cat, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/cat/test/test-desc/test.json') @@ -261,14 +264,14 @@ def test_produces_fallback_layout_for_catalog(self): strategy = TemplateLayoutStrategy(collection_template=self.TEST_COLLECTION_TEMPLATE, item_template=self.TEST_ITEM_TEMPLATE, fallback_strategy=fallback) - cat = pystac.Catalog(id='test', description='test desc') + cat = ps.Catalog(id='test', description='test desc') href = strategy.get_href(cat, parent_dir='http://example.com') expected = fallback.get_href(cat, parent_dir='http://example.com') self.assertEqual(href, expected) def test_produces_layout_for_collection(self): strategy = TemplateLayoutStrategy(collection_template=self.TEST_COLLECTION_TEMPLATE) - collection = TestCases.test_case_4().get_child('acc') + collection = self._get_collection() href = strategy.get_href(collection, parent_dir='http://example.com') self.assertEqual( href, @@ -278,7 +281,7 @@ def test_produces_layout_for_collection(self): def test_produces_layout_for_collection_with_filename(self): template = 'col/${id}/${license}/col.json' strategy = TemplateLayoutStrategy(collection_template=template) - collection = TestCases.test_case_4().get_child('acc') + collection = self._get_collection() href = strategy.get_href(collection, parent_dir='http://example.com') self.assertEqual( href, 'http://example.com/col/{}/{}/col.json'.format(collection.id, collection.license)) @@ -288,15 +291,15 @@ def test_produces_fallback_layout_for_collection(self): strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE, item_template=self.TEST_ITEM_TEMPLATE, fallback_strategy=fallback) - collection = TestCases.test_case_4().get_child('acc') + collection = self._get_collection() href = strategy.get_href(collection, parent_dir='http://example.com') expected = fallback.get_href(collection, parent_dir='http://example.com') self.assertEqual(href, expected) def test_produces_layout_for_item(self): strategy = TemplateLayoutStrategy(item_template=self.TEST_ITEM_TEMPLATE) - collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + collection = self._get_collection() + item = next(iter(collection.get_all_items())) href = strategy.get_href(item, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/item/{}/{}.json'.format(item.collection_id, item.id)) @@ -304,8 +307,8 @@ def test_produces_layout_for_item(self): def test_produces_layout_for_item_without_filename(self): template = 'item/${collection}' strategy = TemplateLayoutStrategy(item_template=template) - collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + collection = self._get_collection() + item = next(iter(collection.get_all_items())) href = strategy.get_href(item, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/item/{}/{}.json'.format(item.collection_id, item.id)) @@ -315,8 +318,8 @@ def test_produces_fallback_layout_for_item(self): strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE, collection_template=self.TEST_COLLECTION_TEMPLATE, fallback_strategy=fallback) - collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + collection = self._get_collection() + item = next(iter(collection.get_all_items())) href = strategy.get_href(item, parent_dir='http://example.com') expected = fallback.get_href(item, parent_dir='http://example.com') self.assertEqual(href, expected) @@ -327,12 +330,12 @@ def setUp(self): self.strategy = BestPracticesLayoutStrategy() def test_produces_layout_for_root_catalog(self): - cat = pystac.Catalog(id='test', description='test desc') + cat = ps.Catalog(id='test', description='test desc') href = self.strategy.get_href(cat, parent_dir='http://example.com', is_root=True) self.assertEqual(href, 'http://example.com/catalog.json') def test_produces_layout_for_child_catalog(self): - cat = pystac.Catalog(id='test', description='test desc') + cat = ps.Catalog(id='test', description='test desc') href = self.strategy.get_href(cat, parent_dir='http://example.com') self.assertEqual(href, 'http://example.com/test/catalog.json') @@ -348,7 +351,7 @@ def test_produces_layout_for_child_collection(self): def test_produces_layout_for_item(self): collection = TestCases.test_case_8() - item = next(collection.get_all_items()) + item = next(iter(collection.get_all_items())) href = self.strategy.get_href(item, parent_dir='http://example.com') expected = 'http://example.com/{}/{}.json'.format(item.id, item.id) self.assertEqual(href, expected) diff --git a/tests/test_writing.py b/tests/test_writing.py index d5ac1d311..1ebbc6e7f 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -1,9 +1,8 @@ import unittest from tempfile import TemporaryDirectory -import pystac -from pystac import (STAC_IO, STACObject, Collection, CatalogType, HIERARCHICAL_LINKS) -from pystac.serialization import (STACObjectType) +import pystac as ps +from pystac import (STAC_IO, Collection, CatalogType, HIERARCHICAL_LINKS) from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href from pystac.validation import validate_dict @@ -14,7 +13,7 @@ class STACWritingTest(unittest.TestCase): """Tests writing STACs, using JSON Schema validation, and ensure that links are correctly set to relative or absolute. """ - def validate_catalog(self, catalog): + def validate_catalog(self, catalog: ps.Catalog): catalog.validate() validated_count = 1 @@ -27,12 +26,12 @@ def validate_catalog(self, catalog): return validated_count - def validate_file(self, path, object_type): + def validate_file(self, path: str, object_type: str): d = STAC_IO.read_json(path) return validate_dict(d, object_type) - def validate_link_types(self, root_href, catalog_type): - def validate_asset_href_type(item, item_href, link_type): + def validate_link_types(self, root_href: str, catalog_type: ps.CatalogType): + def validate_asset_href_type(item: ps.Item, item_href: str): for asset in item.assets.values(): if not is_absolute_href(asset.href): is_valid = not is_absolute_href(asset.href) @@ -44,36 +43,36 @@ def validate_asset_href_type(item, item_href, link_type): else: self.assertTrue(is_valid) - def validate_item_link_type(href, link_type, should_include_self): + def validate_item_link_type(href: str, link_type: str, should_include_self: bool): item_dict = STAC_IO.read_json(href) - item = STACObject.from_file(href) - rel_links = HIERARCHICAL_LINKS + pystac.STAC_EXTENSIONS.get_extended_object_links(item) + item = ps.Item.from_file(href) + rel_links = HIERARCHICAL_LINKS + ps.STAC_EXTENSIONS.get_extended_object_links(item) for link in item.get_links(): if not link.rel == 'self': if link_type == 'RELATIVE' and link.rel in rel_links: - self.assertFalse(is_absolute_href(link.get_href())) + self.assertFalse(is_absolute_href(link.href)) else: - self.assertTrue(is_absolute_href(link.get_href())) + self.assertTrue(is_absolute_href(link.href)) - validate_asset_href_type(item, href, link_type) + validate_asset_href_type(item, href) rels = set([link['rel'] for link in item_dict['links']]) self.assertEqual('self' in rels, should_include_self) - def validate_catalog_link_type(href, link_type, should_include_self): + def validate_catalog_link_type(href: str, link_type: str, should_include_self: bool): cat_dict = STAC_IO.read_json(href) - cat = STACObject.from_file(href) + cat = ps.Catalog.from_file(href) rels = set([link['rel'] for link in cat_dict['links']]) self.assertEqual('self' in rels, should_include_self) for child_link in cat.get_child_links(): - child_href = make_absolute_href(child_link.target, href) + child_href = make_absolute_href(child_link.href, href) validate_catalog_link_type(child_href, link_type, catalog_type == CatalogType.ABSOLUTE_PUBLISHED) for item_link in cat.get_item_links(): - item_href = make_absolute_href(item_link.target, href) + item_href = make_absolute_href(item_link.href, href) validate_item_link_type(item_href, link_type, catalog_type == CatalogType.ABSOLUTE_PUBLISHED) @@ -87,25 +86,25 @@ def validate_catalog_link_type(href, link_type, should_include_self): validate_catalog_link_type(root_href, link_type, root_should_include_href) - def do_test(self, catalog, catalog_type): + def do_test(self, catalog: ps.Catalog, catalog_type: ps.CatalogType): with TemporaryDirectory() as tmp_dir: catalog.normalize_hrefs(tmp_dir) self.validate_catalog(catalog) catalog.save(catalog_type=catalog_type) - root_href = catalog.get_self_href() + root_href = catalog.self_href self.validate_link_types(root_href, catalog_type) - for parent, children, items in catalog.walk(): + for parent, _, items in catalog.walk(): if issubclass(type(parent), Collection): - stac_object_type = STACObjectType.COLLECTION + stac_object_type = ps.STACObjectType.COLLECTION else: - stac_object_type = STACObjectType.CATALOG - self.validate_file(parent.get_self_href(), stac_object_type) + stac_object_type = ps.STACObjectType.CATALOG + self.validate_file(parent.self_href, stac_object_type) for item in items: - self.validate_file(item.get_self_href(), STACObjectType.ITEM) + self.validate_file(item.self_href, ps.STACObjectType.ITEM) def test_testcases(self): for catalog in TestCases.all_test_catalogs(): diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index b5b2591a8..3b3f8f2f3 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -1,10 +1,10 @@ import os from datetime import datetime import csv +from typing import Any, Dict, List -import pystac -from pystac import (Catalog, Item, Asset, Extent, TemporalExtent, SpatialExtent, MediaType, - Extensions) +from pystac import (Catalog, Collection, Item, Asset, Extent, TemporalExtent, SpatialExtent, + MediaType, Extensions) from pystac.extensions.label import (LabelOverview, LabelClasses, LabelCount) TEST_LABEL_CATALOG = { @@ -34,7 +34,7 @@ } } -RANDOM_GEOM = { +RANDOM_GEOM: Dict[str, Any] = { "type": "Polygon", "coordinates": [[[-2.5048828125, 3.8916575492899987], [-1.9610595703125, 3.8916575492899987], @@ -42,7 +42,7 @@ [-2.5048828125, 3.8916575492899987]]] } -RANDOM_BBOX = [ +RANDOM_BBOX: List[float] = [ RANDOM_GEOM['coordinates'][0][0][0], RANDOM_GEOM['coordinates'][0][0][1], RANDOM_GEOM['coordinates'][0][1][0], RANDOM_GEOM['coordinates'][0][1][1] ] @@ -53,11 +53,11 @@ class TestCases: @staticmethod - def get_path(rel_path): + def get_path(rel_path: str) -> str: return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', rel_path)) @staticmethod - def get_examples_info(): + def get_examples_info() -> List[Dict[str, Any]]: examples = [] info_path = TestCases.get_path('data-files/examples/example-info.csv') @@ -90,7 +90,7 @@ def get_examples_info(): return examples @staticmethod - def all_test_catalogs(): + def all_test_catalogs() -> List[Catalog]: return [ TestCases.test_case_1(), TestCases.test_case_2(), @@ -102,15 +102,15 @@ def all_test_catalogs(): ] @staticmethod - def test_case_1(): + def test_case_1() -> Catalog: return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-1/catalog.json')) @staticmethod - def test_case_2(): + def test_case_2() -> Catalog: return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-2/catalog.json')) @staticmethod - def test_case_3(): + def test_case_3() -> Catalog: root_cat = Catalog(id='test3', description='test case 3 catalog', title='test case 3 title') image_item = Item(id='imagery-item', @@ -175,8 +175,8 @@ def test_case_7(): TestCases.get_path('data-files/catalogs/label_catalog_0_8_1/catalog.json')) @staticmethod - def test_case_8(): + def test_case_8() -> Collection: """Planet disaster data example catalog, 1.0.0-beta.2""" - return pystac.read_file( + return Collection.from_file( TestCases.get_path('data-files/catalogs/' 'planet-example-1.0.0-beta.2/collection.json')) diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 8b11ae193..48a70bfaf 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -8,6 +8,8 @@ import jsonschema import pystac +import pystac.validation +from pystac.cache import CollectionCache from pystac.serialization.common_properties import merge_common_properties from pystac.validation import STACValidationError from tests.utils import TestCases @@ -31,35 +33,36 @@ def test_validate_current_version(self): def test_validate_examples(self): for example in TestCases.get_examples_info(): - stac_version = example['stac_version'] - path = example['path'] - valid = example['valid'] + with self.subTest(example['path']): + stac_version = example['stac_version'] + path = example['path'] + valid = example['valid'] - if stac_version < '0.8': - with open(path) as f: - stac_json = json.load(f) - - self.assertEqual(len(pystac.validation.validate_dict(stac_json)), 0) - else: - with self.subTest(path): + if stac_version < '0.8': with open(path) as f: stac_json = json.load(f) - # Check if common properties need to be merged - if stac_version < '1.0': - if example['object_type'] == pystac.STACObjectType.ITEM: - collection_cache = pystac.cache.CollectionCache() - merge_common_properties(stac_json, collection_cache, path) - - if valid: - pystac.validation.validate_dict(stac_json) - else: - with self.assertRaises(STACValidationError): - try: - pystac.validation.validate_dict(stac_json) - except STACValidationError as e: - self.assertIsInstance(e.source, jsonschema.ValidationError) - raise e + self.assertEqual(len(pystac.validation.validate_dict(stac_json)), 0) + else: + with self.subTest(path): + with open(path) as f: + stac_json = json.load(f) + + # Check if common properties need to be merged + if stac_version < '1.0': + if example['object_type'] == pystac.STACObjectType.ITEM: + collection_cache = CollectionCache() + merge_common_properties(stac_json, collection_cache, path) + + if valid: + pystac.validation.validate_dict(stac_json) + else: + with self.assertRaises(STACValidationError): + try: + pystac.validation.validate_dict(stac_json) + except STACValidationError as e: + self.assertIsInstance(e.source, jsonschema.ValidationError) + raise e def test_validate_error_contains_href(self): # Test that the exception message contains the HREF of the object if available. @@ -130,4 +133,5 @@ def test_validates_geojson_with_tuple_coordinates(self): datetime=datetime.utcnow(), properties={}) - self.assertIsNone(item.validate()) + # Should not raise. + item.validate() From c78f832a0462f27f17b420e93af7970c5f285e2d Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sun, 25 Apr 2021 01:46:48 -0400 Subject: [PATCH 04/51] Add mypy to test step; fixes based on mypy errors --- mypy.ini | 3 + pystac/__init__.py | 16 ++-- pystac/cache.py | 8 +- pystac/catalog.py | 87 +++++++++--------- pystac/collection.py | 68 ++++++++------ pystac/extensions/__init__.py | 2 +- pystac/extensions/eo.py | 13 +-- pystac/extensions/file.py | 17 ++-- pystac/extensions/label.py | 28 +++--- pystac/extensions/pointcloud.py | 19 ++-- pystac/extensions/projection.py | 8 +- pystac/extensions/sar.py | 9 +- pystac/extensions/sat.py | 15 ++-- pystac/extensions/scientific.py | 21 ++--- pystac/extensions/single_file_stac.py | 53 +++++------ pystac/extensions/timestamps.py | 10 +-- pystac/extensions/version.py | 103 +++++++++++----------- pystac/extensions/view.py | 14 +-- pystac/item.py | 30 ++++--- pystac/link.py | 36 ++++---- pystac/media_type.py | 2 +- pystac/serialization/common_properties.py | 11 ++- pystac/serialization/identify.py | 45 +++++----- pystac/serialization/migrate.py | 20 +++-- pystac/stac_io.py | 16 ++-- pystac/stac_object.py | 51 +++++------ pystac/utils.py | 2 +- pystac/validation/__init__.py | 20 +++-- pystac/validation/schema_uri_map.py | 14 +-- pystac/validation/stac_validator.py | 18 ++-- pystac/version.py | 2 +- requirements-dev.txt | 6 +- scripts/test | 3 + 33 files changed, 413 insertions(+), 357 deletions(-) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..132834a06 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +ignore_missing_imports = True +disallow_untyped_defs = True \ No newline at end of file diff --git a/pystac/__init__.py b/pystac/__init__.py index c4f4c54e4..a3ff580be 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -23,7 +23,7 @@ class STACTypeError(Exception): from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore -from pystac.stac_io import STAC_IO +from pystac.stac_io import STAC_IO # type:ignore from pystac.extensions import Extensions # type:ignore from pystac.stac_object import (STACObject, STACObjectType) # type:ignore from pystac.media_type import MediaType # type:ignore @@ -37,12 +37,8 @@ class STACTypeError(Exception): Provider) # type:ignore from pystac.item import (Item, Asset, CommonMetadata) # type:ignore -from pystac.serialization import stac_object_from_dict - import pystac.validation -STAC_IO.stac_object_from_dict = stac_object_from_dict - import pystac.extensions.base import pystac.extensions.eo import pystac.extensions.label @@ -92,7 +88,9 @@ def read_file(href: str) -> STACObject: return STACObject.from_file(href) -def write_file(obj: STACObject, include_self_link: bool = True, dest_href: Optional[str] = None): +def write_file(obj: STACObject, + include_self_link: bool = True, + dest_href: Optional[str] = None) -> None: """Writes a STACObject to a file. This will write only the Catalog, Collection or Item ``obj``. It will not attempt @@ -115,7 +113,9 @@ def write_file(obj: STACObject, include_self_link: bool = True, dest_href: Optio obj.save_object(include_self_link=include_self_link, dest_href=dest_href) -def read_dict(d: Dict[str, Any], href: Optional[str] = None, root: Optional[Catalog] = None): +def read_dict(d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None) -> STACObject: """Reads a STAC object from a dict representing the serialized JSON version of the STAC object. @@ -132,4 +132,4 @@ def read_dict(d: Dict[str, Any], href: Optional[str] = None, root: Optional[Cata If provided, the root's resolved object cache can be used to search for previously resolved instances of the STAC object. """ - return stac_object_from_dict(d, href, root) + return STAC_IO.stac_object_from_dict(d, href, root) diff --git a/pystac/cache.py b/pystac/cache.py index c8ea88277..acbcd340b 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -26,7 +26,7 @@ def get_cache_key(stac_object: "STACObject") -> Tuple[str, bool]: return (href, True) else: ids: List[str] = [] - obj = stac_object + obj: Optional[STACObject] = stac_object while obj is not None: ids.append(obj.id) obj = obj.get_parent() @@ -65,7 +65,7 @@ def __init__(self, self.hrefs_to_objects = hrefs_to_objects or {} self.ids_to_collections = ids_to_collections or {} - self._collection_cache = None + self._collection_cache: Optional[ResolvedObjectCollectionCache] = None def get_or_cache(self, obj: "STACObject") -> "STACObject": """Gets the STACObject that is the cached version of the given STACObject; or, if @@ -268,7 +268,9 @@ def contains_id(self, collection_id: str) -> bool: return (self.resolved_object_cache.contains_collection_id(collection_id) or super().contains_id(collection_id)) - def cache(self, collection: Dict[str, Any], href: Optional[str] = None) -> None: + def cache(self, + collection: Union["Collection", Dict[str, Any]], + href: Optional[str] = None) -> None: super().cache(collection, href) @staticmethod diff --git a/pystac/catalog.py b/pystac/catalog.py index ac91592d9..fe7e2099f 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -14,7 +14,7 @@ class CatalogType(str, Enum): - def __str__(self): + def __str__(self) -> str: return str(self.value) SELF_CONTAINED = 'SELF_CONTAINED' @@ -137,7 +137,7 @@ def __init__(self, if href is not None: self.set_self_href(href) - self.catalog_type = catalog_type + self.catalog_type: CatalogType = catalog_type self._resolved_objects.cache(self) @@ -156,7 +156,7 @@ def is_relative(self) -> bool: def add_child(self, child: "Catalog", title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None): + strategy: Optional[HrefLayoutStrategy] = None) -> None: """Adds a link to a child :class:`~pystac.Catalog` or :class:`~pystac.Collection`. This method will set the child's parent to this object, and its root to this Catalog's root. @@ -268,7 +268,7 @@ def get_children(self) -> Iterable["Catalog"]: """ return map(lambda x: cast(ps.Catalog, x), self.get_stac_objects('child')) - def get_child_links(self): + def get_child_links(self) -> List[Link]: """Return all child links of this catalog. Return: @@ -276,7 +276,7 @@ def get_child_links(self): """ return self.get_links('child') - def clear_children(self) -> "Catalog": + def clear_children(self) -> None: """Removes all children from this catalog. Return: @@ -285,7 +285,6 @@ def clear_children(self) -> "Catalog": child_ids = [child.id for child in self.get_children()] for child_id in child_ids: self.remove_child(child_id) - return self def remove_child(self, child_id: str) -> None: """Removes an child from this catalog. @@ -336,7 +335,7 @@ def get_items(self) -> Iterable["ItemType"]: """ return map(lambda x: cast(ps.Item, x), self.get_stac_objects('item')) - def clear_items(self) -> "Catalog": + def clear_items(self) -> None: """Removes all items from this catalog. Return: @@ -349,7 +348,6 @@ def clear_items(self) -> "Catalog": item.set_root(None) self.links = [link for link in self.links if link.rel != 'item'] - return self def remove_item(self, item_id: str) -> None: """Removes an item from this catalog. @@ -396,7 +394,7 @@ def get_item_links(self) -> List[Link]: def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: links = self.links if not include_self_link: - links = filter(lambda l: l.rel != 'self', links) + links = [x for x in links if x.rel != 'self'] d: Dict[str, Any] = { 'id': self.id, @@ -438,7 +436,7 @@ def clone(self) -> "Catalog": return clone - def make_all_asset_hrefs_relative(self): + def make_all_asset_hrefs_relative(self) -> None: """Makes all the HREFs of assets belonging to items in this catalog and all children to be relative, recursively. """ @@ -446,7 +444,7 @@ def make_all_asset_hrefs_relative(self): for item in items: item.make_asset_hrefs_relative() - def make_all_asset_hrefs_absolute(self): + def make_all_asset_hrefs_absolute(self) -> None: """Makes all the HREFs of assets belonging to items in this catalog and all children to be absolute, recursively. """ @@ -457,7 +455,7 @@ def make_all_asset_hrefs_absolute(self): def normalize_and_save(self, root_href: str, catalog_type: Optional[CatalogType] = None, - strategy: Optional[HrefLayoutStrategy] = None): + strategy: Optional[HrefLayoutStrategy] = None) -> None: """Normalizes link HREFs to the given root_href, and saves the catalog. This is a convenience method that simply calls :func:`Catalog.normalize_hrefs @@ -476,7 +474,9 @@ def normalize_and_save(self, self.normalize_hrefs(root_href, strategy=strategy) self.save(catalog_type) - def normalize_hrefs(self, root_href: str, strategy: Optional[HrefLayoutStrategy] = None): + def normalize_hrefs(self, + root_href: str, + strategy: Optional[HrefLayoutStrategy] = None) -> None: """Normalize HREFs will regenerate all link HREFs based on an absolute root_href and the canonical catalog layout as specified in the STAC specification's best practices. @@ -492,7 +492,9 @@ def normalize_hrefs(self, root_href: str, strategy: Optional[HrefLayoutStrategy] `STAC best practices document `_ for the canonical layout of a STAC. """ # noqa E501 if strategy is None: - strategy = BestPracticesLayoutStrategy() + _strategy: HrefLayoutStrategy = BestPracticesLayoutStrategy() + else: + _strategy = strategy # Normalizing requires an absolute path if not is_absolute_href(root_href): @@ -501,9 +503,9 @@ def normalize_hrefs(self, root_href: str, strategy: Optional[HrefLayoutStrategy] def process_item(item: "ItemType", _root_href: str) -> Callable[[], None]: item.resolve_links() - new_self_href = strategy.get_href(item, _root_href) + new_self_href = _strategy.get_href(item, _root_href) - def fn(): + def fn() -> None: item.set_self_href(new_self_href) return fn @@ -514,7 +516,7 @@ def process_catalog(cat: Catalog, _root_href: str, cat.resolve_links() - new_self_href = strategy.get_href(cat, _root_href, is_root) + new_self_href = _strategy.get_href(cat, _root_href, is_root) new_root = os.path.dirname(new_self_href) for item in cat.get_items(): @@ -523,7 +525,7 @@ def process_catalog(cat: Catalog, _root_href: str, for child in cat.get_children(): setter_funcs.extend(process_catalog(child, new_root, is_root=False)) - def fn(): + def fn() -> None: cat.set_self_href(new_self_href) setter_funcs.append(fn) @@ -538,12 +540,10 @@ def fn(): for fn in setter_funcs: fn() - return self - def generate_subcatalogs(self, template: str, defaults: Optional[Dict[str, Any]] = None, - parent_ids: List[str] = None, + parent_ids: Optional[List[str]] = None, **kwargs: Any) -> List["Catalog"]: """Walks through the catalog and generates subcatalogs for items based on the template string. See :class:`~pystac.layout.LayoutTemplate` @@ -582,8 +582,10 @@ def generate_subcatalogs(self, item = cast(ps.Item, link.target) 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())]): + if all([ + '{}'.format(id) == next(id_iter, None) + for id in reversed(list(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. @@ -602,9 +604,9 @@ def generate_subcatalogs(self, curr_parent = subcat # resolve collection link so when added back points to correct location - link = item.get_single_link('collection') - if link is not None: - link.resolve_stac_object() + col_link = item.get_single_link('collection') + if col_link is not None: + col_link.resolve_stac_object() curr_parent.add_item(item) @@ -613,7 +615,7 @@ def generate_subcatalogs(self, return result - def save(self, catalog_type: CatalogType = None) -> None: + def save(self, catalog_type: Optional[CatalogType] = None) -> None: """Save this catalog and all it's children/item to files determined by the object's self link HREF. @@ -652,14 +654,16 @@ def save(self, catalog_type: CatalogType = None) -> None: include_self_link = False # include a self link if this is the root catalog or if ABSOLUTE_PUBLISHED catalog - if ((self.get_self_href() == self.get_root_link().get_absolute_href() - and root.catalog_type != CatalogType.SELF_CONTAINED) - or root.catalog_type == CatalogType.ABSOLUTE_PUBLISHED): + if root.catalog_type == CatalogType.ABSOLUTE_PUBLISHED: include_self_link = True + elif root.catalog_type != CatalogType.SELF_CONTAINED: + root_link = self.get_root_link() + if root_link and root_link.get_absolute_href() == self.get_self_href(): + include_self_link = True self.save_object(include_self_link=include_self_link) - - self.catalog_type = catalog_type + if catalog_type is not None: + self.catalog_type = catalog_type def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["ItemType"]]]: """Walks through children and items of catalogs. @@ -702,7 +706,9 @@ def validate_all(self) -> None: def _object_links(self) -> List[str]: return ['child', 'item'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) - def map_items(self, item_mapper: Callable[["ItemType"], Union["ItemType", List["ItemType"]]]): + def map_items( + self, item_mapper: Callable[["ItemType"], Union["ItemType", + List["ItemType"]]]) -> "Catalog": """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -718,7 +724,7 @@ def map_items(self, item_mapper: Callable[["ItemType"], Union["ItemType", List[" new_cat = cast(Catalog, self.full_copy()) - def process_catalog(catalog: Catalog): + def process_catalog(catalog: Catalog) -> None: for child in catalog.get_children(): process_catalog(child) @@ -742,9 +748,10 @@ def process_catalog(catalog: Catalog): process_catalog(new_cat) return new_cat - def map_assets(self, asset_mapper: Callable[[str, "AssetType"], - Union["AssetType", Tuple[str, "AssetType"], - Dict[str, "AssetType"]]]): + def map_assets( + self, asset_mapper: Callable[[str, "AssetType"], Union["AssetType", Tuple[str, "AssetType"], + Dict[str, "AssetType"]]] + ) -> "Catalog": """Creates a copy of a catalog, with each Asset for each Item passed through the asset_mapper function. @@ -758,7 +765,7 @@ def map_assets(self, asset_mapper: Callable[[str, "AssetType"], Catalog: A full copy of this catalog, with assets manipulated according to the asset_mapper function. """ - def apply_asset_mapper(tup: Tuple[str, "AssetType"]): + def apply_asset_mapper(tup: Tuple[str, "AssetType"]) -> List[Tuple[str, ps.Asset]]: k, v = tup result = asset_mapper(k, v) if result is None: @@ -773,7 +780,7 @@ def apply_asset_mapper(tup: Tuple[str, "AssetType"]): raise Exception('asset_mapper must return a non-empty list') return assets - def item_mapper(item: ps.Item): + def item_mapper(item: ps.Item) -> ps.Item: new_assets = [ x for result in map(apply_asset_mapper, item.assets.items()) for x in result ] @@ -782,7 +789,7 @@ def item_mapper(item: ps.Item): return self.map_items(item_mapper) - def describe(self, include_hrefs: bool = False, _indent: int = 0): + def describe(self, include_hrefs: bool = False, _indent: int = 0) -> None: """Prints out information about this Catalog and all contained STACObjects. diff --git a/pystac/collection.py b/pystac/collection.py index 2f8fbf7a0..ba9c6e3a2 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -8,6 +8,7 @@ import pystac as ps from pystac import (STACObjectType, CatalogType) from pystac.catalog import Catalog +from pystac.layout import HrefLayoutStrategy from pystac.link import Link from pystac.utils import datetime_to_str @@ -36,7 +37,7 @@ def __init__(self, bboxes: Union[List[List[float]], List[float]]) -> None: if isinstance(bboxes, list) and isinstance(bboxes[0], float): self.bboxes: List[List[float]] = [cast(List[float], bboxes)] else: - self.bboxes: List[List[float]] = cast(List[List[float]], bboxes) + self.bboxes = cast(List[List[float]], bboxes) def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this SpatialExtent. @@ -147,10 +148,10 @@ def to_dict(self) -> Dict[str, Any]: start = None end = None - if i[0]: + if i[0] is not None: start = datetime_to_str(i[0]) - if i[1]: + if i[1] is not None: end = datetime_to_str(i[1]) encoded_intervals.append([start, end]) @@ -266,30 +267,40 @@ def from_items(items: Iterable["ItemType"]) -> "Extent": Extent: An Extent that spatially and temporally covers all of the given items. """ - def extract_extent_props(item: ps.Item) -> List[Any]: - return item.bbox + [ - item.datetime, item.common_metadata.start_datetime, - item.common_metadata.end_datetime - ] # type:ignore - - xmins, ymins, xmaxs, ymaxs, datetimes, starts, ends = zip(*map(extract_extent_props, items)) + bounds_values: List[List[float]] = [[float('inf')], [float('inf')], [float('-inf')], + [float('-inf')]] + datetimes: List[Datetime] = [] + starts: List[Datetime] = [] + ends: List[Datetime] = [] + + for item in items: + if item.bbox is not None: + for i in range(0, 4): + bounds_values[i].append(item.bbox[i]) + if item.datetime is not None: + datetimes.append(item.datetime) + if item.common_metadata.start_datetime is not None: + starts.append(item.common_metadata.start_datetime) + if item.common_metadata.end_datetime is not None: + ends.append(item.common_metadata.end_datetime) if not any(datetimes + starts): start_timestamp = None else: - start_timestamp = min([ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in filter(None, datetimes + starts) - ]) + start_timestamp = min( + [dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) for dt in datetimes + starts]) if not any(datetimes + ends): end_timestamp = None else: - end_timestamp = max([ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in filter(None, datetimes + ends) - ]) - - spatial = SpatialExtent([[min(xmins), min(ymins), max(xmaxs), max(ymaxs)]]) + end_timestamp = max( + [dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) for dt in datetimes + ends]) + + spatial = SpatialExtent([[ + min(bounds_values[0]), + min(bounds_values[1]), + max(bounds_values[2]), + max(bounds_values[3]) + ]]) temporal = TemporalExtent([[start_timestamp, end_timestamp]]) return Extent(spatial, temporal) @@ -424,8 +435,8 @@ def __init__(self, keywords: Optional[List[str]] = None, providers: Optional[List[Provider]] = None, summaries: Optional[Dict[str, Any]] = None): - super(Collection, self).__init__(id, description, title, stac_extensions, extra_fields, - href, catalog_type or CatalogType.ABSOLUTE_PUBLISHED) + super().__init__(id, description, title, stac_extensions, extra_fields, href, catalog_type + or CatalogType.ABSOLUTE_PUBLISHED) self.extent = extent self.license = license @@ -437,12 +448,15 @@ def __init__(self, def __repr__(self) -> str: return ''.format(self.id) - def add_item(self, item: "ItemType", title: Optional[str] = None) -> None: - super(Collection, self).add_item(item, title) + def add_item(self, + item: "ItemType", + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None) -> None: + super().add_item(item, title, strategy) item.set_collection(self) def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: - d = super(Collection, self).to_dict(include_self_link) + d = super().to_dict(include_self_link) d['extent'] = self.extent.to_dict() d['license'] = self.license if self.stac_extensions is not None: @@ -456,7 +470,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: return d - def clone(self): + def clone(self) -> "Collection": clone = Collection(id=self.id, description=self.description, extent=self.extent.clone(), @@ -537,7 +551,7 @@ def from_dict(cls, return collection - def update_extent_from_items(self): + def update_extent_from_items(self) -> None: """ Update datetime and bbox based on all items to a single bbox and time window. """ diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index 1f944a7d6..d8911d50f 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -10,7 +10,7 @@ class ExtensionError(Exception): class Extensions(str, Enum): """Enumerates the IDs of common extensions.""" - def __str__(self): + def __str__(self) -> str: return str(self.value) CHECKSUM = 'checksum' diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index c31fc55b5..9ea49b08f 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -17,7 +17,7 @@ def apply(self, common_name: Optional[str] = None, description: Optional[str] = None, center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None): + full_width_half_max: Optional[float] = None) -> None: """ Sets the properties for this Band. @@ -43,7 +43,7 @@ def create(cls, common_name: Optional[str] = None, description: Optional[str] = None, center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None): + full_width_half_max: Optional[float] = None) -> "Band": """ Creates a new band. @@ -202,8 +202,8 @@ def band_description(common_name: str) -> Optional[str]: """ # noqa E501 r = Band.band_range(common_name) if r is not None: - r = "Common name: {}, Range: {} to {}".format(common_name, r[0], r[1]) - return r + return "Common name: {}, Range: {} to {}".format(common_name, r[0], r[1]) + return None class EOItemExt(ItemExtension): @@ -228,7 +228,7 @@ def __init__(self, item: Item) -> None: self.item = item - def apply(self, bands: List[Band], cloud_cover: Optional[float] = None): + def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: """Applies label extension properties to the extended Item. Args: @@ -340,4 +340,5 @@ def from_item(cls, item: Item) -> "EOItemExt": return cls(item) -EO_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.EO, [ExtendedObject(Item, EOItemExt)]) +EO_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.EO, [ExtendedObject(Item, EOItemExt)]) diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index c86861035..fbb5fb30d 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -51,7 +51,7 @@ def apply(self, data_type: Optional[FileDataType] = None, size: Optional[int] = None, nodata: Optional[List[Any]] = None, - checksum: Optional[str] = None): + checksum: Optional[str] = None) -> None: """Applies file extension properties to the extended Item. Args: @@ -66,13 +66,6 @@ def apply(self, self.nodata = nodata self.checksum = checksum - def _set_property(self, key: str, value: Any, asset: Optional[Asset]) -> None: - target = self.item.properties if asset is None else asset.properties - if value is None: - target.pop(key, None) - else: - target[key] = value - @property def data_type(self) -> Optional[FileDataType]: """Get or sets the data_type of the file. @@ -102,6 +95,7 @@ def get_data_type(self, asset: Optional[Asset] = None) -> Optional[FileDataType] if data_type is not None: return FileDataType(data_type) + return None def set_data_type(self, data_type: Optional[FileDataType], @@ -111,7 +105,8 @@ def set_data_type(self, If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ - self._set_property('file:data_type', data_type.value, asset) + v = None if data_type is None else data_type.value + self._set_property('file:data_type', v, asset) @property def size(self) -> Optional[int]: @@ -223,5 +218,5 @@ def from_item(cls, item: Item) -> "FileItemExt": return cls(item) -FILE_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.FILE, - [ExtendedObject(Item, FileItemExt)]) +FILE_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.FILE, [ExtendedObject(Item, FileItemExt)]) diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 92d33b123..d1902610a 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -13,7 +13,7 @@ class LabelType(str, Enum): """Enumerates valid label types (RASTER or VECTOR).""" - def __str__(self): + def __str__(self) -> str: return str(self.value) VECTOR = 'vector' @@ -31,7 +31,9 @@ class LabelClasses: def __init__(self, properties: Dict[str, Any]): self.properties = properties - def apply(self, classes: Union[List[str], List[int], List[float]], name: Optional[str] = None): + def apply(self, + classes: Union[List[str], List[int], List[float]], + name: Optional[str] = None) -> None: """Sets the properties for this LabelClasses. Args: @@ -113,7 +115,7 @@ class LabelCount: def __init__(self, properties: Dict[str, Any]): self.properties = properties - def apply(self, name: str, count: int): + def apply(self, name: str, count: int) -> None: """Sets the properties for this LabelCount. Args: @@ -164,7 +166,7 @@ def count(self) -> int: return result @count.setter - def count(self, v: int): + def count(self, v: int) -> None: self.properties['count'] = v def to_dict(self) -> Dict[str, Any]: @@ -259,7 +261,7 @@ def __init__(self, properties: Dict[str, Any]): def apply(self, property_key: Optional[str], counts: Optional[List[LabelCount]] = None, - statistics: Optional[List[LabelStatistics]] = None): + statistics: Optional[List[LabelStatistics]] = None) -> None: """Sets the properties for this LabelOverview. Either ``counts`` or ``statistics``, or both, can be placed in an overview; @@ -380,7 +382,7 @@ def merge_counts(self, other: "LabelOverview") -> "LabelOverview": else: count_by_prop: Dict[str, int] = {} - def add_counts(counts: List[LabelCount]): + def add_counts(counts: List[LabelCount]) -> None: for c in counts: if c.name not in count_by_prop: count_by_prop[c.name] = c.count @@ -435,7 +437,7 @@ def apply(self, label_classes: Optional[List[LabelClasses]] = None, label_tasks: Optional[List[str]] = None, label_methods: Optional[List[str]] = None, - label_overviews: Optional[List[LabelOverview]] = None): + label_overviews: Optional[List[LabelOverview]] = None) -> None: """Applies label extension properties to the extended Item. Args: @@ -493,7 +495,7 @@ def label_type(self) -> LabelType: return LabelType(result) @label_type.setter - def label_type(self, v: LabelType): + def label_type(self, v: LabelType) -> None: if v not in LabelType.ALL: raise STACError("label_type must be one of " "{}. Invalid input: {}".format(LabelType.ALL, v)) @@ -621,7 +623,7 @@ def __repr__(self) -> str: def add_source(self, source_item: Item, title: Optional[str] = None, - assets: Optional[List[str]] = None): + assets: Optional[List[str]] = None) -> None: """Adds a link to a source item. Args: @@ -654,7 +656,7 @@ def add_labels(self, href: str, title: Optional[str] = None, media_type: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None): + properties: Optional[Dict[str, Any]] = None) -> None: """Adds a label asset to this LabelItem. Args: @@ -673,7 +675,7 @@ def add_labels(self, def add_geojson_labels(self, href: str, title: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None): + properties: Optional[Dict[str, Any]] = None) -> None: """Adds a GeoJSON label asset to this LabelItem. Args: @@ -694,5 +696,5 @@ def from_item(cls, item: Item) -> "LabelItemExt": return cls(item) -LABEL_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.LABEL, - [ExtendedObject(Item, LabelItemExt)]) +LABEL_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.LABEL, [ExtendedObject(Item, LabelItemExt)]) diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index c173281bd..5778149fd 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -119,7 +119,7 @@ def apply(self, maximum: Optional[float] = None, minimum: Optional[float] = None, stddev: Optional[float] = None, - variance: Optional[float] = None): + variance: Optional[float] = None) -> None: """Sets the properties for this PointcloudStatistic. Args: @@ -150,7 +150,7 @@ def create(cls, maximum: Optional[float] = None, minimum: Optional[float] = None, stddev: Optional[float] = None, - variance: Optional[float] = None): + variance: Optional[float] = None) -> "PointcloudStatistic": """Creates a new PointcloudStatistic class. Args: @@ -346,7 +346,7 @@ def apply(self, schemas: List[PointcloudSchema], density: Optional[float] = None, statistics: Optional[List[PointcloudStatistic]] = None, - epsg: Optional[int] = None): # TODO: Remove epsg per spec + epsg: Optional[int] = None) -> None: # TODO: Remove epsg per spec """Applies Pointcloud extension properties to the extended Item. Args: @@ -402,7 +402,7 @@ def get_count(self, asset: Optional[Asset] = None) -> int: return result - def set_count(self, count: int, asset: Optional[Asset] = None): + def set_count(self, count: int, asset: Optional[Asset] = None) -> None: """Set an Item or an Asset count. If an Asset is supplied, sets the property on the Asset. @@ -524,7 +524,10 @@ def get_schemas(self, asset: Optional[Asset] = None) -> List[PointcloudSchema]: else: schemas = asset.properties.get('pc:schemas') - return [PointcloudSchema(s) for s in schemas] + if schemas is None: + return [] + else: + return [PointcloudSchema(s) for s in schemas] def set_schemas(self, schemas: List[PointcloudSchema], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset schema @@ -585,7 +588,7 @@ def statistics(self) -> Optional[List[PointcloudStatistic]]: return self.get_statistics() @statistics.setter - def statistics(self, v: Optional[List[PointcloudStatistic]]): + def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None: self.set_statistics(v) def get_statistics(self, asset: Optional[Asset] = None) -> Optional[List[PointcloudStatistic]]: @@ -628,5 +631,5 @@ def from_item(cls, item: Item) -> "PointcloudItemExt": return cls(item) -POINTCLOUD_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.POINTCLOUD, - [ExtendedObject(Item, PointcloudItemExt)]) +POINTCLOUD_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.POINTCLOUD, [ExtendedObject(Item, PointcloudItemExt)]) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 39e28af83..3d6285f0f 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -34,7 +34,7 @@ def apply(self, bbox: Optional[List[float]] = None, centroid: Optional[Dict[str, float]] = None, shape: Optional[List[int]] = None, - transform: Optional[List[float]] = None): + transform: Optional[List[float]] = None) -> None: """Applies Projection extension properties to the extended Item. Args: @@ -139,7 +139,7 @@ def get_wkt2(self, asset: Optional[Asset] = None) -> Optional[str]: else: return asset.properties.get('proj:wkt2') - def set_wkt2(self, wkt2: Optional[str], asset: Optional[Asset] = None): + def set_wkt2(self, wkt2: Optional[str], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset wkt2. If an Asset is supplied, sets the property on the Asset. @@ -418,5 +418,5 @@ def from_item(cls, item: Item) -> "ProjectionItemExt": return cls(item) -PROJECTION_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.PROJECTION, - [ExtendedObject(Item, ProjectionItemExt)]) +PROJECTION_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.PROJECTION, [ExtendedObject(Item, ProjectionItemExt)]) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 97f81b297..35d61f97c 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -83,7 +83,7 @@ def apply(self, looks_range: Optional[int] = None, looks_azimuth: Optional[int] = None, looks_equivalent_number: Optional[float] = None, - observation_direction: Optional[ObservationDirection] = None): + observation_direction: Optional[ObservationDirection] = None) -> None: """Applies sar extension properties to the extended Item. Args: @@ -337,6 +337,7 @@ def observation_direction(self, v: ObservationDirection) -> None: self.item.properties[OBSERVATION_DIRECTION] = v.value -SAR_EXTENSION_DEFINITION = base.ExtensionDefinition(Extensions.SAR, [ - base.ExtendedObject(pystac.Item, SarItemExt), -]) +SAR_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( + Extensions.SAR, [ + base.ExtendedObject(pystac.Item, SarItemExt), + ]) diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 84faf14f9..1b670f40b 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -38,7 +38,9 @@ class SatItemExt(base.ItemExtension): def __init__(self, an_item: pystac.Item) -> None: self.item = an_item - def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Optional[int] = None): + def apply(self, + orbit_state: Optional[OrbitState] = None, + relative_orbit: Optional[int] = None) -> None: """Applies ext extension properties to the extended Item. Must specify at least one of orbit_state or relative_orbit. @@ -57,7 +59,7 @@ def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Option self.relative_orbit = relative_orbit @classmethod - def from_item(cls, an_item: pystac.Item): + def from_item(cls, an_item: pystac.Item) -> "SatItemExt": return cls(an_item) @classmethod @@ -72,7 +74,7 @@ def orbit_state(self) -> Optional[OrbitState]: OrbitState or None """ if ORBIT_STATE not in self.item.properties: - return + return None return OrbitState(self.item.properties[ORBIT_STATE]) @orbit_state.setter @@ -108,6 +110,7 @@ def relative_orbit(self, v: int) -> None: self.item.properties[RELATIVE_ORBIT] = v -SAT_EXTENSION_DEFINITION = base.ExtensionDefinition(Extensions.SAT, [ - base.ExtendedObject(pystac.Item, SatItemExt), -]) +SAT_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( + Extensions.SAT, [ + base.ExtendedObject(pystac.Item, SatItemExt), + ]) diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index ba044ef22..52c740fb6 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -50,14 +50,14 @@ def to_dict(self) -> Dict[str, str]: return copy.deepcopy({'doi': self.doi, 'citation': self.citation}) @staticmethod - def from_dict(d: Dict[str, str]): + def from_dict(d: Dict[str, str]) -> "Publication": return Publication(d['doi'], d['citation']) def get_link(self) -> pystac.Link: return pystac.Link(CITE_AS, doi_to_url(self.doi)) -def remove_link(links: List[pystac.Link], doi: str): +def remove_link(links: List[pystac.Link], doi: str) -> None: url = doi_to_url(doi) for i, a_link in enumerate(links): if a_link.rel != CITE_AS: @@ -105,7 +105,7 @@ def apply(self, self.publications = publications @classmethod - def from_item(cls, an_item: pystac.Item): + def from_item(cls, an_item: pystac.Item) -> "ScientificItemExt": return cls(an_item) @classmethod @@ -218,7 +218,7 @@ def __init__(self, a_collection: pystac.Collection): def apply(self, doi: Optional[str] = None, citation: Optional[str] = None, - publications: Optional[List[Publication]] = None): + publications: Optional[List[Publication]] = None) -> None: """Applies scientific extension properties to the extended Collection. Args: @@ -235,7 +235,7 @@ def apply(self, self.publications = publications @classmethod - def from_collection(cls, a_collection: pystac.Collection): + def from_collection(cls, a_collection: pystac.Collection) -> "ScientificCollectionExt": return cls(a_collection) @classmethod @@ -309,7 +309,7 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: return if not publication: - for one_pub in self.publications: + for one_pub in self.publications or []: remove_link(self.collection.links, one_pub.doi) del self.collection.extra_fields[PUBLICATIONS] @@ -324,7 +324,8 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: del self.collection.extra_fields[PUBLICATIONS] -SCIENTIFIC_EXTENSION_DEFINITION = base.ExtensionDefinition(Extensions.SCIENTIFIC, [ - base.ExtendedObject(pystac.Item, ScientificItemExt), - base.ExtendedObject(pystac.Collection, ScientificCollectionExt) -]) +SCIENTIFIC_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( + Extensions.SCIENTIFIC, [ + base.ExtendedObject(pystac.Item, ScientificItemExt), + base.ExtendedObject(pystac.Collection, ScientificCollectionExt) + ]) diff --git a/pystac/extensions/single_file_stac.py b/pystac/extensions/single_file_stac.py index ecc211a2a..d3f919d5f 100644 --- a/pystac/extensions/single_file_stac.py +++ b/pystac/extensions/single_file_stac.py @@ -1,13 +1,11 @@ from pystac.item import Item -from typing import List, Optional, cast -from pystac.catalog import Catalog +from typing import Dict, List, Optional, cast -from pystac import (STACError, Extensions) -from pystac.collection import Collection +import pystac as ps from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) -def create_single_file_stac(catalog: Catalog): +def create_single_file_stac(catalog: ps.Catalog) -> ps.Catalog: """Creates a Single File STAC from a STAC catalog. This method will recursively collect any collections and items in the catalog @@ -21,10 +19,10 @@ def create_single_file_stac(catalog: Catalog): Args: catalog (Catalog): Catalog to walk while constructing the Single File STAC """ - collections = {} - items = [] + collections: Dict[str, ps.Collection] = {} + items: List[ps.Item] = [] for root, _, cat_items in catalog.walk(): - if issubclass(type(root), Collection): + if isinstance(root, ps.Collection): new_collection = root.clone() new_collection.clear_links() collections[root.id] = new_collection @@ -33,16 +31,16 @@ def create_single_file_stac(catalog: Catalog): new_item.clear_links() items.append(new_item) - filtered_collections = [] + filtered_collections: List[ps.Collection] = [] for item in items: - if item.collection_id in collections: + if item.collection_id is not None and item.collection_id in collections: filtered_collections.append(collections[item.collection_id]) collections.pop(item.collection_id) result = catalog.clone() result.clear_links() - result.ext.enable(Extensions.SINGLE_FILE_STAC) - sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[Extensions.SINGLE_FILE_STAC]) + result.ext.enable(ps.Extensions.SINGLE_FILE_STAC) + sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[ps.Extensions.SINGLE_FILE_STAC]) sfs_ext.apply(features=items, collections=filtered_collections) return result @@ -64,15 +62,17 @@ class SingleFileSTACCatalogExt(CatalogExtension): Using SingleFileSTACCatalogExt to directly wrap a Catalog will add the 'proj' extension ID to the catalog's stac_extensions. """ - def __init__(self, catalog: Catalog): + def __init__(self, catalog: ps.Catalog) -> None: if catalog.stac_extensions is None: - catalog.stac_extensions = [str(Extensions.SINGLE_FILE_STAC)] - elif str(Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: - catalog.stac_extensions.append(str(Extensions.SINGLE_FILE_STAC)) + catalog.stac_extensions = [str(ps.Extensions.SINGLE_FILE_STAC)] + elif str(ps.Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: + catalog.stac_extensions.append(str(ps.Extensions.SINGLE_FILE_STAC)) self.catalog = catalog - def apply(self, features: List[Item], collections: Optional[List[Collection]] = None): + def apply(self, + features: List[Item], + collections: Optional[List[ps.Collection]] = None) -> None: """ Args: features (List[Item]): List of items contained by @@ -84,9 +84,10 @@ def apply(self, features: List[Item], collections: Optional[List[Collection]] = self.collections = collections @classmethod - def enable_extension(cls, catalog: Catalog): + def enable_extension(cls, stac_object: ps.STACObject) -> None: # Ensure the 'type' property is correct so that the Catalog is valid GeoJSON. - catalog.extra_fields['type'] = 'FeatureCollection' + if isinstance(stac_object, ps.Catalog): + stac_object.extra_fields['type'] = 'FeatureCollection' @property def features(self) -> List[Item]: @@ -97,7 +98,7 @@ def features(self) -> List[Item]: """ features = self.catalog.extra_fields.get('features') if features is None: - raise STACError('Invalid Single File STAC: does not have "features" property.') + raise ps.STACError('Invalid Single File STAC: does not have "features" property.') return [Item.from_dict(feature) for feature in features] @@ -106,7 +107,7 @@ def features(self, v: List[Item]) -> None: self.catalog.extra_fields['features'] = [item.to_dict() for item in v] @property - def collections(self) -> Optional[List[Collection]]: + def collections(self) -> Optional[List[ps.Collection]]: """Get or sets a list of :class:`~pystac.Collection` objects contained in this Single File STAC. """ @@ -115,10 +116,10 @@ def collections(self) -> Optional[List[Collection]]: if collections is None: return None else: - return [Collection.from_dict(col) for col in collections] + return [ps.Collection.from_dict(col) for col in collections] @collections.setter - def collections(self, v: Optional[List[Collection]]) -> None: + def collections(self, v: Optional[List[ps.Collection]]) -> None: if v is not None: self.catalog.extra_fields['collections'] = [col.to_dict() for col in v] else: @@ -129,9 +130,9 @@ def _object_links(cls) -> List[str]: return [] @classmethod - def from_catalog(cls, catalog: Catalog): + def from_catalog(cls, catalog: ps.Catalog) -> "SingleFileSTACCatalogExt": return SingleFileSTACCatalogExt(catalog) -SFS_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.SINGLE_FILE_STAC, - [ExtendedObject(Catalog, SingleFileSTACCatalogExt)]) +SFS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + ps.Extensions.SINGLE_FILE_STAC, [ExtendedObject(ps.Catalog, SingleFileSTACCatalogExt)]) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 4801029c4..095bfab8f 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -41,7 +41,7 @@ def _object_links(cls) -> List[str]: def apply(self, published: Optional[Datetime] = None, expires: Optional[Datetime] = None, - unpublished: Optional[Datetime] = None): + unpublished: Optional[Datetime] = None) -> None: """Applies timestamps extension properties to the extended Item. Args: @@ -74,9 +74,9 @@ def _timestamp_getter(self, key: str, asset: Optional[Asset] = None) -> Optional def _timestamp_setter(self, timestamp: Optional[Datetime], key: str, - asset: Optional[Asset] = None): + asset: Optional[Asset] = None) -> None: if timestamp is not None: - value = datetime_to_str(timestamp) + value: Optional[str] = datetime_to_str(timestamp) else: value = None self._set_property(key, value, asset) @@ -197,5 +197,5 @@ def set_unpublished(self, self._timestamp_setter(unpublished, 'unpublished', asset) -TIMESTAMPS_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.TIMESTAMPS, - [ExtendedObject(Item, TimestampsItemExt)]) +TIMESTAMPS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.TIMESTAMPS, [ExtendedObject(Item, TimestampsItemExt)]) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index ebb89ddf0..cf95ac9bb 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -7,7 +7,7 @@ from typing import List, Optional, cast -import pystac +import pystac as ps from pystac import Extensions, STACError from pystac.extensions import base @@ -39,17 +39,17 @@ class VersionItemExt(base.ItemExtension): Using VersionItemExt to directly wrap an item will add the 'version' extension ID to the item's stac_extensions. """ - item: pystac.Item + item: ps.Item - def __init__(self, an_item) -> None: + def __init__(self, an_item: ps.Item) -> None: self.item = an_item def apply(self, version: str, deprecated: Optional[bool] = None, - latest: Optional[pystac.Item] = None, - predecessor: Optional[pystac.Item] = None, - successor: Optional[pystac.Item] = None) -> None: + latest: Optional[ps.Item] = None, + predecessor: Optional[ps.Item] = None, + successor: Optional[ps.Item] = None) -> None: """Applies version extension properties to the extended Item. Args: @@ -99,65 +99,65 @@ def deprecated(self) -> bool: @deprecated.setter def deprecated(self, v: bool) -> None: if not isinstance(v, bool): - raise pystac.STACError(DEPRECATED + ' must be a bool') + raise ps.STACError(DEPRECATED + ' must be a bool') self.item.properties[DEPRECATED] = v @property - def latest(self) -> Optional[pystac.Item]: + def latest(self) -> Optional[ps.Item]: """Get or sets the most recent item. Returns: Item or None """ - result = next(self.item.get_stac_objects(LATEST), None) + result = next(iter(self.item.get_stac_objects(LATEST)), None) if result is None: return None - return cast(pystac.Item, result) + return cast(ps.Item, result) @latest.setter - def latest(self, source_item: pystac.Item) -> None: + def latest(self, source_item: ps.Item) -> None: self.item.clear_links(LATEST) if source_item: - self.item.add_link(pystac.Link(LATEST, source_item, MEDIA_TYPE)) + self.item.add_link(ps.Link(LATEST, source_item, MEDIA_TYPE)) @property - def predecessor(self) -> Optional[pystac.Item]: + def predecessor(self) -> Optional[ps.Item]: """Get or sets the previous item. Returns: Item or None """ - result = next(self.item.get_stac_objects(PREDECESSOR), None) + result = next(iter(self.item.get_stac_objects(PREDECESSOR)), None) if result is None: return None - return cast(pystac.Item, result) + return cast(ps.Item, result) @predecessor.setter - def predecessor(self, source_item: pystac.Item) -> None: + def predecessor(self, source_item: ps.Item) -> None: self.item.clear_links(PREDECESSOR) if source_item: - self.item.add_link(pystac.Link(PREDECESSOR, source_item, MEDIA_TYPE)) + self.item.add_link(ps.Link(PREDECESSOR, source_item, MEDIA_TYPE)) @property - def successor(self) -> Optional[pystac.Item]: + def successor(self) -> Optional[ps.Item]: """Get or sets the next item. Returns: Item or None """ - result = next(self.item.get_stac_objects(SUCCESSOR), None) + result = next(iter(self.item.get_stac_objects(SUCCESSOR)), None) if result is None: return None - return cast(pystac.Item, result) + return cast(ps.Item, result) @successor.setter - def successor(self, source_item: pystac.Item) -> None: + def successor(self, source_item: ps.Item) -> None: self.item.clear_links(SUCCESSOR) if source_item: - self.item.add_link(pystac.Link(SUCCESSOR, source_item, MEDIA_TYPE)) + self.item.add_link(ps.Link(SUCCESSOR, source_item, MEDIA_TYPE)) @classmethod - def from_item(cls, an_item: pystac.Item): + def from_item(cls, an_item: ps.Item) -> "VersionItemExt": return cls(an_item) @classmethod @@ -179,9 +179,9 @@ class VersionCollectionExt(base.CollectionExtension): Using VersionCollectionExt to directly wrap a collection will add the 'version' extension ID to the collections's stac_extensions. """ - collection: pystac.Collection + collection: ps.Collection - def __init__(self, a_collection) -> None: + def __init__(self, a_collection: ps.Collection) -> None: self.collection = a_collection @property @@ -210,67 +210,67 @@ def deprecated(self) -> bool: return bool(self.collection.extra_fields.get(DEPRECATED)) @deprecated.setter - def deprecated(self, v) -> None: + def deprecated(self, v: bool) -> None: if not isinstance(v, bool): - raise pystac.STACError(DEPRECATED + ' must be a bool') + raise ps.STACError(DEPRECATED + ' must be a bool') self.collection.extra_fields[DEPRECATED] = v @property - def latest(self) -> Optional[pystac.Collection]: + def latest(self) -> Optional[ps.Collection]: """Get or sets the most recent collection. Returns: Collection or None """ - result = next(self.collection.get_stac_objects(LATEST), None) + result = next(iter(self.collection.get_stac_objects(LATEST)), None) if result is None: return None - return cast(pystac.Collection, result) + return cast(ps.Collection, result) @latest.setter - def latest(self, source_collection: pystac.Collection) -> None: + def latest(self, source_collection: ps.Collection) -> None: self.collection.clear_links(LATEST) if source_collection: - self.collection.add_link(pystac.Link(LATEST, source_collection, MEDIA_TYPE)) + self.collection.add_link(ps.Link(LATEST, source_collection, MEDIA_TYPE)) @property - def predecessor(self) -> Optional[pystac.Collection]: + def predecessor(self) -> Optional[ps.Collection]: """Get or sets the previous collection. Returns: Collection or None """ - result = next(self.collection.get_stac_objects(PREDECESSOR), None) + result = next(iter(self.collection.get_stac_objects(PREDECESSOR)), None) if result is None: return None - return cast(pystac.Collection, result) + return cast(ps.Collection, result) @predecessor.setter - def predecessor(self, source_collection: pystac.Collection) -> None: + def predecessor(self, source_collection: ps.Collection) -> None: self.collection.clear_links(PREDECESSOR) if source_collection: - self.collection.add_link(pystac.Link(PREDECESSOR, source_collection, MEDIA_TYPE)) + self.collection.add_link(ps.Link(PREDECESSOR, source_collection, MEDIA_TYPE)) @property - def successor(self) -> Optional[pystac.Collection]: + def successor(self) -> Optional[ps.Collection]: """Get or sets the next collection. Returns: Collection or None """ - result = next(self.collection.get_stac_objects(SUCCESSOR), None) + result = next(iter(self.collection.get_stac_objects(SUCCESSOR)), None) if result is None: return None - return cast(pystac.Collection, result) + return cast(ps.Collection, result) @successor.setter - def successor(self, source_collection: pystac.Collection) -> None: + def successor(self, source_collection: ps.Collection) -> None: self.collection.clear_links(SUCCESSOR) if source_collection: - self.collection.add_link(pystac.Link(SUCCESSOR, source_collection, MEDIA_TYPE)) + self.collection.add_link(ps.Link(SUCCESSOR, source_collection, MEDIA_TYPE)) @classmethod - def from_collection(cls, a_collection: pystac.Collection): + def from_collection(cls, a_collection: ps.Collection) -> "VersionCollectionExt": return cls(a_collection) @classmethod @@ -279,10 +279,10 @@ def _object_links(cls) -> List[str]: def apply(self, version: str, - deprecated: Optional[str] = None, - latest: Optional[pystac.Collection] = None, - predecessor=None, - successor=None) -> None: + deprecated: Optional[bool] = None, + latest: Optional[ps.Collection] = None, + predecessor: Optional[ps.Collection] = None, + successor: Optional[ps.Collection] = None) -> None: """Applies version extension properties to the extended Collection. Args: @@ -305,7 +305,8 @@ def apply(self, self.successor = successor -VERSION_EXTENSION_DEFINITION = base.ExtensionDefinition(Extensions.VERSION, [ - base.ExtendedObject(pystac.Item, VersionItemExt), - base.ExtendedObject(pystac.Collection, VersionCollectionExt) -]) +VERSION_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( + Extensions.VERSION, [ + base.ExtendedObject(ps.Item, VersionItemExt), + base.ExtendedObject(ps.Collection, VersionCollectionExt) + ]) diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 9d8554ebd..cab79ea87 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -34,7 +34,7 @@ def apply(self, incidence_angle: Optional[float] = None, azimuth: Optional[float] = None, sun_azimuth: Optional[float] = None, - sun_elevation: Optional[float] = None): + sun_elevation: Optional[float] = None) -> None: """Applies View Geometry extension properties to the extended Item. Args: @@ -78,7 +78,7 @@ def off_nadir(self) -> Optional[float]: return self.get_off_nadir() @off_nadir.setter - def off_nadir(self, v: Optional[float]): + def off_nadir(self, v: Optional[float]) -> None: self.set_off_nadir(v) def get_off_nadir(self, asset: Optional[Asset] = None) -> Optional[float]: @@ -95,7 +95,7 @@ def get_off_nadir(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('view:off_nadir') - def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[Asset] = None): + def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset off_nadir. If an Asset is supplied, sets the property on the Asset. @@ -132,7 +132,9 @@ def get_incidence_angle(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('view:incidence_angle') - def set_incidence_angle(self, incidence_angle: Optional[float], asset: Optional[Asset] = None): + def set_incidence_angle(self, + incidence_angle: Optional[float], + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset incidence_angle. If an Asset is supplied, sets the property on the Asset. @@ -260,5 +262,5 @@ def from_item(cls, item: Item) -> "ViewItemExt": return cls(item) -VIEW_EXTENSION_DEFINITION = ExtensionDefinition(Extensions.VIEW, - [ExtendedObject(Item, ViewItemExt)]) +VIEW_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( + Extensions.VIEW, [ExtendedObject(Item, ViewItemExt)]) diff --git a/pystac/item.py b/pystac/item.py index d0c881c4b..a0c71bf82 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -36,7 +36,7 @@ def title(self) -> Optional[str]: return self.properties.get('title') @title.setter - def title(self, v: Optional[str]): + def title(self, v: Optional[str]) -> None: self.properties['title'] = v @property @@ -49,7 +49,7 @@ def description(self) -> Optional[str]: return self.properties.get('description') @description.setter - def description(self, v: Optional[str]): + def description(self, v: Optional[str]) -> None: self.properties['description'] = v # Date and Time Range @@ -232,11 +232,18 @@ def set_providers(self, If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ - providers_dicts = [d.to_dict() for d in providers] if asset is None: - self.properties['providers'] = providers_dicts + if providers is None: + self.properties.pop('providers', None) + else: + providers_dicts = [d.to_dict() for d in providers] + self.properties['providers'] = providers_dicts else: - asset.properties['providers'] = providers_dicts + if providers is None: + asset.properties.pop('providers', None) + else: + providers_dicts = [d.to_dict() for d in providers] + asset.properties['providers'] = providers_dicts # Instrument @property @@ -599,7 +606,7 @@ def __init__(self, self.properties = {} # The Item which owns this Asset. - self.owner = None + self.owner: Optional[Item] = None def set_owner(self, item: "Item") -> None: """Sets the owning item of this Asset. @@ -655,7 +662,7 @@ def to_dict(self) -> Dict[str, Any]: return d - def clone(self): + def clone(self) -> "Asset": """Clones this asset. Returns: @@ -668,7 +675,7 @@ def clone(self): roles=self.roles, properties=self.properties) - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.href) @staticmethod @@ -783,15 +790,14 @@ def __init__(self, if href is not None: self.set_self_href(href) + self.collection_id: Optional[str] = None if collection is not None: if isinstance(collection, Collection): self.set_collection(collection) else: self.collection_id = collection - else: - self.collection_id = None - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.id) def set_self_href(self, href: str) -> None: @@ -950,7 +956,7 @@ def get_collection(self) -> Optional[Collection]: def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: links = self.links if not include_self_link: - links = filter(lambda x: x.rel != 'self', links) + links = [x for x in links if x.rel != 'self'] assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items())) diff --git a/pystac/link.py b/pystac/link.py index 0490ecaa7..6c0aeff28 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -6,10 +6,10 @@ from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) if TYPE_CHECKING: - from pystac.stac_object import STACObject - from pystac.item import Item - from pystac.catalog import Catalog - from pystac.collection import Collection + from pystac.stac_object import STACObject as STACObjectType + from pystac.item import Item as ItemType + from pystac.catalog import Catalog as CatalogType + from pystac.collection import Collection as CollectionType HIERARCHICAL_LINKS = ['root', 'child', 'parent', 'collection', 'item', 'items'] @@ -58,18 +58,18 @@ class Link: """ def __init__(self, rel: str, - target: Union[str, "STACObject"], + target: Union[str, "STACObjectType"], media_type: Optional[str] = None, title: Optional[str] = None, properties: Optional[Dict[str, Any]] = None) -> None: self.rel = rel - self.target: Union[str, "STACObject"] = target # An object or an href + self.target: Union[str, "STACObjectType"] = target # An object or an href self.media_type = media_type self.title = title self.properties = properties - self.owner = None + self.owner: Optional["STACObjectType"] = None - def set_owner(self, owner: "STACObject") -> "Link": + def set_owner(self, owner: "STACObjectType") -> "Link": """Sets the owner of this link. Args: @@ -110,7 +110,7 @@ def get_href(self) -> Optional[str]: rel_links = HIERARCHICAL_LINKS + \ ps.STAC_EXTENSIONS.get_extended_object_links(self.owner) # if a hierarchical link with an owner and root, and relative catalog - if root.is_relative() and self.rel in rel_links: + if root and root.is_relative() and self.rel in rel_links: owner_href = self.owner.get_self_href() if owner_href is not None: href = make_relative_href(href, owner_href) @@ -147,10 +147,10 @@ def get_absolute_href(self) -> Optional[str]: return href - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.rel, self.target) - def resolve_stac_object(self, root: Optional["Catalog"] = None) -> "Link": + def resolve_stac_object(self, root: Optional["CatalogType"] = None) -> "Link": """Resolves a STAC object from the HREF of this link, if the link is not already resolved. @@ -195,7 +195,7 @@ def resolve_stac_object(self, root: Optional["Catalog"] = None) -> "Link": return self - def is_resolved(self): + def is_resolved(self) -> bool: """Determines if the link's target is a resolved STACObject. Returns: @@ -226,7 +226,7 @@ def to_dict(self) -> Dict[str, Any]: return d - def clone(self): + def clone(self) -> "Link": """Clones this link. This makes a copy of all link information, but does not clone a STACObject @@ -261,17 +261,17 @@ def from_dict(d: Dict[str, Any]) -> "Link": return Link(rel=rel, target=href, media_type=media_type, title=title, properties=properties) @staticmethod - def root(c: "Catalog") -> "Link": + def root(c: "CatalogType") -> "Link": """Creates a link to a root Catalog or Collection.""" return Link('root', c, media_type='application/json') @staticmethod - def parent(c: "Catalog") -> "Link": + def parent(c: "CatalogType") -> "Link": """Creates a link to a parent Catalog or Collection.""" return Link('parent', c, media_type='application/json') @staticmethod - def collection(c: "Collection") -> "Link": + def collection(c: "CollectionType") -> "Link": """Creates a link to an item's Collection.""" return Link('collection', c, media_type='application/json') @@ -281,11 +281,11 @@ def self_href(href: str) -> "Link": return Link('self', href, media_type='application/json') @staticmethod - def child(c: "Catalog", title: Optional[str] = None) -> "Link": + def child(c: "CatalogType", title: Optional[str] = None) -> "Link": """Creates a link to a child Catalog or Collection.""" return Link('child', c, title=title, media_type='application/json') @staticmethod - def item(item: "Item", title: Optional[str] = None) -> "Link": + def item(item: "ItemType", title: Optional[str] = None) -> "Link": """Creates a link to an Item.""" return Link('item', item, title=title, media_type='application/json') diff --git a/pystac/media_type.py b/pystac/media_type.py index 5bc4c2798..d9e2d50e0 100644 --- a/pystac/media_type.py +++ b/pystac/media_type.py @@ -4,7 +4,7 @@ class MediaType(str, Enum): """A list of common media types that can be used in STAC Asset and Link metadata. """ - def __str__(self): + def __str__(self) -> str: return str(self.value) COG = 'image/tiff; application=geotiff; profile=cloud-optimized' diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index 05a249762..72ea7b925 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -1,10 +1,9 @@ -from typing import Any, Dict, Iterable, Optional, Union, cast -from pystac.utils import make_absolute_href -from pystac.stac_io import STAC_IO -from pystac.serialization.identify import STACVersionID +from typing import Any, Dict, Iterable, List, Optional, Union, cast import pystac as ps from pystac.cache import CollectionCache +from pystac.serialization.identify import STACVersionID +from pystac.utils import make_absolute_href def merge_common_properties(item_dict: Dict[str, Any], @@ -59,7 +58,7 @@ def merge_common_properties(item_dict: Dict[str, Any], if isinstance(item_dict['links'], dict): links = list(cast(Iterable[Dict[str, Any]], item_dict['links'].values())) else: - links = cast(Iterable[Dict[str, Any]], item_dict['links']) + links = cast(List[Dict[str, Any]], item_dict['links']) collection_link = next((link for link in links if link['rel'] == 'collection'), None) if collection_link is not None: @@ -71,7 +70,7 @@ def merge_common_properties(item_dict: Dict[str, Any], collection = collection_cache.get_by_href(collection_href) if collection is None: - collection = STAC_IO.read_json(collection_href) + collection = ps.STAC_IO.read_json(collection_href) if collection is not None: collection_id = None diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 8c4216830..b434fd198 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,10 +1,13 @@ from functools import total_ordering -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast -import pystac +import pystac as ps from pystac.version import STACVersion from pystac.extensions import Extensions +if TYPE_CHECKING: + from pystac.stac_object import STACObjectType as STACObjectTypeType + @total_ordering class STACVersionID: @@ -103,7 +106,7 @@ def is_later_than(self, v: Union[str, STACVersionID]) -> bool: v = STACVersionID(v) return v < self.min_version - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.min_version, self.max_version) @@ -119,7 +122,7 @@ class STACJSONDescription: custom_extensions (List[str]): List of custom extensions (URIs to JSON Schemas) used by this STAC Object. """ - def __init__(self, object_type: str, version_range: STACVersionRange, + def __init__(self, object_type: "STACObjectTypeType", version_range: STACVersionRange, common_extensions: List[str], custom_extensions: List[str]) -> None: self.object_type = object_type self.version_range = version_range @@ -144,7 +147,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], # assets (collection assets) - if object_type == pystac.STACObjectType.ITEMCOLLECTION: + if object_type == ps.STACObjectType.ITEMCOLLECTION: if 'assets' in d: stac_extensions.add('assets') version_range.set_min(STACVersionID('0.8.0')) @@ -173,19 +176,19 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range.set_min(STACVersionID('0.6.2')) # datacube - if object_type == pystac.STACObjectType.ITEM: + if object_type == ps.STACObjectType.ITEM: if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.DATACUBE) version_range.set_min(STACVersionID('0.6.1')) # datetime-range (old extension) - if object_type == pystac.STACObjectType.ITEM: + if object_type == ps.STACObjectType.ITEM: if 'dtr:start_datetime' in d['properties']: stac_extensions.add('datetime-range') version_range.set_min(STACVersionID('0.6.0')) # eo - if object_type == pystac.STACObjectType.ITEM: + if object_type == ps.STACObjectType.ITEM: if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.EO) if 'eo:epsg' in d['properties']: @@ -200,13 +203,13 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range.set_max(STACVersionID('0.5.2')) # pointcloud - if object_type == pystac.STACObjectType.ITEM: + if object_type == ps.STACObjectType.ITEM: if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.POINTCLOUD) version_range.set_min(STACVersionID('0.6.2')) # sar - if object_type == pystac.STACObjectType.ITEM: + if object_type == ps.STACObjectType.ITEM: if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): stac_extensions.add(Extensions.SAR) version_range.set_min(STACVersionID('0.6.2')) @@ -233,7 +236,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range.set_max(STACVersionID('0.6.2')) # scientific - if object_type == pystac.STACObjectType.ITEM or object_type == pystac.STACObjectType.COLLECTION: + if object_type == ps.STACObjectType.ITEM or object_type == ps.STACObjectType.COLLECTION: if 'properties' in d: prop_keys = cast(Dict[str, Any], d['properties']).keys() if any(k.startswith('sci:') for k in prop_keys): @@ -241,7 +244,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], version_range.set_min(STACVersionID('0.6.0')) # Single File STAC - if object_type == pystac.STACObjectType.ITEMCOLLECTION: + if object_type == ps.STACObjectType.ITEMCOLLECTION: if 'collections' in d: stac_extensions.add(Extensions.SINGLE_FILE_STAC) version_range.set_min(STACVersionID('0.8.0')) @@ -266,7 +269,7 @@ def _split_extensions(stac_extensions: List[str]) -> Tuple[List[str], List[str]] return (common_extensions, custom_extensions) -def identify_stac_object_type(json_dict: Dict[str, Any]): +def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectTypeType": """Determines the STACObjectType of the provided JSON dict. Args: @@ -281,14 +284,14 @@ def identify_stac_object_type(json_dict: Dict[str, Any]): if 'type' in json_dict and 'assets' not in json_dict: if 'stac_version' in json_dict and cast(str, json_dict['stac_version']).startswith('0'): if json_dict['type'] == 'FeatureCollection': - object_type = pystac.STACObjectType.ITEMCOLLECTION + object_type = ps.STACObjectType.ITEMCOLLECTION if 'extent' in json_dict: - object_type = pystac.STACObjectType.COLLECTION + object_type = ps.STACObjectType.COLLECTION elif 'assets' in json_dict: - object_type = pystac.STACObjectType.ITEM + object_type = ps.STACObjectType.ITEM else: - object_type = pystac.STACObjectType.CATALOG + object_type = ps.STACObjectType.CATALOG return object_type @@ -311,10 +314,10 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: stac_extensions = json_dict.get('stac_extensions', None) if stac_version is None: - if (object_type == pystac.STACObjectType.CATALOG - or object_type == pystac.STACObjectType.COLLECTION): + if (object_type == ps.STACObjectType.CATALOG + or object_type == ps.STACObjectType.COLLECTION): version_range.set_max(STACVersionID('0.5.2')) - elif object_type == pystac.STACObjectType.ITEM: + elif object_type == ps.STACObjectType.ITEM: version_range.set_max(STACVersionID('0.7.0')) else: # ItemCollection version_range.set_min(STACVersionID('0.8.0')) @@ -330,7 +333,7 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: # but ItemCollection (except after 0.9.0, when ItemCollection also got # the stac_extensions property). if version_range.is_earlier_than('0.8.0') or \ - (object_type == pystac.STACObjectType.ITEMCOLLECTION and not version_range.is_later_than( + (object_type == ps.STACObjectType.ITEMCOLLECTION and not version_range.is_later_than( '0.8.1')): stac_extensions = _identify_stac_extensions(object_type, json_dict, version_range) else: diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 3442be738..a7f0f9bcf 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -50,16 +50,17 @@ def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, if 'assets' in d: d['item_assets'] = d['assets'] del d['assets'] + return None def _migrate_checksum(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: - pass + return None def _migrate_datacube(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: - pass + return None def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, @@ -74,6 +75,8 @@ def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, d['properties']['end_datetime'] = d['properties']['dtr:end_datetime'] del d['properties']['dtr:end_datetime'] + return None + def _migrate_eo(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: @@ -159,7 +162,8 @@ def _migrate_eo(d: Dict[str, Any], version: STACVersionID, return added_extensions -def _migrate_label(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: +def _migrate_label(d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> Optional[Set[str]]: if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': props = d['properties'] # Migrate 0.8.0-rc1 non-pluralized forms @@ -180,10 +184,12 @@ def _migrate_label(d: Dict[str, Any], version: STACVersionID, info: STACJSONDesc props['label:methods'] = props['label:method'] del props['label:method'] + return None + def _migrate_pointcloud(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: - pass + return None def _migrate_sar(d: Dict[str, Any], version: STACVersionID, @@ -202,15 +208,17 @@ def _migrate_sar(d: Dict[str, Any], version: STACVersionID, d['properties']['constellation'] = d['properties']['sar:constellation'] del d['properties']['sar:constellation'] + return None + def _migrate_scientific(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: - pass + return None def _migrate_single_file_stac(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: - pass + return None def _get_object_migrations( diff --git a/pystac/stac_io.py b/pystac/stac_io.py index c213f0a0d..d00693b1e 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -6,9 +6,11 @@ from urllib.request import urlopen from urllib.error import HTTPError +import pystac.serialization + if TYPE_CHECKING: - from pystac.stac_object import STACObject - from pystac.catalog import Catalog + from pystac.stac_object import STACObject as STACObjectType + from pystac.catalog import Catalog as CatalogType class STAC_IO: @@ -55,9 +57,11 @@ def default_write_text_method(uri: str, txt: str) -> None: cloud storage. """ - # Replaced in __init__ to account for extension objects. - stac_object_from_dict: Optional[Callable[[Dict[str, Any], Optional[str], Optional["Catalog"]], - "STACObject"]] = None + @staticmethod + def stac_object_from_dict(d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["CatalogType"] = None) -> "STACObjectType": + return pystac.serialization.stac_object_from_dict(d, href, root) # This is set in __init__.py _STAC_OBJECT_CLASSES = None @@ -116,7 +120,7 @@ def read_json(cls, uri: str) -> Dict[str, Any]: return json.loads(STAC_IO.read_text(uri)) @classmethod - def read_stac_object(cls, uri: str, root: Optional["Catalog"] = None) -> "STACObject": + def read_stac_object(cls, uri: str, root: Optional["CatalogType"] = None) -> "STACObjectType": """Read a STACObject from a JSON file at the given URI. Args: diff --git a/pystac/stac_object.py b/pystac/stac_object.py index ed0f8d9fb..65c10e366 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,6 +1,6 @@ from abc import (ABC, abstractmethod) from enum import Enum -from typing import Any, Dict, Generator, List, Optional, cast, TYPE_CHECKING +from typing import Any, Dict, Iterable, List, Optional, cast, TYPE_CHECKING import pystac as ps from pystac import STACError @@ -15,7 +15,7 @@ class STACObjectType(str, Enum): - def __str__(self): + def __str__(self) -> str: return str(self.value) CATALOG = 'CATALOG' @@ -33,7 +33,7 @@ class LinkMixin: links: List[Link] - def add_link(self, link: Link): + def add_link(self, link: Link) -> None: """Add a link to this object's set of links. Args: @@ -41,9 +41,8 @@ def add_link(self, link: Link): """ link.set_owner(cast(STACObject, self)) self.links.append(link) - return self - def add_links(self, links: List[Link]) -> "LinkMixin": + def add_links(self, links: List[Link]) -> None: """Add links to this object's set of links. Args: @@ -52,9 +51,8 @@ def add_links(self, links: List[Link]) -> "LinkMixin": for link in links: self.add_link(link) - return self - def remove_links(self, rel: str) -> "LinkMixin": + def remove_links(self, rel: str) -> None: """Remove links to this object's set of links that match the given ``rel``. Args: @@ -62,7 +60,6 @@ def remove_links(self, rel: str) -> "LinkMixin": """ self.links = [link for link in self.links if link.rel != rel] - return self def get_single_link(self, rel: str) -> Optional[Link]: """Get single link that match the given ``rel``. @@ -73,7 +70,7 @@ def get_single_link(self, rel: str) -> Optional[Link]: return next((link for link in self.links if link.rel == rel), None) - def get_links(self, rel: Optional[str] = None): + def get_links(self, rel: Optional[str] = None) -> List[Link]: """Gets the :class:`~pystac.Link` instances associated with this object. Args: @@ -89,7 +86,7 @@ def get_links(self, rel: Optional[str] = None): else: return [link for link in self.links if link.rel == rel] - def clear_links(self, rel: Optional[str] = None): + def clear_links(self, rel: Optional[str] = None) -> None: """Clears all :class:`~pystac.Link` instances associated with this object. Args: @@ -99,9 +96,8 @@ def clear_links(self, rel: Optional[str] = None): self.links = [link for link in self.links if link.rel != rel] else: self.links = [] - return self - def get_root_link(self): + def get_root_link(self) -> Optional[Link]: """Get the :class:`~pystac.Link` representing the root for this object. @@ -146,7 +142,7 @@ def get_self_href(self) -> Optional[str]: else: return None - def set_self_href(self, href: str) -> "LinkMixin": + def set_self_href(self, href: str) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -167,8 +163,6 @@ def set_self_href(self, href: str) -> "LinkMixin": if root_link is not None and root_link.is_resolved(): cast(ps.Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) - return self - class STACObject(LinkMixin, ABC): """A STACObject is the base class for any element of STAC that @@ -182,9 +176,9 @@ class STACObject(LinkMixin, ABC): """ id: str - STAC_OBJECT_TYPE = None # Overridden by the child classes with their type. + STAC_OBJECT_TYPE: STACObjectType - def __init__(self, stac_extensions: List[str]): + def __init__(self, stac_extensions: List[str]) -> None: self.links = [] self.stac_extensions = stac_extensions @@ -220,7 +214,7 @@ def get_root(self) -> Optional["CatalogType"]: else: return None - def set_root(self, root: Optional["CatalogType"]) -> "STACObject": + def set_root(self, root: Optional["CatalogType"]) -> None: """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -248,8 +242,6 @@ def set_root(self, root: Optional["CatalogType"]) -> "STACObject": self.add_link(new_root_link) root._resolved_objects.cache(self) - return self - def get_parent(self) -> Optional["CatalogType"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the parent for this object. The root is represented by a @@ -266,7 +258,7 @@ def get_parent(self) -> Optional["CatalogType"]: else: return None - def set_parent(self, parent: Optional["CatalogType"]) -> "STACObject": + def set_parent(self, parent: Optional["CatalogType"]) -> None: """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -278,9 +270,8 @@ def set_parent(self, parent: Optional["CatalogType"]) -> "STACObject": self.remove_links('parent') if parent is not None: self.add_link(Link.parent(parent)) - return self - def get_stac_objects(self, rel: str) -> Generator["STACObject", None, None]: + def get_stac_objects(self, rel: str) -> Iterable["STACObject"]: """Gets the :class:`~pystac.STACObject` instances that are linked to by links with their ``rel`` property matching the passed in argument. @@ -289,7 +280,7 @@ def get_stac_objects(self, rel: str) -> Generator["STACObject", None, None]: ``rel`` property against. Returns: - Generator[STACObjects]: A possibly empty generator of STACObjects that are + Iterable[STACObjects]: A possibly empty iterable of STACObjects that are connected to this object through links with the given ``rel``. """ links = self.links[:] @@ -355,15 +346,17 @@ def full_copy(self, if link.rel in link_rels: link.resolve_stac_object() target = cast("STACObject", link.target) - if target in root._resolved_objects: - target = root._resolved_objects.get(target) - assert target is not None + if root is not None and target in root._resolved_objects: + cached_target = root._resolved_objects.get(target) + assert cached_target is not None + target = cached_target else: target_parent = None if link.rel in ['child', 'item'] and isinstance(clone, ps.Catalog): target_parent = clone copied_target = target.full_copy(root=root, parent=target_parent) - root._resolved_objects.cache(copied_target) + if root is not None: + root._resolved_objects.cache(copied_target) target = copied_target if link.rel in ['child', 'item']: target.set_root(root) @@ -390,7 +383,7 @@ def ext(self) -> "ExtensionIndex": """ return ExtensionIndex(self) - def resolve_links(self): + def resolve_links(self) -> None: """Ensure all STACObjects linked to by this STACObject are resolved. This is important for operations such as changing HREFs. diff --git a/pystac/utils.py b/pystac/utils.py index f0b30f709..b8ea00576 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -180,7 +180,7 @@ def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]: lats: List[float] = [] lons: List[float] = [] - def extract_coords(coords: List[Union[List[float], List[List[Any]]]]): + def extract_coords(coords: List[Union[List[float], List[List[Any]]]]) -> None: for x in coords: # This handles points if isinstance(x, float): diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 3f504ad2f..e7c2b55aa 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -1,11 +1,13 @@ # flake8: noqa from typing import Dict, List, Any, Optional, cast, TYPE_CHECKING -import pystac + +import pystac as ps from pystac.serialization.identify import identify_stac_object from pystac.utils import make_absolute_href if TYPE_CHECKING: - from pystac.stac_object import STACObject + from pystac.stac_object import STACObject as STACObjectType + from pystac.stac_object import STACObjectType as STACObjectTypeType class STACValidationError(Exception): @@ -26,7 +28,7 @@ def __init__(self, message: str, source: Optional[Any] = None): from pystac.validation.stac_validator import (STACValidator, JsonSchemaSTACValidator) -def validate(stac_object: "STACObject") -> List[Any]: +def validate(stac_object: "STACObjectType") -> List[Any]: """Validates a :class:`~pystac.STACObject`. Args: @@ -42,13 +44,13 @@ def validate(stac_object: "STACObject") -> List[Any]: """ return validate_dict(stac_dict=stac_object.to_dict(), stac_object_type=stac_object.STAC_OBJECT_TYPE, - stac_version=pystac.get_stac_version(), + stac_version=ps.get_stac_version(), extensions=stac_object.stac_extensions, href=stac_object.get_self_href()) def validate_dict(stac_dict: Dict[str, Any], - stac_object_type: Optional[str] = None, + stac_object_type: Optional["STACObjectTypeType"] = None, stac_version: Optional[str] = None, extensions: Optional[List[str]] = None, href: Optional[str] = None) -> List[Any]: @@ -119,19 +121,19 @@ def validate_all(stac_dict: Dict[str, Any], href: str) -> None: extensions=info.common_extensions, href=href) - if info.object_type != pystac.STACObjectType.ITEM: + if info.object_type != ps.STACObjectType.ITEM: if 'links' in stac_dict: # Account for 0.6 links if isinstance(stac_dict['links'], dict): links: List[Dict[str, Any]] = list(stac_dict['links'].values()) else: - links: List[Dict[str, Any]] = cast(List[Dict[str, Any]], stac_dict.get('links')) + links = cast(List[Dict[str, Any]], stac_dict.get('links')) for link in links: rel = link.get('rel') if rel in ['item', 'child']: link_href = make_absolute_href(cast(str, link.get('href')), start_href=href) if link_href is not None: - d = pystac.STAC_IO.read_json(link_href) + d = ps.STAC_IO.read_json(link_href) validate_all(d, link_href) @@ -159,7 +161,7 @@ def set_validator(cls, validator: STACValidator) -> None: cls._validator = validator -def set_validator(validator: STACValidator): +def set_validator(validator: STACValidator) -> None: """Sets the STACValidator to use in PySTAC. Args: diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index c9f9a7d1f..29ba9b349 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,5 +1,5 @@ from abc import (ABC, abstractmethod) -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple import pystac from pystac import (STACObjectType, Extensions) @@ -9,11 +9,11 @@ class SchemaUriMap(ABC): """Abstract class defining schema URIs for STAC core objects and extensions. """ - def __init__(self): + def __init__(self) -> None: pass @abstractmethod - def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> str: + def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: """Get the schema URI for the given object type and stac version. Args: @@ -27,7 +27,7 @@ def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> @abstractmethod def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, - stac_version: str) -> str: + stac_version: str) -> Optional[str]: """Get the extension's schema URI for the given object type, stac version. Args: @@ -162,7 +162,7 @@ class DefaultSchemaUriMap(SchemaUriMap): } @classmethod - def _append_base_uri_if_needed(cls, uri: str, stac_version: str): + def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str]: # Only append the base URI if it's not already an absolute URI if '://' not in uri: base_uri = None @@ -176,7 +176,7 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: str): else: return uri - def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str): + def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: uri = None is_latest = stac_version == pystac.get_stac_version() @@ -194,7 +194,7 @@ def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str): return self._append_base_uri_if_needed(uri, stac_version) def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, - stac_version: str): + stac_version: str) -> Optional[str]: uri = None is_latest = stac_version == pystac.get_stac_version() diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 2106040fd..f7ce27794 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -25,9 +25,9 @@ class STACValidator(ABC): @abstractmethod def validate_core(self, stac_dict: Dict[str, Any], - stac_object_type: str, + stac_object_type: STACObjectType, stac_version: str, - href: Optional[str] = None): + href: Optional[str] = None) -> Any: """Validate a core stac object. Return value can be None or specific to the implementation. @@ -44,10 +44,10 @@ def validate_core(self, @abstractmethod def validate_extension(self, stac_dict: Dict[str, Any], - stac_object_type: str, + stac_object_type: STACObjectType, stac_version: str, extension_id: str, - href: Optional[str] = None): + href: Optional[str] = None) -> Any: """Validate an extension stac object. Return value can be None or specific to the implementation. @@ -64,10 +64,10 @@ def validate_extension(self, def validate(self, stac_dict: Dict[str, Any], - stac_object_type: str, + stac_object_type: STACObjectType, stac_version: str, extensions: List[str], - href: Optional[str] = None): + href: Optional[str] = None) -> List[Any]: """Validate a STAC object JSON. Args: @@ -118,7 +118,7 @@ class JsonSchemaSTACValidator(STACValidator): Note: This class requires the ``jsonschema`` library to be installed. """ - def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None): + def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: if jsonschema is None: raise Exception('Cannot instantiate, requires jsonschema package') @@ -167,7 +167,7 @@ def validate_core(self, stac_dict: Dict[str, Any], stac_object_type: STACObjectType, stac_version: str, - href: Optional[str] = None): + href: Optional[str] = None) -> Optional[str]: """Validate a core stac object. Return value can be None or specific to the implementation. @@ -201,7 +201,7 @@ def validate_extension(self, stac_object_type: STACObjectType, stac_version: str, extension_id: str, - href: Optional[str] = None): + href: Optional[str] = None) -> Optional[str]: """Validate an extension stac object. Return value can be None or specific to the implementation. diff --git a/pystac/version.py b/pystac/version.py index ab69c70f3..7758fda73 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -44,7 +44,7 @@ def get_stac_version() -> str: return STACVersion.get_stac_version() -def set_stac_version(stac_version: str): +def set_stac_version(stac_version: str) -> None: """Sets the STAC version that PySTAC should use. This is the version that will be set as the "stac_version" property diff --git a/requirements-dev.txt b/requirements-dev.txt index 03a4f079e..5feddb12f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,7 @@ +mypy==0.790 +flake8==3.8.* +yapf==0.30.* + codespell==1.17.1 ipython==7.16.1 jsonschema==3.2.0 @@ -6,7 +10,5 @@ Sphinx==1.8.0 sphinx-autobuild==0.7.1 sphinxcontrib-fulltoc==1.2.0 sphinxcontrib-napoleon==0.7 -flake8==3.8.* -yapf==0.30.* nbsphinx==0.7.1 coverage==5.2.* diff --git a/scripts/test b/scripts/test index 93df987d6..bc1582076 100755 --- a/scripts/test +++ b/scripts/test @@ -17,6 +17,9 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then if [ "${1:-}" = "--help" ]; then usage else + # Types + mypy pystac + # Lint flake8 pystac tests From 73fad0d073017618dc79d06cc5de20ad63182a40 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sun, 25 Apr 2021 02:15:52 -0400 Subject: [PATCH 05/51] Remove LinkMixin, implement directly into STACObject --- pystac/stac_object.py | 66 ++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 65c10e366..a027b3ecd 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -24,14 +24,36 @@ def __str__(self) -> str: ITEMCOLLECTION = 'ITEMCOLLECTION' -class LinkMixin: - """Mixin class for adding and accessing links. +class STACObject(ABC): + """A STACObject is the base class for any element of STAC that + has links e.g. (Catalogs, Collections, or Items). A STACObject has + common functionality, can be converted to and from Python ``dicts`` representing + JSON, and can be cloned or copied. - Implementing classes must have a `links` attribute that is - a list of links. + Attributes: + links (List[Link]): A list of :class:`~pystac.Link` objects representing + all links associated with this STACObject. """ + id: str + + STAC_OBJECT_TYPE: STACObjectType + + def __init__(self, stac_extensions: List[str]) -> None: + self.links: List[Link] = [] + self.stac_extensions = stac_extensions + + def validate(self) -> List[Any]: + """Validate this STACObject. - links: List[Link] + Returns a list of validation results, which depends on the validation + implementation. For JSON Schema validation, this will be a list + of schema URIs that were used during validation. + + Raises: + STACValidationError + """ + import pystac.validation + return pystac.validation.validate(self) # type:ignore def add_link(self, link: Link) -> None: """Add a link to this object's set of links. @@ -163,38 +185,6 @@ def set_self_href(self, href: str) -> None: if root_link is not None and root_link.is_resolved(): cast(ps.Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) - -class STACObject(LinkMixin, ABC): - """A STACObject is the base class for any element of STAC that - has links e.g. (Catalogs, Collections, or Items). A STACObject has - common functionality, can be converted to and from Python ``dicts`` representing - JSON, and can be cloned or copied. - - Attributes: - links (List[Link]): A list of :class:`~pystac.Link` objects representing - all links associated with this STACObject. - """ - id: str - - STAC_OBJECT_TYPE: STACObjectType - - def __init__(self, stac_extensions: List[str]) -> None: - self.links = [] - self.stac_extensions = stac_extensions - - def validate(self) -> List[Any]: - """Validate this STACObject. - - Returns a list of validation results, which depends on the validation - implementation. For JSON Schema validation, this will be a list - of schema URIs that were used during validation. - - Raises: - STACValidationError - """ - import pystac.validation - return pystac.validation.validate(self) # type:ignore - def get_root(self) -> Optional["CatalogType"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the root for this object. The root is represented by a @@ -345,7 +335,7 @@ def full_copy(self, for link in clone.links: if link.rel in link_rels: link.resolve_stac_object() - target = cast("STACObject", link.target) + target = cast(STACObject, link.target) if root is not None and target in root._resolved_objects: cached_target = root._resolved_objects.get(target) assert cached_target is not None From 4100daeaa3666b44bbcd0a2e5f61de6ec88fe31c Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sun, 25 Apr 2021 02:25:13 -0400 Subject: [PATCH 06/51] Use underscore naming convention for TYPE_CHECKING imports --- pystac/cache.py | 48 +++++++++++++++--------------- pystac/catalog.py | 27 +++++++++-------- pystac/collection.py | 6 ++-- pystac/extensions/base.py | 24 +++++++-------- pystac/layout.py | 51 +++++++++++++++++--------------- pystac/link.py | 28 +++++++++--------- pystac/serialization/identify.py | 6 ++-- pystac/stac_io.py | 8 ++--- pystac/stac_object.py | 24 +++++++-------- pystac/validation/__init__.py | 8 ++--- 10 files changed, 117 insertions(+), 113 deletions(-) diff --git a/pystac/cache.py b/pystac/cache.py index acbcd340b..a3aeefcba 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -5,11 +5,11 @@ import pystac as ps if TYPE_CHECKING: - from pystac.stac_object import STACObject - from pystac.collection import Collection + from pystac.stac_object import STACObject as STACObject_Type + from pystac.collection import Collection as Collection_Type -def get_cache_key(stac_object: "STACObject") -> Tuple[str, bool]: +def get_cache_key(stac_object: "STACObject_Type") -> Tuple[str, bool]: """Produce a cache key for the given STAC object. If a self href is set, use that as the cache key. @@ -26,7 +26,7 @@ def get_cache_key(stac_object: "STACObject") -> Tuple[str, bool]: return (href, True) else: ids: List[str] = [] - obj: Optional[STACObject] = stac_object + obj: Optional[ps.STACObject] = stac_object while obj is not None: ids.append(obj.id) obj = obj.get_parent() @@ -58,16 +58,16 @@ class ResolvedObjectCache: ids_to_collections (Dict[str, Collection]): Map of collection IDs to collections. """ def __init__(self, - id_keys_to_objects: Optional[Dict[str, "STACObject"]] = None, - hrefs_to_objects: Optional[Dict[str, "STACObject"]] = None, - ids_to_collections: Dict[str, "Collection"] = None): + id_keys_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, + hrefs_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, + ids_to_collections: Dict[str, "Collection_Type"] = None): self.id_keys_to_objects = id_keys_to_objects or {} self.hrefs_to_objects = hrefs_to_objects or {} self.ids_to_collections = ids_to_collections or {} self._collection_cache: Optional[ResolvedObjectCollectionCache] = None - def get_or_cache(self, obj: "STACObject") -> "STACObject": + def get_or_cache(self, obj: "STACObject_Type") -> "STACObject_Type": """Gets the STACObject that is the cached version of the given STACObject; or, if none exists, sets the cached object to the given object. @@ -93,7 +93,7 @@ def get_or_cache(self, obj: "STACObject") -> "STACObject": self.cache(obj) return obj - def get(self, obj: "STACObject") -> Optional["STACObject"]: + def get(self, obj: "STACObject_Type") -> Optional["STACObject_Type"]: """Get the cached object that has the same cache key as the given object. Args: @@ -109,7 +109,7 @@ def get(self, obj: "STACObject") -> Optional["STACObject"]: else: return self.id_keys_to_objects.get(key) - def get_by_href(self, href: str) -> Optional["STACObject"]: + def get_by_href(self, href: str) -> Optional["STACObject_Type"]: """Gets the cached object at href. Args: @@ -120,7 +120,7 @@ def get_by_href(self, href: str) -> Optional["STACObject"]: """ return self.hrefs_to_objects.get(href) - def get_collection_by_id(self, id: str) -> Optional["Collection"]: + def get_collection_by_id(self, id: str) -> Optional["Collection_Type"]: """Retrieved a cached Collection by its ID. Args: @@ -132,7 +132,7 @@ def get_collection_by_id(self, id: str) -> Optional["Collection"]: """ return self.ids_to_collections.get(id) - def cache(self, obj: "STACObject") -> None: + def cache(self, obj: "STACObject_Type") -> None: """Set the given object into the cache. Args: @@ -147,7 +147,7 @@ def cache(self, obj: "STACObject") -> None: if isinstance(obj, ps.Collection): self.ids_to_collections[obj.id] = obj - def remove(self, obj: "STACObject") -> None: + def remove(self, obj: "STACObject_Type") -> None: """Removes any cached object that matches the given object's cache key. Args: @@ -163,7 +163,7 @@ def remove(self, obj: "STACObject") -> None: if obj.STAC_OBJECT_TYPE == ps.STACObjectType.COLLECTION: self.id_keys_to_objects.pop(obj.id, None) - def __contains__(self, obj: "STACObject") -> bool: + def __contains__(self, obj: "STACObject_Type") -> bool: key, is_href = get_cache_key(obj) return key in self.hrefs_to_objects if is_href else key in self.id_keys_to_objects @@ -215,22 +215,22 @@ class CollectionCache: and will set Collection JSON that it reads in order to merge in common properties. """ def __init__(self, - cached_ids: Dict[str, Union["Collection", Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union["Collection", Dict[str, Any]]] = None): + cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None): self.cached_ids = cached_ids or {} self.cached_hrefs = cached_hrefs or {} - def get_by_id(self, collection_id: str) -> Optional[Union["Collection", Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: return self.cached_ids.get(collection_id) - def get_by_href(self, href: str) -> Optional[Union["Collection", Dict[str, Any]]]: + def get_by_href(self, href: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: return self.cached_hrefs.get(href) def contains_id(self, collection_id: str) -> bool: return collection_id in self.cached_ids def cache(self, - collection: Union["Collection", Dict[str, Any]], + collection: Union["Collection_Type", Dict[str, Any]], href: Optional[str] = None) -> None: """Caches a collection JSON.""" if isinstance(collection, ps.Collection): @@ -245,19 +245,19 @@ def cache(self, class ResolvedObjectCollectionCache(CollectionCache): def __init__(self, resolved_object_cache: ResolvedObjectCache, - cached_ids: Dict[str, Union["Collection", Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union["Collection", Dict[str, Any]]] = None): + cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None): super().__init__(cached_ids, cached_hrefs) self.resolved_object_cache = resolved_object_cache - def get_by_id(self, collection_id: str) -> Optional[Union["Collection", Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: result = self.resolved_object_cache.get_collection_by_id(collection_id) if result is None: return super().get_by_id(collection_id) else: return result - def get_by_href(self, href: str) -> Optional[Union["Collection", Dict[str, Any]]]: + def get_by_href(self, href: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: result = self.resolved_object_cache.get_by_href(href) if result is None: return super().get_by_href(href) @@ -269,7 +269,7 @@ def contains_id(self, collection_id: str) -> bool: or super().contains_id(collection_id)) def cache(self, - collection: Union["Collection", Dict[str, Any]], + collection: Union["Collection_Type", Dict[str, Any]], href: Optional[str] = None) -> None: super().cache(collection, href) diff --git a/pystac/catalog.py b/pystac/catalog.py index fe7e2099f..12c66014f 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -10,7 +10,7 @@ from pystac.cache import ResolvedObjectCache from pystac.utils import (is_absolute_href, make_absolute_href) if TYPE_CHECKING: - from pystac.item import Asset as AssetType, Item as ItemType + from pystac.item import Asset as Asset_Type, Item as Item_Type class CatalogType(str, Enum): @@ -198,7 +198,7 @@ def add_children(self, children: Iterable["Catalog"]) -> None: self.add_child(child) def add_item(self, - item: "ItemType", + item: "Item_Type", title: Optional[str] = None, strategy: Optional[HrefLayoutStrategy] = None) -> None: """Adds a link to an :class:`~pystac.Item`. @@ -228,7 +228,7 @@ def add_item(self, self.add_link(Link.item(item, title=title)) - def add_items(self, items: Iterable["ItemType"]) -> None: + def add_items(self, items: Iterable["Item_Type"]) -> None: """Adds links to multiple :class:`~pystac.Item` s. This method will set each item's parent to this object, and their root to this Catalog's root. @@ -307,7 +307,7 @@ def remove_child(self, child_id: str) -> None: child.set_root(None) self.links = new_links - def get_item(self, id: str, recursive: bool = False) -> Optional["ItemType"]: + def get_item(self, id: str, recursive: bool = False) -> Optional["Item_Type"]: """Returns an item with a given ID. Args: @@ -327,7 +327,7 @@ def get_item(self, id: str, recursive: bool = False) -> Optional["ItemType"]: return item return None - def get_items(self) -> Iterable["ItemType"]: + def get_items(self) -> Iterable["Item_Type"]: """Return all items of this catalog. Return: @@ -370,7 +370,7 @@ def remove_item(self, item_id: str) -> None: item.set_root(None) self.links = new_links - def get_all_items(self) -> Iterable["ItemType"]: + def get_all_items(self) -> Iterable["Item_Type"]: """Get all items from this catalog and all subcatalogs. Will traverse any subcatalogs recursively. @@ -500,7 +500,7 @@ def normalize_hrefs(self, if not is_absolute_href(root_href): root_href = make_absolute_href(root_href, os.getcwd(), start_is_dir=True) - def process_item(item: "ItemType", _root_href: str) -> Callable[[], None]: + def process_item(item: "Item_Type", _root_href: str) -> Callable[[], None]: item.resolve_links() new_self_href = _strategy.get_href(item, _root_href) @@ -665,7 +665,7 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: if catalog_type is not None: self.catalog_type = catalog_type - def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["ItemType"]]]: + def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["Item_Type"]]]: """Walks through children and items of catalogs. For each catalog in the STAC's tree rooted at this catalog (including this catalog @@ -707,8 +707,8 @@ def _object_links(self) -> List[str]: return ['child', 'item'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) def map_items( - self, item_mapper: Callable[["ItemType"], Union["ItemType", - List["ItemType"]]]) -> "Catalog": + self, item_mapper: Callable[["Item_Type"], Union["Item_Type", + List["Item_Type"]]]) -> "Catalog": """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -749,8 +749,9 @@ def process_catalog(catalog: Catalog) -> None: return new_cat def map_assets( - self, asset_mapper: Callable[[str, "AssetType"], Union["AssetType", Tuple[str, "AssetType"], - Dict[str, "AssetType"]]] + self, asset_mapper: Callable[[str, "Asset_Type"], Union["Asset_Type", Tuple[str, + "Asset_Type"], + Dict[str, "Asset_Type"]]] ) -> "Catalog": """Creates a copy of a catalog, with each Asset for each Item passed through the asset_mapper function. @@ -765,7 +766,7 @@ def map_assets( Catalog: A full copy of this catalog, with assets manipulated according to the asset_mapper function. """ - def apply_asset_mapper(tup: Tuple[str, "AssetType"]) -> List[Tuple[str, ps.Asset]]: + def apply_asset_mapper(tup: Tuple[str, "Asset_Type"]) -> List[Tuple[str, ps.Asset]]: k, v = tup result = asset_mapper(k, v) if result is None: diff --git a/pystac/collection.py b/pystac/collection.py index ba9c6e3a2..c4a710e35 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -13,7 +13,7 @@ from pystac.utils import datetime_to_str if TYPE_CHECKING: - from pystac.item import Item as ItemType + from pystac.item import Item as Item_Type class SpatialExtent: @@ -257,7 +257,7 @@ def from_dict(d: Dict[str, Any]) -> "Extent": TemporalExtent.from_dict(temporal_extent_dict)) @staticmethod - def from_items(items: Iterable["ItemType"]) -> "Extent": + def from_items(items: Iterable["Item_Type"]) -> "Extent": """Create an Extent based on the datetimes and bboxes of a list of items. Args: @@ -449,7 +449,7 @@ def __repr__(self) -> str: return ''.format(self.id) def add_item(self, - item: "ItemType", + item: "Item_Type", title: Optional[str] = None, strategy: Optional[HrefLayoutStrategy] = None) -> None: super().add_item(item, title, strategy) diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index e647464f6..21499928f 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -7,12 +7,12 @@ from pystac.extensions import ExtensionError if TYPE_CHECKING: - from pystac.stac_object import STACObject + from pystac.stac_object import STACObject as STACObject_Type class STACObjectExtension(ABC): @classmethod - def _from_object(cls, stac_object: "STACObject") -> "STACObjectExtension": + def _from_object(cls, stac_object: "STACObject_Type") -> "STACObjectExtension": ... @classmethod @@ -21,7 +21,7 @@ def _object_links(cls) -> List[str]: raise NotImplementedError("_object_links") @classmethod - def enable_extension(cls, stac_object: "STACObject") -> None: + def enable_extension(cls, stac_object: "STACObject_Type") -> None: """Enables the extension for the given stac_object. Child classes can choose to override this method in order to modify the stac_object when an extension is enabled. @@ -41,7 +41,7 @@ class ExtendedObject: stac_object_class: The STAC object class that is being extended. extension_class: The class of the extension, e.g. LabelItemExt """ - def __init__(self, stac_object_class: Type["STACObject"], + def __init__(self, stac_object_class: Type["STACObject_Type"], extension_class: Type[STACObjectExtension]): if stac_object_class is Catalog: if not issubclass(extension_class, CatalogExtension): @@ -76,7 +76,7 @@ def __init__(self, extension_id: str, extended_objects: List[ExtendedObject]): class CatalogExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object: "STACObject") -> "CatalogExtension": + def _from_object(cls, stac_object: "STACObject_Type") -> "CatalogExtension": if not isinstance(stac_object, Catalog): raise ValueError(f"This extension applies to Catalogs, not {cls}") return cls.from_catalog(stac_object) @@ -89,7 +89,7 @@ def from_catalog(cls, catalog: Catalog) -> "CatalogExtension": class CollectionExtension(STACObjectExtension): @classmethod - def _from_object(cls, stac_object: "STACObject") -> "CollectionExtension": + def _from_object(cls, stac_object: "STACObject_Type") -> "CollectionExtension": if not isinstance(stac_object, Collection): raise ValueError(f"This extension applies to Collections, not {cls}") return cls.from_collection(stac_object) @@ -104,7 +104,7 @@ class ItemExtension(STACObjectExtension): item: Item @classmethod - def _from_object(cls, stac_object: "STACObject") -> "ItemExtension": + def _from_object(cls, stac_object: "STACObject_Type") -> "ItemExtension": if not isinstance(stac_object, Item): raise ValueError(f"This extension applies to Items, not {cls}") return cls.from_item(stac_object) @@ -176,7 +176,7 @@ def remove_extension(self, extension_id: str) -> None: def get_extension_class( self, extension_id: str, - stac_object_class: Type["STACObject"]) -> Optional[Type[STACObjectExtension]]: + stac_object_class: Type["STACObject_Type"]) -> Optional[Type[STACObjectExtension]]: """Gets the extension class for a given stac object class if one exists, otherwise returns None """ @@ -211,7 +211,7 @@ def get_extension_class( return ext_class - def extend_object(self, extension_id: str, stac_object: "STACObject") -> STACObjectExtension: + def extend_object(self, extension_id: str, stac_object: "STACObject_Type") -> STACObjectExtension: """Returns the extension object for the given STACObject and the given extension_id """ @@ -223,7 +223,7 @@ def extend_object(self, extension_id: str, stac_object: "STACObject") -> STACObj return ext_class._from_object(stac_object) - def get_extended_object_links(self, stac_object: "STACObject") -> List[str]: + def get_extended_object_links(self, stac_object: "STACObject_Type") -> List[str]: if stac_object.stac_extensions is None: return [] return [ @@ -233,7 +233,7 @@ def get_extended_object_links(self, stac_object: "STACObject") -> List[str]: for link_rel in e_obj.extension_class._object_links() ] - def can_extend(self, extension_id: str, stac_object_class: Type["STACObject"]) -> bool: + def can_extend(self, extension_id: str, stac_object_class: Type["STACObject_Type"]) -> bool: """Returns True if the extension can extend the given object type. Args: @@ -256,7 +256,7 @@ def can_extend(self, extension_id: str, stac_object_class: Type["STACObject"]) - if issubclass(stac_object_class, e.stac_object_class) ]) - def enable_extension(self, extension_id: str, stac_object: "STACObject") -> None: + def enable_extension(self, extension_id: str, stac_object: "STACObject_Type") -> None: """Enables the extension for the given object. This will at least ensure the extension ID is in the object's "stac_extensions" diff --git a/pystac/layout.py b/pystac/layout.py index 01cc9ef99..b2ac8f750 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -7,10 +7,10 @@ import pystac as ps if TYPE_CHECKING: - from pystac.stac_object import STACObject - from pystac.catalog import Catalog - from pystac.collection import Collection - from pystac.item import Item + from pystac.stac_object import STACObject as STACObject_Type + from pystac.catalog import Catalog as Catalog_Type + from pystac.collection import Collection as Collection_Type + from pystac.item import Item as Item_Type class TemplateError(Exception): @@ -87,7 +87,7 @@ def __init__(self, template: str, defaults: Dict[str, str] = None) -> None: template_vars.append(v) self.template_vars = template_vars - def _get_template_value(self, stac_object: "STACObject", template_var: str) -> Any: + def _get_template_value(self, stac_object: "STACObject_Type", template_var: str) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: if isinstance(stac_object, ps.Item): # Datetime @@ -121,7 +121,7 @@ def _get_template_value(self, stac_object: "STACObject", template_var: str) -> A # Allow dot-notation properties for arbitrary object values. props = template_var.split('.') - prop_source: Optional[Union[STACObject, Dict[str, Any]]] = None + prop_source: Optional[Union[ps.STACObject, Dict[str, Any]]] = None error = TemplateError('Cannot find property {} on {} for template {}'.format( template_var, stac_object, self.template)) @@ -160,7 +160,7 @@ def _get_template_value(self, stac_object: "STACObject", template_var: str) -> A return v - def get_template_values(self, stac_object: "STACObject") -> Dict[str, Any]: + def get_template_values(self, stac_object: "STACObject_Type") -> Dict[str, Any]: """Gets a dictionary of template variables to values derived from the given stac_object. If the template vars cannot be found in the stac object, and defaults was supplied to this template, a default @@ -183,7 +183,7 @@ def get_template_values(self, stac_object: "STACObject") -> Dict[str, Any]: return OrderedDict([(k, self._get_template_value(stac_object, k)) for k in self.template_vars]) - def substitute(self, stac_object: "STACObject") -> str: + def substitute(self, stac_object: "STACObject_Type") -> str: """Substitutes the values derived from :meth:`~pystac.layout.LayoutTemplate.get_template_values` into the template string for this template. @@ -212,7 +212,10 @@ def substitute(self, stac_object: "STACObject") -> str: class HrefLayoutStrategy(ABC): """Base class for HREF Layout strategies.""" - def get_href(self, stac_object: "STACObject", parent_dir: str, is_root: bool = False) -> str: + def get_href(self, + stac_object: "STACObject_Type", + parent_dir: str, + is_root: bool = False) -> str: if isinstance(stac_object, ps.Item): return self.get_item_href(stac_object, parent_dir) elif isinstance(stac_object, ps.Collection): @@ -223,15 +226,15 @@ def get_href(self, stac_object: "STACObject", parent_dir: str, is_root: bool = F raise ps.STACError('Unknown STAC object type {}'.format(stac_object)) @abstractmethod - def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: pass @abstractmethod - def get_item_href(self, item: "Item", parent_dir: str) -> str: + def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: pass @@ -256,9 +259,9 @@ class CustomLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ def __init__(self, - catalog_func: Optional[Callable[["Catalog", str, bool], str]] = None, - collection_func: Optional[Callable[["Collection", str, bool], str]] = None, - item_func: Optional[Callable[["Item", str], str]] = None, + catalog_func: Optional[Callable[["Catalog_Type", str, bool], str]] = None, + collection_func: Optional[Callable[["Collection_Type", str, bool], str]] = None, + item_func: Optional[Callable[["Item_Type", str], str]] = None, fallback_strategy: Optional[HrefLayoutStrategy] = None): self.item_func = item_func self.collection_func = collection_func @@ -267,21 +270,21 @@ def __init__(self, fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: if self.catalog_func is not None: result = self.catalog_func(cat, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) - def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: if self.collection_func is not None: result = self.collection_func(col, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) - def get_item_href(self, item: "Item", parent_dir: str) -> str: + def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: if self.item_func is not None: result = self.item_func(item, parent_dir) if result is not None: @@ -328,7 +331,7 @@ def __init__(self, fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: if is_root or self.catalog_template is None: return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) else: @@ -338,7 +341,7 @@ def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> st return os.path.join(parent_dir, template_path) - def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: if is_root or self.collection_template is None: return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) else: @@ -348,7 +351,7 @@ def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) return os.path.join(parent_dir, template_path) - def get_item_href(self, item: "Item", parent_dir: str) -> str: + def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: if self.item_template is None: return self.fallback_strategy.get_item_href(item, parent_dir) else: @@ -373,7 +376,7 @@ class BestPracticesLayoutStrategy(HrefLayoutStrategy): All paths are appended to the parent directory. """ - def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> str: + def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: if is_root: cat_root = parent_dir else: @@ -381,7 +384,7 @@ def get_catalog_href(self, cat: "Catalog", parent_dir: str, is_root: bool) -> st return os.path.join(cat_root, cat.DEFAULT_FILE_NAME) - def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) -> str: + def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: if is_root: col_root = parent_dir else: @@ -389,7 +392,7 @@ def get_collection_href(self, col: "Collection", parent_dir: str, is_root: bool) return os.path.join(col_root, col.DEFAULT_FILE_NAME) - def get_item_href(self, item: "Item", parent_dir: str) -> str: + def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: item_root = os.path.join(parent_dir, '{}'.format(item.id)) return os.path.join(item_root, '{}.json'.format(item.id)) diff --git a/pystac/link.py b/pystac/link.py index 6c0aeff28..27e86a01b 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -6,10 +6,10 @@ from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) if TYPE_CHECKING: - from pystac.stac_object import STACObject as STACObjectType - from pystac.item import Item as ItemType - from pystac.catalog import Catalog as CatalogType - from pystac.collection import Collection as CollectionType + from pystac.stac_object import STACObject as STACObject_Type + from pystac.item import Item as Item_Type + from pystac.catalog import Catalog as Catalog_Type + from pystac.collection import Collection as Collection_Type HIERARCHICAL_LINKS = ['root', 'child', 'parent', 'collection', 'item', 'items'] @@ -58,18 +58,18 @@ class Link: """ def __init__(self, rel: str, - target: Union[str, "STACObjectType"], + target: Union[str, "STACObject_Type"], media_type: Optional[str] = None, title: Optional[str] = None, properties: Optional[Dict[str, Any]] = None) -> None: self.rel = rel - self.target: Union[str, "STACObjectType"] = target # An object or an href + self.target: Union[str, "STACObject_Type"] = target # An object or an href self.media_type = media_type self.title = title self.properties = properties - self.owner: Optional["STACObjectType"] = None + self.owner: Optional["STACObject_Type"] = None - def set_owner(self, owner: "STACObjectType") -> "Link": + def set_owner(self, owner: "STACObject_Type") -> "Link": """Sets the owner of this link. Args: @@ -150,7 +150,7 @@ def get_absolute_href(self) -> Optional[str]: def __repr__(self) -> str: return ''.format(self.rel, self.target) - def resolve_stac_object(self, root: Optional["CatalogType"] = None) -> "Link": + def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": """Resolves a STAC object from the HREF of this link, if the link is not already resolved. @@ -261,17 +261,17 @@ def from_dict(d: Dict[str, Any]) -> "Link": return Link(rel=rel, target=href, media_type=media_type, title=title, properties=properties) @staticmethod - def root(c: "CatalogType") -> "Link": + def root(c: "Catalog_Type") -> "Link": """Creates a link to a root Catalog or Collection.""" return Link('root', c, media_type='application/json') @staticmethod - def parent(c: "CatalogType") -> "Link": + def parent(c: "Catalog_Type") -> "Link": """Creates a link to a parent Catalog or Collection.""" return Link('parent', c, media_type='application/json') @staticmethod - def collection(c: "CollectionType") -> "Link": + def collection(c: "Collection_Type") -> "Link": """Creates a link to an item's Collection.""" return Link('collection', c, media_type='application/json') @@ -281,11 +281,11 @@ def self_href(href: str) -> "Link": return Link('self', href, media_type='application/json') @staticmethod - def child(c: "CatalogType", title: Optional[str] = None) -> "Link": + def child(c: "Catalog_Type", title: Optional[str] = None) -> "Link": """Creates a link to a child Catalog or Collection.""" return Link('child', c, title=title, media_type='application/json') @staticmethod - def item(item: "ItemType", title: Optional[str] = None) -> "Link": + def item(item: "Item_Type", title: Optional[str] = None) -> "Link": """Creates a link to an Item.""" return Link('item', item, title=title, media_type='application/json') diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index b434fd198..45d7dbad4 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -6,7 +6,7 @@ from pystac.extensions import Extensions if TYPE_CHECKING: - from pystac.stac_object import STACObjectType as STACObjectTypeType + from pystac.stac_object import STACObjectType as STACObjectType_Type @total_ordering @@ -122,7 +122,7 @@ class STACJSONDescription: custom_extensions (List[str]): List of custom extensions (URIs to JSON Schemas) used by this STAC Object. """ - def __init__(self, object_type: "STACObjectTypeType", version_range: STACVersionRange, + def __init__(self, object_type: "STACObjectType_Type", version_range: STACVersionRange, common_extensions: List[str], custom_extensions: List[str]) -> None: self.object_type = object_type self.version_range = version_range @@ -269,7 +269,7 @@ def _split_extensions(stac_extensions: List[str]) -> Tuple[List[str], List[str]] return (common_extensions, custom_extensions) -def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectTypeType": +def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectType_Type": """Determines the STACObjectType of the provided JSON dict. Args: diff --git a/pystac/stac_io.py b/pystac/stac_io.py index d00693b1e..135c889d4 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -9,8 +9,8 @@ import pystac.serialization if TYPE_CHECKING: - from pystac.stac_object import STACObject as STACObjectType - from pystac.catalog import Catalog as CatalogType + from pystac.stac_object import STACObject as STACObject_Type + from pystac.catalog import Catalog as Catalog_Type class STAC_IO: @@ -60,7 +60,7 @@ def default_write_text_method(uri: str, txt: str) -> None: @staticmethod def stac_object_from_dict(d: Dict[str, Any], href: Optional[str] = None, - root: Optional["CatalogType"] = None) -> "STACObjectType": + root: Optional["Catalog_Type"] = None) -> "STACObject_Type": return pystac.serialization.stac_object_from_dict(d, href, root) # This is set in __init__.py @@ -120,7 +120,7 @@ def read_json(cls, uri: str) -> Dict[str, Any]: return json.loads(STAC_IO.read_text(uri)) @classmethod - def read_stac_object(cls, uri: str, root: Optional["CatalogType"] = None) -> "STACObjectType": + def read_stac_object(cls, uri: str, root: Optional["Catalog_Type"] = None) -> "STACObject_Type": """Read a STACObject from a JSON file at the given URI. Args: diff --git a/pystac/stac_object.py b/pystac/stac_object.py index a027b3ecd..efe8dddea 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -10,8 +10,8 @@ from pystac.extensions import ExtensionError if TYPE_CHECKING: - from pystac.catalog import Catalog as CatalogType - from pystac.extensions.base import STACObjectExtension as STACObjectExtensionType + from pystac.catalog import Catalog as Catalog_Type + from pystac.extensions.base import STACObjectExtension as STACObjectExtension_Type class STACObjectType(str, Enum): @@ -185,7 +185,7 @@ def set_self_href(self, href: str) -> None: if root_link is not None and root_link.is_resolved(): cast(ps.Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) - def get_root(self) -> Optional["CatalogType"]: + def get_root(self) -> Optional["Catalog_Type"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the root for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'root'``. @@ -200,11 +200,11 @@ def get_root(self) -> Optional["CatalogType"]: root_link.resolve_stac_object() # Use set_root, so Catalogs can merge ResolvedObjectCache instances. self.set_root(cast(ps.Catalog, root_link.target)) - return cast("CatalogType", root_link.target) + return cast(ps.Catalog, root_link.target) else: return None - def set_root(self, root: Optional["CatalogType"]) -> None: + def set_root(self, root: Optional["Catalog_Type"]) -> None: """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -232,7 +232,7 @@ def set_root(self, root: Optional["CatalogType"]) -> None: self.add_link(new_root_link) root._resolved_objects.cache(self) - def get_parent(self) -> Optional["CatalogType"]: + def get_parent(self) -> Optional["Catalog_Type"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the parent for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'parent'``. @@ -248,7 +248,7 @@ def get_parent(self) -> Optional["CatalogType"]: else: return None - def set_parent(self, parent: Optional["CatalogType"]) -> None: + def set_parent(self, parent: Optional["Catalog_Type"]) -> None: """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -307,8 +307,8 @@ def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = STAC_IO.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) def full_copy(self, - root: Optional["CatalogType"] = None, - parent: Optional["CatalogType"] = None) -> "STACObject": + root: Optional["Catalog_Type"] = None, + parent: Optional["Catalog_Type"] = None) -> "STACObject": """Create a full copy of this STAC object and any stac objects linked to by this object. @@ -455,7 +455,7 @@ def from_file(cls, href: str) -> "STACObject": def from_dict(cls, d: Dict[str, Any], href: Optional[str] = None, - root: Optional["CatalogType"] = None, + root: Optional["Catalog_Type"] = None, migrate: bool = False) -> "STACObject": """Parses this STACObject from the passed in dictionary. @@ -487,7 +487,7 @@ class ExtensionIndex: def __init__(self, stac_object: STACObject) -> None: self.stac_object = stac_object - def __getitem__(self, extension_id: str) -> "STACObjectExtensionType": + def __getitem__(self, extension_id: str) -> "STACObjectExtension_Type": """Gets the extension object for the given extension. Returns: @@ -507,7 +507,7 @@ def __getitem__(self, extension_id: str) -> "STACObjectExtensionType": return ps.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) - def __getattr__(self, extension_id: str) -> "STACObjectExtensionType": + def __getattr__(self, extension_id: str) -> "STACObjectExtension_Type": """Gets an extension based on a dynamic attribute. This takes the attribute name and passes it to __getitem__. diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index e7c2b55aa..cb28695cb 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -6,8 +6,8 @@ from pystac.utils import make_absolute_href if TYPE_CHECKING: - from pystac.stac_object import STACObject as STACObjectType - from pystac.stac_object import STACObjectType as STACObjectTypeType + from pystac.stac_object import STACObject as STACObject_Type + from pystac.stac_object import STACObjectType as STACObjectType_Type class STACValidationError(Exception): @@ -28,7 +28,7 @@ def __init__(self, message: str, source: Optional[Any] = None): from pystac.validation.stac_validator import (STACValidator, JsonSchemaSTACValidator) -def validate(stac_object: "STACObjectType") -> List[Any]: +def validate(stac_object: "STACObject_Type") -> List[Any]: """Validates a :class:`~pystac.STACObject`. Args: @@ -50,7 +50,7 @@ def validate(stac_object: "STACObjectType") -> List[Any]: def validate_dict(stac_dict: Dict[str, Any], - stac_object_type: Optional["STACObjectTypeType"] = None, + stac_object_type: Optional["STACObjectType_Type"] = None, stac_version: Optional[str] = None, extensions: Optional[List[str]] = None, href: Optional[str] = None) -> List[Any]: From 330e1b71f94abc1bdb2fc4e56973789b0c6e2dd4 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sun, 25 Apr 2021 23:30:06 -0400 Subject: [PATCH 07/51] New extension architecture in place, refactor eo, file and label. Introdues "ExtensionHooks" which are not necessary for an extension but are registered if the extension needs to modify the behavor of PySTAC in it's processes. The two places extensions can currently hook into are during object resolution, where they can add link reltypes that indicate STACObject links based on the extension; also migration - the logic for migrating extensions will be moved from the migrate subpackage to each extension itself. --- pystac/__init__.py | 20 +- pystac/catalog.py | 2 +- pystac/extensions/__init__.py | 27 +-- pystac/extensions/base.py | 308 ++++---------------------- pystac/extensions/eo.py | 236 +++++++++++++------- pystac/extensions/file.py | 168 ++++---------- pystac/extensions/hooks.py | 56 +++++ pystac/extensions/label.py | 164 ++++++++------ pystac/extensions/pointcloud.py | 67 +++--- pystac/extensions/projection.py | 14 +- pystac/extensions/sar.py | 21 +- pystac/extensions/sat.py | 10 +- pystac/extensions/scientific.py | 37 ++-- pystac/extensions/single_file_stac.py | 273 +++++++++++------------ pystac/extensions/timestamps.py | 17 +- pystac/extensions/version.py | 25 ++- pystac/extensions/view.py | 45 ++-- pystac/item.py | 2 +- pystac/link.py | 2 +- pystac/serialization/__init__.py | 2 +- pystac/serialization/identify.py | 252 +++++++++++++++++---- pystac/serialization/migrate.py | 59 +---- pystac/stac_object.py | 138 ++++++------ pystac/utils.py | 30 ++- pystac/validation/schema_uri_map.py | 68 +++--- tests/extensions/test_eo.py | 86 +++---- tests/extensions/test_extensions.py | 10 +- tests/extensions/test_file.py | 40 ++-- tests/extensions/test_label.py | 80 +++---- tests/extensions/test_pointcloud.py | 4 +- tests/extensions/test_projection.py | 4 +- tests/extensions/test_sar.py | 6 +- tests/extensions/test_sat.py | 4 +- tests/extensions/test_scientific.py | 12 +- tests/extensions/test_timestamps.py | 6 +- tests/extensions/test_version.py | 18 +- tests/extensions/test_view.py | 8 +- tests/test_catalog.py | 6 +- tests/test_writing.py | 2 +- tests/utils/test_cases.py | 4 +- 40 files changed, 1134 insertions(+), 1199 deletions(-) create mode 100644 pystac/extensions/hooks.py diff --git a/pystac/__init__.py b/pystac/__init__.py index a3ff580be..ec3fd5119 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -24,7 +24,6 @@ class STACTypeError(Exception): from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore from pystac.stac_io import STAC_IO # type:ignore -from pystac.extensions import Extensions # type:ignore from pystac.stac_object import (STACObject, STACObjectType) # type:ignore from pystac.media_type import MediaType # type:ignore from pystac.link import (Link, HIERARCHICAL_LINKS) # type:ignore @@ -39,7 +38,7 @@ class STACTypeError(Exception): import pystac.validation -import pystac.extensions.base +import pystac.extensions.hooks import pystac.extensions.eo import pystac.extensions.label import pystac.extensions.pointcloud @@ -53,21 +52,8 @@ class STACTypeError(Exception): import pystac.extensions.view import pystac.extensions.file -STAC_EXTENSIONS: pystac.extensions.base.RegisteredSTACExtensions = pystac.extensions.base.RegisteredSTACExtensions( - [ - pystac.extensions.eo.EO_EXTENSION_DEFINITION, - pystac.extensions.label.LABEL_EXTENSION_DEFINITION, - pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_DEFINITION, - pystac.extensions.projection.PROJECTION_EXTENSION_DEFINITION, - pystac.extensions.sar.SAR_EXTENSION_DEFINITION, - pystac.extensions.sat.SAT_EXTENSION_DEFINITION, - pystac.extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION, - pystac.extensions.single_file_stac.SFS_EXTENSION_DEFINITION, - pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION, - pystac.extensions.version.VERSION_EXTENSION_DEFINITION, - pystac.extensions.view.VIEW_EXTENSION_DEFINITION, - pystac.extensions.file.FILE_EXTENSION_DEFINITION - ]) +EXTENSION_HOOKS: pystac.extensions.hooks.RegisteredExtensionHooks = pystac.extensions.hooks.RegisteredExtensionHooks( + [pystac.extensions.eo.EO_EXTENSION_HOOKS, pystac.extensions.label.LABEL_EXTENSION_HOOKS]) def read_file(href: str) -> STACObject: diff --git a/pystac/catalog.py b/pystac/catalog.py index 12c66014f..c17ba15ab 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -704,7 +704,7 @@ def validate_all(self) -> None: item.validate() def _object_links(self) -> List[str]: - return ['child', 'item'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) + return ['child', 'item'] + (ps.EXTENSION_HOOKS.get_extended_object_links(self) or []) def map_items( self, item_mapper: Callable[["Item_Type"], Union["Item_Type", diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index d8911d50f..46ff4be3c 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa -from enum import Enum class ExtensionError(Exception): @@ -7,26 +6,6 @@ class ExtensionError(Exception): """ pass - -class Extensions(str, Enum): - """Enumerates the IDs of common extensions.""" - def __str__(self) -> str: - return str(self.value) - - CHECKSUM = 'checksum' - COLLECTION_ASSETS = 'collection-assets' - DATACUBE = 'datacube' - EO = 'eo' - ITEM_ASSETS = 'item-assets' - LABEL = 'label' - POINTCLOUD = 'pointcloud' - PROJECTION = 'projection' - SAR = 'sar' - SAT = 'sat' - SCIENTIFIC = 'scientific' - SINGLE_FILE_STAC = 'single-file-stac' - TILED_ASSETS = 'tiled-assets' - TIMESTAMPS = 'timestamps' - VERSION = 'version' - VIEW = 'view' - FILE = 'file' +from pystac.extensions.eo import eo_ext # type:ignore +from pystac.extensions.file import file_ext # type:ignore +from pystac.extensions.label import label_ext # type:ignore diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 21499928f..872ba160f 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,281 +1,57 @@ -from abc import (ABC, abstractmethod) -from typing import Any, Iterable, List, Optional, TYPE_CHECKING, Type +from abc import ABC +from typing import Generic, Iterable, Optional, Dict, Any, Type, TypeVar -from pystac.catalog import Catalog -from pystac.collection import Collection -from pystac.item import Asset, Item -from pystac.extensions import ExtensionError +import pystac as ps -if TYPE_CHECKING: - from pystac.stac_object import STACObject as STACObject_Type +class ExtensionException(Exception): + pass -class STACObjectExtension(ABC): - @classmethod - def _from_object(cls, stac_object: "STACObject_Type") -> "STACObjectExtension": - ... - @classmethod - @abstractmethod - def _object_links(cls) -> List[str]: - raise NotImplementedError("_object_links") +P = TypeVar('P') - @classmethod - def enable_extension(cls, stac_object: "STACObject_Type") -> None: - """Enables the extension for the given stac_object. - Child classes can choose to override this method in order to - modify the stac_object when an extension is enabled. - """ - pass +class PropertiesExtension(ABC): + properties: Dict[str, Any] + additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + def _get_property(self, prop_name: str, typ: Type[P] = Any) -> Optional[P]: + result: Optional[typ] = self.properties.get(prop_name) + if result is not None: + return result + if self.additional_read_properties is not None: + for props in self.additional_read_properties: + result = props.get(prop_name) + if result is not None: + return result + return None -class ExtendedObject: - """ExtendedObject maps STACObject classes (Catalog, Collection and Item) to - extension classes (classes that implement one of CatalogExtension, CollectionExtension, - or ItemCollection). When an extension is registered with PySTAC it uses the registered - list of ExtendedObject to determine how to handle extending objects, e.g. when item.ext.label - is called, it searches for the ExtendedObject associated with the label extension that - maps Item to LabelItemExt. - - Args: - stac_object_class: The STAC object class that is being extended. - extension_class: The class of the extension, e.g. LabelItemExt - """ - def __init__(self, stac_object_class: Type["STACObject_Type"], - extension_class: Type[STACObjectExtension]): - if stac_object_class is Catalog: - if not issubclass(extension_class, CatalogExtension): - raise ExtensionError( - "Classes extending catalogs must inherit from CatalogExtension") - if stac_object_class is Collection: - if not issubclass(extension_class, CollectionExtension): - raise ExtensionError( - "Classes extending collections must inherit from CollectionExtension") - if stac_object_class is Item: - if not issubclass(extension_class, ItemExtension): - raise ExtensionError("Classes extending item must inherit from ItemExtension") - - self.stac_object_class = stac_object_class - self.extension_class = extension_class - - -class ExtensionDefinition: - """Defines an extension that can be registered with PySTAC. - - Args: - extension_id: The ID for the extension. This is the same idea that will appear in - the ``stac_extensions`` property of implementing objects, and will be used to refer - to the extension in PySTAC. - extended_objects (List[ExtendedObject]): The list of ExtendedObjects which map STACObject - types to their extension. Should only contain one entry per stac object type. - """ - def __init__(self, extension_id: str, extended_objects: List[ExtendedObject]): - self.extension_id = extension_id - self.extended_objects = extended_objects - - -class CatalogExtension(STACObjectExtension): - @classmethod - def _from_object(cls, stac_object: "STACObject_Type") -> "CatalogExtension": - if not isinstance(stac_object, Catalog): - raise ValueError(f"This extension applies to Catalogs, not {cls}") - return cls.from_catalog(stac_object) - - @classmethod - @abstractmethod - def from_catalog(cls, catalog: Catalog) -> "CatalogExtension": - raise NotImplementedError("from_catalog") - - -class CollectionExtension(STACObjectExtension): - @classmethod - def _from_object(cls, stac_object: "STACObject_Type") -> "CollectionExtension": - if not isinstance(stac_object, Collection): - raise ValueError(f"This extension applies to Collections, not {cls}") - return cls.from_collection(stac_object) - - @classmethod - @abstractmethod - def from_collection(cls, collection: Collection) -> "CollectionExtension": - raise NotImplementedError("from_collection") - - -class ItemExtension(STACObjectExtension): - item: Item - - @classmethod - def _from_object(cls, stac_object: "STACObject_Type") -> "ItemExtension": - if not isinstance(stac_object, Item): - raise ValueError(f"This extension applies to Items, not {cls}") - return cls.from_item(stac_object) - - @classmethod - @abstractmethod - def from_item(cls, item: Item) -> "ItemExtension": - raise NotImplementedError("from_item") - - def _set_property(self, - key: str, - value: Any, - asset: Optional[Asset], - pop_if_none: bool = True) -> None: - ''' - Set an Item or an Asset property. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - - If the passed value to set is None, the property key is removed from - the dictionary of properties. - - It's recommended to use this method from extensions, instead of implementing - the logic for that in the corresponding subclasses. - - Args: - key (str): The name of the property - value (Object): the value to set - asset: The Asset to modify. If None, the property will be set in the Item - pop_if_none: If True and the value is None, the property will be removed from - the item or asset properties. If this is False, a None value will be set - (which will translate into a null JSON value). There are some cases - where required properties can be nullable, in which case False should - be used. Defaults to True. - ''' - target = self.item.properties if asset is None else asset.properties - if value is None: - target.pop(key, None) + def _set_property(self, prop_name: str, v: Optional[Any], pop_if_none: bool = True) -> None: + if v is None and pop_if_none: + self.properties.pop(prop_name, None) else: - target[key] = value - + self.properties[prop_name] = v -class RegisteredSTACExtensions: - def __init__(self, extension_definitions: Iterable[ExtensionDefinition]): - self.extensions = dict([(e.extension_id, e) for e in extension_definitions]) - def is_registered_extension(self, extension_id: str) -> bool: - """Determines whether or not the given extension ID has been registered.""" - return extension_id in self.extensions +S = TypeVar('S', bound=ps.STACObject) - def get_registered_extensions(self) -> List[str]: - """Returns the list of registered extension IDs.""" - return list(self.extensions.keys()) - def add_extension(self, extension_definition: ExtensionDefinition) -> None: - e_id = extension_definition.extension_id - if e_id in self.extensions: - raise ExtensionError("ExtensionDefinition with id '{}' already exists.".format(e_id)) +class EnableExtensionMixin(Generic[S]): + obj: S + schema_uri: str - self.extensions[e_id] = extension_definition - - def remove_extension(self, extension_id: str) -> None: - """Remove an extension from PySTAC.""" - if extension_id not in self.extensions: - raise ExtensionError( - "ExtensionDefinition with id '{}' is not registered.".format(extension_id)) - del self.extensions[extension_id] - - def get_extension_class( - self, extension_id: str, - stac_object_class: Type["STACObject_Type"]) -> Optional[Type[STACObjectExtension]]: - """Gets the extension class for a given stac object class if one exists, otherwise - returns None - """ - ext = self.extensions.get(extension_id) - if ext is None: - raise ExtensionError("No ExtensionDefinition registered with id '{}'. " - "Is the extension ID correct, or are you forgetting to call " - "'add_extension' for a custom extension?".format(extension_id)) - - ext_classes = [ - e.extension_class for e in ext.extended_objects - if issubclass(stac_object_class, e.stac_object_class) - ] - - ext_class = None - if len(ext_classes) == 0: - return None - elif len(ext_classes) == 1: - ext_class = ext_classes[0] + def add_extension(self) -> None: + if self.obj.stac_extensions is None: + self.obj.stac_extensions = [self.schema_uri] else: - # Need to check collection extensions before catalogs. - sort_key = {} - for c in ext_classes: - for i, base_cl in enumerate([ItemExtension, CollectionExtension, CatalogExtension]): - if issubclass(c, base_cl): - sort_key[c] = i - break - if c not in sort_key: - sort_key[c] = -1 - - ext_class = sorted(ext_classes, key=lambda c: sort_key[c])[0] - - return ext_class - - def extend_object(self, extension_id: str, stac_object: "STACObject_Type") -> STACObjectExtension: - """Returns the extension object for the given STACObject and the given - extension_id - """ - ext_class = self.get_extension_class(extension_id, type(stac_object)) - - if ext_class is None: - raise ExtensionError("Extension '{}' does not extend objects of type {}".format( - extension_id, type(stac_object))) - - return ext_class._from_object(stac_object) - - def get_extended_object_links(self, stac_object: "STACObject_Type") -> List[str]: - if stac_object.stac_extensions is None: - return [] - return [ - link_rel for e_id in stac_object.stac_extensions if e_id in self.extensions - for e_obj in self.extensions[e_id].extended_objects - if issubclass(type(stac_object), e_obj.stac_object_class) - for link_rel in e_obj.extension_class._object_links() - ] - - def can_extend(self, extension_id: str, stac_object_class: Type["STACObject_Type"]) -> bool: - """Returns True if the extension can extend the given object type. - - Args: - extension_id (str): The extension ID to check. - stac_object_class: the class of the object to check. Will check against subclasses, - so will return the correct result even if the object is a subclass of Catalog, - Collection or Item. - - Returns: - bool - """ - ext = self.extensions.get(extension_id) - - # Check to make sure this is a registered extension. - if ext is None: - raise ExtensionError("'{}' is not a registered extension".format(extension_id)) - - return any([ - e.extension_class for e in ext.extended_objects - if issubclass(stac_object_class, e.stac_object_class) - ]) - - def enable_extension(self, extension_id: str, stac_object: "STACObject_Type") -> None: - """Enables the extension for the given object. - - This will at least ensure the extension ID is in the object's "stac_extensions" - property. Some extensions may make additional modifications to the object. - """ - # Check to make sure this is a registered extension. - if not self.is_registered_extension(extension_id): - raise ExtensionError("'{}' is not an extension " - "registered with PySTAC".format(extension_id)) - - ext_class = self.get_extension_class(extension_id, type(stac_object)) - - if ext_class is None: - raise ExtensionError("Extension '{}' does not extend objects of type {}".format( - extension_id, type(stac_object))) - - if stac_object.stac_extensions is None: - stac_object.stac_extensions = [extension_id] - elif extension_id not in stac_object.stac_extensions: - stac_object.stac_extensions.append(extension_id) - - ext_class.enable_extension(stac_object) + self.obj.stac_extensions.append(self.schema_uri) + + def remove_extension(self) -> None: + if self.obj.stac_extensions is not None: + self.obj.stac_extensions = [ + uri for uri in self.obj.stac_extensions if uri != self.schema_uri + ] + + @property + def has_extension(self) -> bool: + return (self.obj.stac_extensions is not None + and self.schema_uri in self.obj.stac_extensions) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 9ea49b08f..1026069c6 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -1,7 +1,19 @@ -from typing import Any, Dict, List, Optional, Tuple, cast -from pystac import Extensions -from pystac.item import Asset, Item -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) +import re +from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, Union, cast + +import pystac as ps +from pystac.extensions.base import EnableExtensionMixin, ExtensionException, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from pystac.utils import map_opt + + +T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) + +SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" + +BANDS_PROP = "eo:bands" +CLOUD_COVER_PROP = "eo:cloud_cover" class Band: @@ -206,7 +218,7 @@ def band_description(common_name: str) -> Optional[str]: return None -class EOItemExt(ItemExtension): +class EOExtension(Generic[T], PropertiesExtension): """EOItemExt is the extension of the Item in the eo extension which represents a snapshot of the earth for a single date and time. @@ -220,14 +232,6 @@ class EOItemExt(ItemExtension): Using EOItemExt to directly wrap an item will add the 'eo' extension ID to the item's stac_extensions. """ - def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.EO)] - elif str(Extensions.EO) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.EO)) - - self.item = item - def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: """Applies label extension properties to the extended Item. @@ -244,53 +248,16 @@ def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: def bands(self) -> Optional[List[Band]]: """Get or sets a list of :class:`~pystac.Band` objects that represent the available bands. - - Returns: - List[Band] """ - return self.get_bands() - - @bands.setter - def bands(self, v: List[Band]) -> None: - self.set_bands(v) - - def get_bands(self, asset: Optional[Asset] = None) -> Optional[List[Band]]: - """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 or - all the asset's eo bands - - Returns: - List[Band] - """ - bands: Optional[List[Dict[str, Any]]] = None - if asset is not None and 'eo:bands' in asset.properties: - bands = asset.properties.get('eo:bands') - 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: - asset_bands: List[Dict[str, Any]] = [] - for _, value in self.item.get_assets().items(): - if 'eo:bands' in value.properties: - asset_bands.extend(cast(List[Dict[str, Any]], value.properties.get('eo:bands'))) - if any(asset_bands): - bands = asset_bands - - if bands is not None: - return [Band(b) for b in bands] - return None + return self._get_bands() - def set_bands(self, bands: List[Band], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset bands. + def _get_bands(self) -> Optional[List[Band]]: + return map_opt(lambda bands: [Band(b) for b in bands], + self._get_property(BANDS_PROP, List[Dict[str, Any]])) - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - band_dicts = [b.to_dict() for b in bands] - self._set_property('eo:bands', band_dicts, asset) + @bands.setter + def bands(self, v: Optional[List[Band]]) -> None: + self._set_property(BANDS_PROP, map_opt(lambda bands: [b.to_dict() for b in bands], v)) @property def cloud_cover(self) -> Optional[float]: @@ -300,45 +267,144 @@ def cloud_cover(self) -> Optional[float]: Returns: float or None """ - return self.get_cloud_cover() + return self._get_property(CLOUD_COVER_PROP, float) @cloud_cover.setter def cloud_cover(self, v: Optional[float]) -> None: - self.set_cloud_cover(v) + self._set_property(CLOUD_COVER_PROP, v) - def get_cloud_cover(self, asset: Optional[Asset] = None) -> Optional[float]: - """Gets an Item or an Asset cloud_cover. - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value +class ItemEOExtension(EnableExtensionMixin[ps.Item], EOExtension[ps.Item]): + schema_uri = SCHEMA_URI - Returns: - float + def __init__(self, item: ps.Item): + self.obj = item + self.properties = item.properties + + def _get_bands(self) -> Optional[List[Band]]: + """Get or sets a list of :class:`~pystac.Band` objects that represent + the available bands. """ - if asset is None or 'eo:cloud_cover' not in asset.properties: - return self.item.properties.get('eo:cloud_cover') - else: - return asset.properties.get('eo:cloud_cover') + bands = self._get_property(BANDS_PROP, List[Dict[str, Any]]) - def set_cloud_cover(self, cloud_cover: Optional[float], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset cloud_cover. + # get assets with eo:bands even if not in item + if bands is None: + asset_bands: List[Dict[str, Any]] = [] + for _, value in self.obj.get_assets().items(): + if BANDS_PROP in value.properties: + asset_bands.extend(cast(List[Dict[str, Any]], value.properties.get(BANDS_PROP))) + if any(asset_bands): + bands = asset_bands - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('eo:cloud_cover', cloud_cover, asset) + if bands is not None: + return [Band(b) for b in bands] + return None def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.obj.id) - @classmethod - def _object_links(cls) -> List[str]: - return [] - @classmethod - def from_item(cls, item: Item) -> "EOItemExt": - return cls(item) +class AssetEOExtension(EOExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner: + self.additional_read_properties = [asset.owner.properties] - -EO_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.EO, [ExtendedObject(Item, EOItemExt)]) + def __repr__(self) -> str: + return ''.format(self.asset_href) + + +def eo_ext(obj: Union[ps.Item, ps.Asset]) -> EOExtension[T]: + if isinstance(obj, ps.Item): + return ItemEOExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetEOExtension(obj) + else: + raise ExtensionException(f"EO extension does not apply to type {type(obj)}") + +class EOExtensionHooks(ExtensionHooks): + schema_uri = SCHEMA_URI + + def migrate(self, d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + added_extensions: Set[str] = set([]) + if version < '0.5': + if 'eo:crs' in d['properties']: + # Try to pull out the EPSG code. + # Otherwise, just leave it alone. + wkt = d['properties']['eo:crs'] + matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) + if len(matches) > 0: + epsg_code = matches[-1].group(1) + d['properties'].pop('eo:crs') + d['properties']['eo:epsg'] = int(epsg_code) + + if version < '0.6': + # Change eo:bands from a dict to a list. eo:bands on an asset + # is an index instead of a dict key. eo:bands is in properties. + bands_dict = d['eo:bands'] + keys_to_indices: Dict[str, int] = {} + bands: List[Dict[str, Any]] = [] + for i, (k, band) in enumerate(bands_dict.items()): + keys_to_indices[k] = i + bands.append(band) + + d.pop('eo:bands') + d['properties']['eo:bands'] = bands + for k, asset in d['assets'].items(): + if 'eo:bands' in asset: + asset_band_indices: List[int] = [] + for bk in asset['eo:bands']: + asset_band_indices.append(keys_to_indices[bk]) + asset['eo:bands'] = sorted(asset_band_indices) + + if version < '0.9': + # Some eo fields became common_metadata + if 'eo:platform' in d['properties'] and 'platform' not in d['properties']: + d['properties']['platform'] = d['properties']['eo:platform'] + del d['properties']['eo:platform'] + + if 'eo:instrument' in d['properties'] and 'instruments' not in d['properties']: + d['properties']['instruments'] = [d['properties']['eo:instrument']] + del d['properties']['eo:instrument'] + + if 'eo:constellation' in d['properties'] and 'constellation' not in d['properties']: + d['properties']['constellation'] = d['properties']['eo:constellation'] + del d['properties']['eo:constellation'] + + # Some eo fields became view extension fields + eo_to_view_fields = [ + 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' + ] + + view_enabled = 'view' in d['stac_extensions'] + for field in eo_to_view_fields: + if 'eo:{}'.format(field) in d['properties']: + if not view_enabled: + added_extensions.add('view') + view_enabled = True + if not 'view:{}'.format(field) in d['properties']: + d['properties']['view:{}'.format(field)] = \ + d['properties']['eo:{}'.format(field)] + del d['properties']['eo:{}'.format(field)] + + if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: + # gsd moved from eo to common metadata + if 'eo:gsd' in d['properties']: + d['properties']['gsd'] = d['properties']['eo:gsd'] + del d['properties']['eo:gsd'] + + # The way bands were declared in assets changed. + # In 1.0.0-beta.1 they are inlined into assets as + # opposed to having indices back into a property-level array. + if 'eo:bands' in d['properties']: + bands = d['properties']['eo:bands'] + for asset in d['assets'].values(): + if 'eo:bands' in asset: + new_bands: List[Dict[str, Any]] = [] + for band_index in asset['eo:bands']: + new_bands.append(bands[band_index]) + asset['eo:bands'] = new_bands + +EO_EXTENSION_HOOKS: ExtensionHooks = EOExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index fbb5fb30d..3c3b06cbe 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,12 +1,24 @@ import enum -from typing import Any, List, Optional +from typing import Any, Generic, List, Optional, TypeVar, Union -from pystac import Extensions -from pystac.item import Asset, Item -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) +import pystac as ps +from pystac.extensions.base import EnableExtensionMixin, ExtensionException, PropertiesExtension +from pystac.utils import map_opt +T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) + +SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" + +DATA_TYPE_PROP = 'file:data_type' +SIZE_PROP = "file:size" +NODATA_PROP = 'file:nodata' +CHECKSUM_PROP = 'file:checksum' + + +class FileDataType(str, enum.Enum): + def __str__(self) -> str: + return str(self.value) -class FileDataType(enum.Enum): INT8 = "int8" INT16 = "int16" INT32 = "int32" @@ -25,7 +37,7 @@ class FileDataType(enum.Enum): OTHER = "other" -class FileItemExt(ItemExtension): +class FileExtension(Generic[T], PropertiesExtension): """FileItemExt is the extension of the Item in the file extension which adds file related details such as checksum, data type and size for assets. @@ -39,14 +51,6 @@ class FileItemExt(ItemExtension): Using FileItemExt to directly wrap an item will add the 'file' extension ID to the item's stac_extensions. """ - def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.FILE)] - elif str(Extensions.FILE) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.FILE)) - - self.item = item - def apply(self, data_type: Optional[FileDataType] = None, size: Optional[int] = None, @@ -73,40 +77,11 @@ def data_type(self) -> Optional[FileDataType]: Returns: FileDataType """ - return self.get_data_type() + return map_opt(lambda s: FileDataType(s), self._get_property(DATA_TYPE_PROP, str)) @data_type.setter def data_type(self, v: Optional[FileDataType]) -> None: - self.set_data_type(v) - - def get_data_type(self, asset: Optional[Asset] = None) -> Optional[FileDataType]: - """Gets an Item or an Asset data_type. - - If an Asset is supplied and the data_type property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - FileDataType - """ - if asset is not None and 'file:data_type' in asset.properties: - data_type = asset.properties.get('file:data_type') - else: - data_type = self.item.properties.get('file:data_type') - - if data_type is not None: - return FileDataType(data_type) - return None - - def set_data_type(self, - data_type: Optional[FileDataType], - asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset data_type. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - v = None if data_type is None else data_type.value - self._set_property('file:data_type', v, asset) + self._set_property(DATA_TYPE_PROP, str(v)) @property def size(self) -> Optional[int]: @@ -115,64 +90,20 @@ def size(self) -> Optional[int]: Returns: int or None """ - return self.get_size() + return self._get_property(SIZE_PROP, int) @size.setter def size(self, v: Optional[int]) -> None: - self.set_size(v) - - def get_size(self, asset: Optional[Asset] = None) -> Optional[int]: - """Gets an Item or an Asset file size. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'file:size' not in asset.properties: - return self.item.properties.get('file:size') - else: - return asset.properties.get('file:size') - - def set_size(self, size: Optional[int], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset size. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('file:size', size, asset) + self._set_property(SIZE_PROP, v) @property def nodata(self) -> Optional[List[Any]]: """Get or sets the no data values""" - return self.get_nodata() + return self._get_property(NODATA_PROP, List[Any]) @nodata.setter def nodata(self, v: Optional[List[Any]]) -> None: - self.set_nodata(v) - - def get_nodata(self, asset: Optional[Asset] = None) -> Optional[List[Any]]: - """Gets an Item or an Asset nodata values. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - list[object] - """ - if asset is None or 'file:nodata' not in asset.properties: - return self.item.properties.get('file:nodata') - else: - return asset.properties.get('file:nodata') - - def set_nodata(self, nodata: Optional[List[Any]], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset nodata values. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('file:nodata', nodata, asset) + self._set_property(NODATA_PROP, v) @property def checksum(self) -> Optional[str]: @@ -181,42 +112,39 @@ def checksum(self) -> Optional[str]: Returns: str or None """ - return self.get_checksum() + return self._get_property(CHECKSUM_PROP, str) @checksum.setter def checksum(self, v: Optional[str]) -> None: - self.set_checksum(v) - - def get_checksum(self, asset: Optional[Asset] = None) -> Optional[str]: - """Gets an Item or an Asset checksum. + self._set_property(CHECKSUM_PROP, v) - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - """ - if asset is None or 'file:checksum' not in asset.properties: - return self.item.properties.get('file:checksum') - else: - return asset.properties.get('file:checksum') - def set_checksum(self, checksum: Optional[str], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset checksum. +class ItemFileExtension(EnableExtensionMixin[ps.Item], FileExtension[ps.Item]): + schema_uri = SCHEMA_URI - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('file:checksum', checksum, asset) + def __init__(self, item: ps.Item): + self.obj = item + self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.obj.id) - @classmethod - def _object_links(cls) -> List[str]: - return [] - @classmethod - def from_item(cls, item: Item) -> "FileItemExt": - return cls(item) +class AssetFileExtension(FileExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner: + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) -FILE_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.FILE, [ExtendedObject(Item, FileItemExt)]) +def file_ext(obj: Union[ps.Item, ps.Asset]) -> FileExtension[T]: + if isinstance(obj, ps.Item): + return ItemFileExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetFileExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py new file mode 100644 index 000000000..81367927a --- /dev/null +++ b/pystac/extensions/hooks.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING + +from pystac.extensions import ExtensionError + +if TYPE_CHECKING: + from pystac.stac_object import STACObject as STACObject_Type + + +class ExtensionHooks(ABC): + """ + + Args: + extension_schema_uri: The Schema URI that is used to validate the extension, + but also act as the ID for the extension. + """ + @property + @abstractmethod + def schema_uri(self) -> str: + pass + + def get_object_links(self, obj: "STACObject_Type") -> Optional[List[str]]: + return None + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + pass + +class RegisteredExtensionHooks: + def __init__(self, hooks: Iterable[ExtensionHooks]): + self.hooks = dict([(e.schema_uri, e) for e in hooks]) + + def add_extension_hooks(self, hooks: ExtensionHooks) -> None: + e_id = hooks.schema_uri + if e_id in self.hooks: + raise ExtensionError("ExtensionDefinition with id '{}' already exists.".format(e_id)) + + self.hooks[e_id] = hooks + + def get_extended_object_links(self, obj: "STACObject_Type") -> List[str]: + result: Optional[List[str]] = None + for ext in obj.stac_extensions: + if ext in self.hooks: + ext_result = self.hooks[ext].get_object_links(obj) + if ext_result is not None: + if result is None: + result = ext_result + else: + result.extend(ext_result) + return result or [] + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + for hooks in self.hooks.values(): + hooks.migrate(obj, version, info) diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index d1902610a..3ceb8147e 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -1,14 +1,14 @@ """STAC Model classes for Label extension. """ from enum import Enum -from pystac.media_type import MediaType from typing import Any, Dict, Iterable, List, Optional, Union, cast -from pystac import STACError -from pystac.extensions import Extensions -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) -from pystac.item import (Item, Asset) -from pystac.link import Link +import pystac as ps +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from pystac.extensions.base import EnableExtensionMixin +from pystac.extensions.hooks import ExtensionHooks + +SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json" class LabelType(str, Enum): @@ -71,13 +71,13 @@ def classes(self) -> Union[List[str], List[int], List[float]]: """ result = self.properties.get('classes') if result is None: - raise STACError(f'LabelClasses does not contain classes property: {self.properties}') + raise ps.STACError(f'LabelClasses does not contain classes property: {self.properties}') return result @classes.setter def classes(self, v: Union[List[str], List[int], List[float]]) -> None: if not type(v) is list: - raise STACError("classes must be a list! Invalid input: {}".format(v)) + raise ps.STACError("classes must be a list! Invalid input: {}".format(v)) self.properties['classes'] = v @@ -146,7 +146,7 @@ def name(self) -> str: """ result = self.properties.get('name') if result is None: - raise STACError(f"Label count has no name property: {self.properties}") + raise ps.STACError(f"Label count has no name property: {self.properties}") return result @name.setter @@ -162,7 +162,7 @@ def count(self) -> int: """ result = self.properties.get('count') if result is None: - raise STACError(f"Label count has no count property: {self.properties}") + raise ps.STACError(f"Label count has no count property: {self.properties}") return result @count.setter @@ -217,7 +217,7 @@ def name(self) -> str: """ result = self.properties.get('name') if result is None: - raise STACError(f"Label statistics has no name property: {self.properties}") + raise ps.STACError(f"Label statistics has no name property: {self.properties}") return result @name.setter @@ -233,7 +233,7 @@ def value(self) -> float: """ result = self.properties.get('value') if result is None: - raise STACError(f"Label statistics has no value property: {self.properties}") + raise ps.STACError(f"Label statistics has no value property: {self.properties}") return result @value.setter @@ -332,7 +332,7 @@ def counts(self, v: Optional[List[LabelCount]]) -> None: self.properties.pop('counts', None) else: if not isinstance(v, list): - raise STACError("counts must be a list! Invalid input: {}".format(v)) + raise ps.STACError("counts must be a list! Invalid input: {}".format(v)) self.properties['counts'] = [c.to_dict() for c in v] @@ -356,7 +356,7 @@ def statistics(self, v: Optional[List[LabelStatistics]]) -> None: self.properties.pop('statistics', None) else: if not isinstance(v, list): - raise STACError("statistics must be a list! Invalid input: {}".format(v)) + raise ps.STACError("statistics must be a list! Invalid input: {}".format(v)) self.properties['statistics'] = [s.to_dict() for s in v] @@ -403,7 +403,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class LabelItemExt(ItemExtension): +class ItemLabelExtension(EnableExtensionMixin[ps.Item]): """A LabelItemExt is the extension of the Item in the label extension which represents a polygon, set of polygons, or raster data defining labels and label metadata and should be part of a Collection. @@ -422,13 +422,9 @@ class LabelItemExt(ItemExtension): the item's stac_extensions. """ # noqa E501 - def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.LABEL)] - elif str(Extensions.LABEL) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.LABEL)) - - self.item = item + def __init__(self, item: ps.Item) -> None: + self.obj = item + self.schema_uri = SCHEMA_URI def apply(self, label_description: str, @@ -476,31 +472,31 @@ def label_description(self) -> str: Returns: str """ - result = self.item.properties.get('label:description') + result = self.obj.properties.get('label:description') if result is None: - raise STACError(f"label:description not set for item {self.item.id}") + raise ps.STACError(f"label:description not set for item {self.obj.id}") return result @label_description.setter def label_description(self, v: str) -> None: - self.item.properties['label:description'] = v + self.obj.properties['label:description'] = v @property def label_type(self) -> LabelType: """Gets or sets an ENUM of either vector label type or raster label type. """ - result = self.item.properties.get('label:type') + result = self.obj.properties.get('label:type') if result is None: - raise STACError(f"label:type is not set for item {self.item.id}") + raise ps.STACError(f"label:type is not set for item {self.obj.id}") return LabelType(result) @label_type.setter def label_type(self, v: LabelType) -> None: if v not in LabelType.ALL: - raise STACError("label_type must be one of " - "{}. Invalid input: {}".format(LabelType.ALL, v)) + raise ps.STACError("label_type must be one of " + "{}. Invalid input: {}".format(LabelType.ALL, v)) - self.item.properties['label:type'] = v + self.obj.properties['label:type'] = v @property def label_properties(self) -> Optional[List[str]]: @@ -514,15 +510,15 @@ def label_properties(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.item.properties.get('label:properties') + return self.obj.properties.get('label:properties') @label_properties.setter def label_properties(self, v: Optional[List[str]]) -> None: if v is not None: if not isinstance(v, list): - raise STACError("label_properties must be a list! Invalid input: {}".format(v)) + raise ps.STACError("label_properties must be a list! Invalid input: {}".format(v)) - self.item.properties['label:properties'] = v + self.obj.properties['label:properties'] = v @property def label_classes(self) -> Optional[List[LabelClasses]]: @@ -534,7 +530,7 @@ def label_classes(self) -> Optional[List[LabelClasses]]: Returns: List[LabelClasses] or None """ - label_classes = self.item.properties.get('label:classes') + label_classes = self.obj.properties.get('label:classes') if label_classes is not None: return [LabelClasses(classes) for classes in label_classes] else: @@ -543,13 +539,13 @@ def label_classes(self) -> Optional[List[LabelClasses]]: @label_classes.setter def label_classes(self, v: Optional[List[LabelClasses]]) -> None: if v is None: - self.item.properties.pop('label:classes', None) + self.obj.properties.pop('label:classes', None) else: if not isinstance(v, list): - raise STACError("label_classes must be a list! Invalid input: {}".format(v)) + raise ps.STACError("label_classes must be a list! Invalid input: {}".format(v)) classes = [x.to_dict() for x in v] - self.item.properties['label:classes'] = classes + self.obj.properties['label:classes'] = classes @property def label_tasks(self) -> Optional[List[str]]: @@ -559,17 +555,17 @@ def label_tasks(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.item.properties.get('label:tasks') + return self.obj.properties.get('label:tasks') @label_tasks.setter def label_tasks(self, v: Optional[List[str]]) -> None: if v is None: - self.item.properties.pop('label:tasks', None) + self.obj.properties.pop('label:tasks', None) else: if not isinstance(v, list): - raise STACError("label_tasks must be a list! Invalid input: {}".format(v)) + raise ps.STACError("label_tasks must be a list! Invalid input: {}".format(v)) - self.item.properties['label:tasks'] = v + self.obj.properties['label:tasks'] = v @property def label_methods(self) -> Optional[List[str]]: @@ -579,17 +575,17 @@ def label_methods(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.item.properties.get('label:methods') + return self.obj.properties.get('label:methods') @label_methods.setter def label_methods(self, v: Optional[List[str]]) -> None: if v is None: - self.item.properties.pop('label:methods', None) + self.obj.properties.pop('label:methods', None) else: if not isinstance(v, list): - raise STACError("label_methods must be a list! Invalid input: {}".format(v)) + raise ps.STACError("label_methods must be a list! Invalid input: {}".format(v)) - self.item.properties['label:methods'] = v + self.obj.properties['label:methods'] = v @property def label_overviews(self) -> Optional[List[LabelOverview]]: @@ -600,7 +596,7 @@ def label_overviews(self) -> Optional[List[LabelOverview]]: Returns: List[LabelOverview] or None """ - overviews = self.item.properties.get('label:overviews') + overviews = self.obj.properties.get('label:overviews') if overviews is not None: return [LabelOverview(overview) for overview in overviews] else: @@ -609,19 +605,19 @@ def label_overviews(self) -> Optional[List[LabelOverview]]: @label_overviews.setter def label_overviews(self, v: Optional[List[LabelOverview]]) -> None: if v is None: - self.item.properties.pop('label:overviews', None) + self.obj.properties.pop('label:overviews', None) else: if not isinstance(v, list): - raise STACError("label_overviews must be a list! Invalid input: {}".format(v)) + raise ps.STACError("label_overviews must be a list! Invalid input: {}".format(v)) overviews = [x.to_dict() for x in v] - self.item.properties['label:overviews'] = overviews + self.obj.properties['label:overviews'] = overviews def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.obj.id) def add_source(self, - source_item: Item, + source_item: ps.Item, title: Optional[str] = None, assets: Optional[List[str]] = None) -> None: """Adds a link to a source item. @@ -635,14 +631,14 @@ def add_source(self, properties = None if assets is not None: properties = {'label:assets': assets} - link = Link('source', - source_item, - title=title, - media_type='application/json', - properties=properties) - self.item.add_link(link) - - def get_sources(self) -> Iterable[Item]: + link = ps.Link('source', + source_item, + title=title, + media_type='application/json', + properties=properties) + self.obj.add_link(link) + + def get_sources(self) -> Iterable[ps.Item]: """Gets any source items that describe the source imagery used to generate this LabelItem. @@ -650,7 +646,7 @@ def get_sources(self) -> Iterable[Item]: Generator[Items]: A possibly empty list of source imagery items. Determined by links of this LabelItem that have ``rel=='source'``. """ - return map(lambda x: cast(Item, x), self.item.get_stac_objects('source')) + return map(lambda x: cast(ps.Item, x), self.obj.get_stac_objects('source')) def add_labels(self, href: str, @@ -669,8 +665,9 @@ def add_labels(self, object JSON. """ - self.item.add_asset( - "labels", Asset(href=href, title=title, media_type=media_type, properties=properties)) + self.obj.add_asset( + "labels", ps.Asset(href=href, title=title, media_type=media_type, + properties=properties)) def add_geojson_labels(self, href: str, @@ -685,16 +682,41 @@ def add_geojson_labels(self, extensions as a way to serialize and deserialize properties on asset object JSON. """ - self.add_labels(href, title=title, properties=properties, media_type=MediaType.GEOJSON) + self.add_labels(href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON) - @classmethod - def _object_links(cls) -> List[str]: - return ['source'] +def label_ext(item: ps.Item) -> ItemLabelExtension: + return ItemLabelExtension(item) - @classmethod - def from_item(cls, item: Item) -> "LabelItemExt": - return cls(item) + +class LabelExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + + def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: + if isinstance(so, ps.Item): + return ['source'] + return None + + def migrate(self, d: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': + props = d['properties'] + # Migrate 0.8.0-rc1 non-pluralized forms + # As it's a common mistake, convert for any pre-1.0.0 version. + if 'label:property' in props and 'label:properties' not in props: + props['label:properties'] = props['label:property'] + del props['label:property'] + + if 'label:task' in props and 'label:tasks' not in props: + props['label:tasks'] = props['label:task'] + del props['label:task'] + + if 'label:overview' in props and 'label:overviews' not in props: + props['label:overviews'] = props['label:overview'] + del props['label:overview'] + + if 'label:method' in props and 'label:methods' not in props: + props['label:methods'] = props['label:method'] + del props['label:method'] -LABEL_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.LABEL, [ExtendedObject(Item, LabelItemExt)]) +LABEL_EXTENSION_HOOKS: ExtensionHooks = LabelExtensionHooks() diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 5778149fd..dcb7b91f9 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -1,8 +1,12 @@ -from typing import Any, Dict, List, Optional -from pystac import Extensions, STACError -from pystac.item import Asset, Item -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) +from typing import Any, Dict, Generic, List, Optional, TypeVar, Union +import pystac as ps +from pystac.extensions.base import EnableExtensionMixin + + +T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) + +SCHEMA_URI = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" class PointcloudSchema: """Defines a schema for dimension of a pointcloud (e.g., name, size, type) @@ -49,13 +53,13 @@ def size(self) -> int: """ result = self.properties.get('size') if result is None: - raise STACError(f"Pointcloud schema does not have size property: {self.properties}") + raise ps.STACError(f"Pointcloud schema does not have size property: {self.properties}") return result @size.setter def size(self, v: int) -> None: if not isinstance(v, int): - raise STACError("size must be an int! Invalid input: {}".format(v)) + raise ps.STACError("size must be an int! Invalid input: {}".format(v)) self.properties['size'] = v @@ -68,7 +72,7 @@ def name(self) -> str: """ result = self.properties.get('name') if result is None: - raise STACError(f"Pointcloud schema does not have name property: {self.properties}") + raise ps.STACError(f"Pointcloud schema does not have name property: {self.properties}") return result @name.setter @@ -84,7 +88,7 @@ def type(self) -> str: """ result = self.properties.get('type') if result is None: - raise STACError(f"Pointcloud schema has no type property: {self.properties}") + raise ps.STACError(f"Pointcloud schema has no type property: {self.properties}") return result @type.setter @@ -186,7 +190,7 @@ def name(self) -> str: """ result = self.properties.get('name') if result is None: - raise STACError(f"Pointcloud statistics does not have name property: {self.properties}") + raise ps.STACError(f"Pointcloud statistics does not have name property: {self.properties}") return result @name.setter @@ -320,7 +324,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class PointcloudItemExt(ItemExtension): +class PointcloudExtension(Generic[T]): """PointcloudItemExt is the extension of an Item in the PointCloud Extension. The Pointclout extension adds pointcloud information to STAC Items. @@ -331,12 +335,7 @@ class PointcloudItemExt(ItemExtension): item (Item): The Item that is being extended. """ - def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.POINTCLOUD)] - elif str(Extensions.POINTCLOUD) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.POINTCLOUD)) - + def __init__(self, item: ps.Item) -> None: self.item = item def apply(self, @@ -383,7 +382,7 @@ def count(self) -> int: def count(self, v: int) -> None: self.set_count(v) - def get_count(self, asset: Optional[Asset] = None) -> int: + def get_count(self, asset: Optional[ps.Asset] = None) -> int: """Gets an Item or an Asset count. If an Asset is supplied and the Item property exists on the Asset, @@ -398,11 +397,11 @@ def get_count(self, asset: Optional[Asset] = None) -> int: result = asset.properties.get('pc:count') if result is None: - raise STACError(f"pc:count not found on point cloud item with ID {self.item.id}") + raise ps.STACError(f"pc:count not found on point cloud item with ID {self.item.id}") return result - def set_count(self, count: int, asset: Optional[Asset] = None) -> None: + def set_count(self, count: int, asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset count. If an Asset is supplied, sets the property on the Asset. @@ -423,7 +422,7 @@ def type(self) -> str: def type(self, v: str) -> None: self.set_type(v) - def get_type(self, asset: Optional[Asset] = None) -> str: + def get_type(self, asset: Optional[ps.Asset] = None) -> str: """Gets an Item or an Asset type. If an Asset is supplied and the Item property exists on the Asset, @@ -438,11 +437,11 @@ def get_type(self, asset: Optional[Asset] = None) -> str: result = asset.properties.get('pc:type') if result is None: - raise STACError(f"pc:type not found on point cloud item with ID {self.item.id}") + raise ps.STACError(f"pc:type not found on point cloud item with ID {self.item.id}") return result - def set_type(self, type: str, asset: Optional[Asset] = None) -> None: + def set_type(self, type: str, asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset type. If an Asset is supplied, sets the property on the Asset. @@ -466,7 +465,7 @@ def encoding(self) -> str: def encoding(self, v: str) -> None: self.set_encoding(v) - def get_encoding(self, asset: Optional[Asset] = None) -> str: + def get_encoding(self, asset: Optional[ps.Asset] = None) -> str: """Gets an Item or an Asset encoding. If an Asset is supplied and the Item property exists on the Asset, @@ -481,11 +480,11 @@ def get_encoding(self, asset: Optional[Asset] = None) -> str: result = asset.properties.get('pc:encoding') if result is None: - raise STACError(f"pc:encoding not found on point cloud item with ID {self.item.id}") + raise ps.STACError(f"pc:encoding not found on point cloud item with ID {self.item.id}") return result - def set_encoding(self, encoding: str, asset: Optional[Asset] = None) -> None: + def set_encoding(self, encoding: str, asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset encoding. If an Asset is supplied, sets the property on the Asset. @@ -510,7 +509,7 @@ def schemas(self) -> List[PointcloudSchema]: def schemas(self, v: List[PointcloudSchema]) -> None: self.set_schemas(v) - def get_schemas(self, asset: Optional[Asset] = None) -> List[PointcloudSchema]: + def get_schemas(self, asset: Optional[ps.Asset] = None) -> List[PointcloudSchema]: """Gets an Item or an Asset projection geometry. If an Asset is supplied and the Item property exists on the Asset, @@ -529,7 +528,7 @@ def get_schemas(self, asset: Optional[Asset] = None) -> List[PointcloudSchema]: else: return [PointcloudSchema(s) for s in schemas] - def set_schemas(self, schemas: List[PointcloudSchema], asset: Optional[Asset] = None) -> None: + def set_schemas(self, schemas: List[PointcloudSchema], asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset schema If an Asset is supplied, sets the property on the Asset. @@ -553,7 +552,7 @@ def density(self) -> Optional[float]: def density(self, v: Optional[float]) -> None: self.set_density(v) - def get_density(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_density(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset density. If an Asset is supplied and the Item property exists on the Asset, @@ -567,7 +566,7 @@ def get_density(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('pc:density') - def set_density(self, density: Optional[float], asset: Optional[Asset] = None) -> None: + def set_density(self, density: Optional[float], asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset density property. If an Asset is supplied, sets the property on the Asset. @@ -591,7 +590,7 @@ def statistics(self) -> Optional[List[PointcloudStatistic]]: def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None: self.set_statistics(v) - def get_statistics(self, asset: Optional[Asset] = None) -> Optional[List[PointcloudStatistic]]: + def get_statistics(self, asset: Optional[ps.Asset] = None) -> Optional[List[PointcloudStatistic]]: """Gets an Item or an Asset centroid. If an Asset is supplied and the Item property exists on the Asset, @@ -611,7 +610,7 @@ def get_statistics(self, asset: Optional[Asset] = None) -> Optional[List[Pointcl def set_statistics(self, statistics: Optional[List[PointcloudStatistic]], - asset: Optional[Asset] = None) -> None: + asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset centroid. If an Asset is supplied, sets the property on the Asset. @@ -627,9 +626,5 @@ def _object_links(cls) -> List[str]: return [] @classmethod - def from_item(cls, item: Item) -> "PointcloudItemExt": + def from_item(cls, item: ps.Item) -> "ItemPointcloudExtension": return cls(item) - - -POINTCLOUD_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.POINTCLOUD, [ExtendedObject(Item, PointcloudItemExt)]) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 3d6285f0f..2059841dd 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,10 +1,9 @@ from typing import Any, Dict, List, Optional -from pystac import Extensions + from pystac.item import Asset, Item -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) -class ProjectionItemExt(ItemExtension): +class ProjectionItemExt(): """ProjectionItemExt is the extension of an Item in the Projection Extension. The Projection extension adds projection information to STAC Items. @@ -19,11 +18,6 @@ class ProjectionItemExt(ItemExtension): the item's stac_extensions. """ def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.PROJECTION)] - elif str(Extensions.PROJECTION) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.PROJECTION)) - self.item = item def apply(self, @@ -416,7 +410,3 @@ def _object_links(cls) -> List[str]: @classmethod def from_item(cls, item: Item) -> "ProjectionItemExt": return cls(item) - - -PROJECTION_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.PROJECTION, [ExtendedObject(Item, ProjectionItemExt)]) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 35d61f97c..04a24dbcd 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -6,9 +6,8 @@ import enum from typing import List, Optional -import pystac -from pystac import Extensions, STACError -from pystac.extensions import base +import pystac as ps +from pystac import STACError # Required INSTRUMENT_MODE: str = 'sar:instrument_mode' @@ -52,7 +51,7 @@ class ObservationDirection(enum.Enum): # TODO: Fix to work with Assets -class SarItemExt(base.ItemExtension): +class SarItemExt(): """SarItemExt extends Item to add sar properties to a STAC Item. Args: @@ -65,9 +64,9 @@ class SarItemExt(base.ItemExtension): Using SarItemExt to directly wrap an item will add the 'sar' extension ID to the item's stac_extensions. """ - item: pystac.Item + item: ps.Item - def __init__(self, an_item: pystac.Item) -> None: + def __init__(self, an_item: ps.Item) -> None: self.item = an_item def apply(self, @@ -138,7 +137,7 @@ def apply(self, self.observation_direction = observation_direction @classmethod - def from_item(cls, an_item: pystac.Item) -> "SarItemExt": + def from_item(cls, an_item: ps.Item) -> "SarItemExt": return cls(an_item) @classmethod @@ -196,7 +195,7 @@ def polarizations(self) -> List[Polarization]: @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: if not isinstance(values, list): - raise pystac.STACError(f'polarizations must be a list. Invalid "{values}"') + raise ps.STACError(f'polarizations must be a list. Invalid "{values}"') self.item.properties[POLARIZATIONS] = [v.value for v in values] @property @@ -335,9 +334,3 @@ def observation_direction(self) -> Optional[ObservationDirection]: @observation_direction.setter def observation_direction(self, v: ObservationDirection) -> None: self.item.properties[OBSERVATION_DIRECTION] = v.value - - -SAR_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( - Extensions.SAR, [ - base.ExtendedObject(pystac.Item, SarItemExt), - ]) diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 1b670f40b..0fb387e8f 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -7,8 +7,6 @@ from typing import List, Optional import pystac -from pystac import Extensions -from pystac.extensions import base ORBIT_STATE: str = 'sat:orbit_state' RELATIVE_ORBIT: str = 'sat:relative_orbit' @@ -20,7 +18,7 @@ class OrbitState(enum.Enum): GEOSTATIONARY = 'geostationary' -class SatItemExt(base.ItemExtension): +class SatItemExt(): """SatItemExt extends Item to add sat properties to a STAC Item. Args: @@ -108,9 +106,3 @@ def relative_orbit(self, v: int) -> None: raise pystac.STACError(f'relative_orbit must be >= 0. Found {v}.') self.item.properties[RELATIVE_ORBIT] = v - - -SAT_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( - Extensions.SAT, [ - base.ExtendedObject(pystac.Item, SatItemExt), - ]) diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 52c740fb6..931b0cf9a 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -11,9 +11,7 @@ from typing import Any, Dict, List, Optional from urllib import parse -import pystac -from pystac import Extensions -from pystac.extensions import base +import pystac as ps # STAC fields strings. PREFIX: str = 'sci:' @@ -53,11 +51,11 @@ def to_dict(self) -> Dict[str, str]: def from_dict(d: Dict[str, str]) -> "Publication": return Publication(d['doi'], d['citation']) - def get_link(self) -> pystac.Link: - return pystac.Link(CITE_AS, doi_to_url(self.doi)) + def get_link(self) -> ps.Link: + return ps.Link(CITE_AS, doi_to_url(self.doi)) -def remove_link(links: List[pystac.Link], doi: str) -> None: +def remove_link(links: List[ps.Link], doi: str) -> None: url = doi_to_url(doi) for i, a_link in enumerate(links): if a_link.rel != CITE_AS: @@ -67,7 +65,7 @@ def remove_link(links: List[pystac.Link], doi: str) -> None: break -class ScientificItemExt(base.ItemExtension): +class ScientificItemExt(): """ScientificItemExt extends Item to add citations and DOIs to a STAC Item. Args: @@ -80,9 +78,9 @@ class ScientificItemExt(base.ItemExtension): Using ScientificItemExt to directly wrap an item will add the 'scientific' extension ID to the item's stac_extensions. """ - item: pystac.Item + item: ps.Item - def __init__(self, an_item: pystac.Item) -> None: + def __init__(self, an_item: ps.Item) -> None: self.item = an_item def apply(self, @@ -105,7 +103,7 @@ def apply(self, self.publications = publications @classmethod - def from_item(cls, an_item: pystac.Item) -> "ScientificItemExt": + def from_item(cls, an_item: ps.Item) -> "ScientificItemExt": return cls(an_item) @classmethod @@ -131,7 +129,7 @@ def doi(self, v: Optional[str]) -> None: if v is not None: self.item.properties[DOI] = v url = doi_to_url(v) - self.item.add_link(pystac.Link(CITE_AS, url)) + self.item.add_link(ps.Link(CITE_AS, url)) @property def citation(self) -> Optional[str]: @@ -197,7 +195,7 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: del self.item.properties[PUBLICATIONS] -class ScientificCollectionExt(base.CollectionExtension): +class ScientificCollectionExt(): """ScientificCollectionExt extends Collection to add citations and DOIs to a STAC Collection. Args: @@ -210,9 +208,9 @@ class ScientificCollectionExt(base.CollectionExtension): Using ScientificCollectionExt to directly wrap a collection will add the 'scientific' extension ID to the collection's stac_extensions. """ - collection: pystac.Collection + collection: ps.Collection - def __init__(self, a_collection: pystac.Collection): + def __init__(self, a_collection: ps.Collection): self.collection = a_collection def apply(self, @@ -235,7 +233,7 @@ def apply(self, self.publications = publications @classmethod - def from_collection(cls, a_collection: pystac.Collection) -> "ScientificCollectionExt": + def from_collection(cls, a_collection: ps.Collection) -> "ScientificCollectionExt": return cls(a_collection) @classmethod @@ -260,7 +258,7 @@ def doi(self, v: Optional[str]) -> None: if v is not None: self.collection.extra_fields[DOI] = v url = doi_to_url(v) - self.collection.add_link(pystac.Link(CITE_AS, url)) + self.collection.add_link(ps.Link(CITE_AS, url)) @property def citation(self) -> Optional[str]: @@ -322,10 +320,3 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: if not self.collection.extra_fields[PUBLICATIONS]: del self.collection.extra_fields[PUBLICATIONS] - - -SCIENTIFIC_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( - Extensions.SCIENTIFIC, [ - base.ExtendedObject(pystac.Item, ScientificItemExt), - base.ExtendedObject(pystac.Collection, ScientificCollectionExt) - ]) diff --git a/pystac/extensions/single_file_stac.py b/pystac/extensions/single_file_stac.py index d3f919d5f..32929ac9c 100644 --- a/pystac/extensions/single_file_stac.py +++ b/pystac/extensions/single_file_stac.py @@ -1,138 +1,135 @@ -from pystac.item import Item -from typing import Dict, List, Optional, cast - -import pystac as ps -from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) - - -def create_single_file_stac(catalog: ps.Catalog) -> ps.Catalog: - """Creates a Single File STAC from a STAC catalog. - - This method will recursively collect any collections and items in the catalog - and return a new catalog with the same properties as the old one, with cleared - links and the 'collections' and 'features' property of the Single File STAC holding - each of the respective collected STAC objects. - - Collections will be filtered to only those referenced by items via the collection_id. - All links in the items and collections will be cleared in the Single File STAC. - - Args: - catalog (Catalog): Catalog to walk while constructing the Single File STAC - """ - collections: Dict[str, ps.Collection] = {} - items: List[ps.Item] = [] - for root, _, cat_items in catalog.walk(): - if isinstance(root, ps.Collection): - new_collection = root.clone() - new_collection.clear_links() - collections[root.id] = new_collection - for item in cat_items: - new_item = item.clone() - new_item.clear_links() - items.append(new_item) - - filtered_collections: List[ps.Collection] = [] - for item in items: - if item.collection_id is not None and item.collection_id in collections: - filtered_collections.append(collections[item.collection_id]) - collections.pop(item.collection_id) - - result = catalog.clone() - result.clear_links() - result.ext.enable(ps.Extensions.SINGLE_FILE_STAC) - sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[ps.Extensions.SINGLE_FILE_STAC]) - sfs_ext.apply(features=items, collections=filtered_collections) - - return result - - -class SingleFileSTACCatalogExt(CatalogExtension): - """An extension of Catalog that provides a set of Collections and Items - as a single file catalog. A SingleFileSTAC - is a self contained catalog that contains everything that would normally be in a - linked set of STAC files. - - Args: - catalog (Catalog): The catalog to be extended. - - Attributes: - catalog (Catalog): The catalog that is being extended. - - Note: - Using SingleFileSTACCatalogExt to directly wrap a Catalog will - add the 'proj' extension ID to the catalog's stac_extensions. - """ - def __init__(self, catalog: ps.Catalog) -> None: - if catalog.stac_extensions is None: - catalog.stac_extensions = [str(ps.Extensions.SINGLE_FILE_STAC)] - elif str(ps.Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: - catalog.stac_extensions.append(str(ps.Extensions.SINGLE_FILE_STAC)) - - self.catalog = catalog - - def apply(self, - features: List[Item], - collections: Optional[List[ps.Collection]] = None) -> None: - """ - Args: - features (List[Item]): List of items contained by - this SingleFileSTAC. - collections (List[Collection]): Optional list of collections that are - used by any of the Items in the catalog. - """ - self.features = features - self.collections = collections - - @classmethod - def enable_extension(cls, stac_object: ps.STACObject) -> None: - # Ensure the 'type' property is correct so that the Catalog is valid GeoJSON. - if isinstance(stac_object, ps.Catalog): - stac_object.extra_fields['type'] = 'FeatureCollection' - - @property - def features(self) -> List[Item]: - """Get or sets a list of :class:`~pystac.Item` contained in this Single File STAC. - - Returns: - List[Item] - """ - features = self.catalog.extra_fields.get('features') - if features is None: - raise ps.STACError('Invalid Single File STAC: does not have "features" property.') - - return [Item.from_dict(feature) for feature in features] - - @features.setter - def features(self, v: List[Item]) -> None: - self.catalog.extra_fields['features'] = [item.to_dict() for item in v] - - @property - def collections(self) -> Optional[List[ps.Collection]]: - """Get or sets a list of :class:`~pystac.Collection` objects contained - in this Single File STAC. - """ - collections = self.catalog.extra_fields.get('collections') - - if collections is None: - return None - else: - return [ps.Collection.from_dict(col) for col in collections] - - @collections.setter - def collections(self, v: Optional[List[ps.Collection]]) -> None: - if v is not None: - self.catalog.extra_fields['collections'] = [col.to_dict() for col in v] - else: - self.catalog.extra_fields.pop('collections', None) - - @classmethod - def _object_links(cls) -> List[str]: - return [] - - @classmethod - def from_catalog(cls, catalog: ps.Catalog) -> "SingleFileSTACCatalogExt": - return SingleFileSTACCatalogExt(catalog) - - -SFS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - ps.Extensions.SINGLE_FILE_STAC, [ExtendedObject(ps.Catalog, SingleFileSTACCatalogExt)]) +# from pystac.item import Item +# from typing import Dict, List, Optional, cast + +# import pystac as ps +# from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) + +# def create_single_file_stac(catalog: ps.Catalog) -> ps.Catalog: +# """Creates a Single File STAC from a STAC catalog. + +# This method will recursively collect any collections and items in the catalog +# and return a new catalog with the same properties as the old one, with cleared +# links and the 'collections' and 'features' property of the Single File STAC holding +# each of the respective collected STAC objects. + +# Collections will be filtered to only those referenced by items via the collection_id. +# All links in the items and collections will be cleared in the Single File STAC. + +# Args: +# catalog (Catalog): Catalog to walk while constructing the Single File STAC +# """ +# collections: Dict[str, ps.Collection] = {} +# items: List[ps.Item] = [] +# for root, _, cat_items in catalog.walk(): +# if isinstance(root, ps.Collection): +# new_collection = root.clone() +# new_collection.clear_links() +# collections[root.id] = new_collection +# for item in cat_items: +# new_item = item.clone() +# new_item.clear_links() +# items.append(new_item) + +# filtered_collections: List[ps.Collection] = [] +# for item in items: +# if item.collection_id is not None and item.collection_id in collections: +# filtered_collections.append(collections[item.collection_id]) +# collections.pop(item.collection_id) + +# result = catalog.clone() +# result.clear_links() +# result.ext.enable(ps.Extensions.SINGLE_FILE_STAC) +# sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[ps.Extensions.SINGLE_FILE_STAC]) +# sfs_ext.apply(features=items, collections=filtered_collections) + +# return result + +# class SingleFileSTACCatalogExt(CatalogExtension): +# """An extension of Catalog that provides a set of Collections and Items +# as a single file catalog. A SingleFileSTAC +# is a self contained catalog that contains everything that would normally be in a +# linked set of STAC files. + +# Args: +# catalog (Catalog): The catalog to be extended. + +# Attributes: +# catalog (Catalog): The catalog that is being extended. + +# Note: +# Using SingleFileSTACCatalogExt to directly wrap a Catalog will +# add the 'proj' extension ID to the catalog's stac_extensions. +# """ +# def __init__(self, catalog: ps.Catalog) -> None: +# if catalog.stac_extensions is None: +# catalog.stac_extensions = [str(ps.Extensions.SINGLE_FILE_STAC)] +# elif str(ps.Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: +# catalog.stac_extensions.append(str(ps.Extensions.SINGLE_FILE_STAC)) + +# self.catalog = catalog + +# def apply(self, +# features: List[Item], +# collections: Optional[List[ps.Collection]] = None) -> None: +# """ +# Args: +# features (List[Item]): List of items contained by +# this SingleFileSTAC. +# collections (List[Collection]): Optional list of collections that are +# used by any of the Items in the catalog. +# """ +# self.features = features +# self.collections = collections + +# @classmethod +# def enable_extension(cls, stac_object: ps.STACObject) -> None: +# # Ensure the 'type' property is correct so that the Catalog is valid GeoJSON. +# if isinstance(stac_object, ps.Catalog): +# stac_object.extra_fields['type'] = 'FeatureCollection' + +# @property +# def features(self) -> List[Item]: +# """Get or sets a list of :class:`~pystac.Item` contained in this Single File STAC. + +# Returns: +# List[Item] +# """ +# features = self.catalog.extra_fields.get('features') +# if features is None: +# raise ps.STACError('Invalid Single File STAC: does not have "features" property.') + +# return [Item.from_dict(feature) for feature in features] + +# @features.setter +# def features(self, v: List[Item]) -> None: +# self.catalog.extra_fields['features'] = [item.to_dict() for item in v] + +# @property +# def collections(self) -> Optional[List[ps.Collection]]: +# """Get or sets a list of :class:`~pystac.Collection` objects contained +# in this Single File STAC. +# """ +# collections = self.catalog.extra_fields.get('collections') + +# if collections is None: +# return None +# else: +# return [ps.Collection.from_dict(col) for col in collections] + +# @collections.setter +# def collections(self, v: Optional[List[ps.Collection]]) -> None: +# if v is not None: +# self.catalog.extra_fields['collections'] = [col.to_dict() for col in v] +# else: +# self.catalog.extra_fields.pop('collections', None) + +# @classmethod +# def _object_links(cls) -> List[str]: +# return [] + +# @classmethod +# def from_catalog(cls, catalog: ps.Catalog) -> "SingleFileSTACCatalogExt": +# return SingleFileSTACCatalogExt(catalog) + +# SFS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( +# ps.Extensions.SINGLE_FILE_STAC, [ExtendedObject(ps.Catalog, SingleFileSTACCatalogExt)]) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 095bfab8f..73d0e2b88 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -1,14 +1,12 @@ from datetime import datetime as Datetime from typing import List, Optional -import pystac -from pystac import Extensions -from pystac.extensions.base import (ExtendedObject, ExtensionDefinition, ItemExtension) +import pystac as ps from pystac.item import Asset, Item from pystac.utils import datetime_to_str, str_to_datetime -class TimestampsItemExt(ItemExtension): +class TimestampsItemExt(): """TimestampsItemExt is the extension of an Item in that allows to specify additional timestamps for assets and metadata. @@ -23,11 +21,6 @@ class TimestampsItemExt(ItemExtension): extension ID to the item's stac_extensions. """ def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.TIMESTAMPS)] - elif str(Extensions.TIMESTAMPS) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.TIMESTAMPS)) - self.item = item @classmethod @@ -53,7 +46,7 @@ def apply(self, was unpublished. """ if published is None and expires is None and unpublished is None: - raise pystac.STACError("timestamps extension needs at least one property value.") + raise ps.STACError("timestamps extension needs at least one property value.") self.published = published self.expires = expires @@ -195,7 +188,3 @@ def set_unpublished(self, Otherwise sets the Item's value. """ self._timestamp_setter(unpublished, 'unpublished', asset) - - -TIMESTAMPS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.TIMESTAMPS, [ExtendedObject(Item, TimestampsItemExt)]) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index cf95ac9bb..c15e0b3e8 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -5,11 +5,13 @@ Note that the version/schema.json does not know about the links. """ +from pystac.extensions.hooks import ExtensionHooks from typing import List, Optional, cast import pystac as ps -from pystac import Extensions, STACError -from pystac.extensions import base +from pystac import STACError + +VERSION_EXT_SCHEMA = "https://stac-extensions.github.io/version/v1.0.0/schema.json" # STAC fields - These are unusual for an extension in that they do not have # a prefix. e.g. nothing like "ver:" @@ -25,7 +27,7 @@ MEDIA_TYPE: str = 'application/json' -class VersionItemExt(base.ItemExtension): +class VersionItemExt(): """VersionItemExt extends Item to add version and deprecated properties along with links to the latest, predecessor, and successor Items. @@ -165,7 +167,7 @@ def _object_links(cls) -> List[str]: return [LATEST, PREDECESSOR, SUCCESSOR] -class VersionCollectionExt(base.CollectionExtension): +class VersionCollectionExt(): """VersionCollectionExt extends Collection to add version and deprecated properties along with links to the latest, predecessor, and successor Collections. @@ -305,8 +307,13 @@ def apply(self, self.successor = successor -VERSION_EXTENSION_DEFINITION: base.ExtensionDefinition = base.ExtensionDefinition( - Extensions.VERSION, [ - base.ExtendedObject(ps.Item, VersionItemExt), - base.ExtendedObject(ps.Collection, VersionCollectionExt) - ]) +class VersionExtensionHooks(ExtensionHooks): + extension_schema = VERSION_EXT_SCHEMA + + def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: + if isinstance(so, ps.Collection) or isinstance(so, ps.Item): + return [LATEST, PREDECESSOR, SUCCESSOR] + return None + + +VERSION_EXTENSION_HOOKS: ExtensionHooks = VersionExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index cab79ea87..ef5a98460 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -1,11 +1,9 @@ -import pystac -from pystac import Extensions -from pystac.item import Asset, Item -from pystac.extensions.base import (ItemExtension, ExtensionDefinition, ExtendedObject) from typing import List, Optional +import pystac as ps -class ViewItemExt(ItemExtension): + +class ViewItemExt(): """ViewItemExt is the extension of the Item in the View Geometry Extension. View Geometry adds metadata related to angles of sensors and other radiance angles that affect the view of resulting data. It will often be combined with other extensions that @@ -21,12 +19,7 @@ class ViewItemExt(ItemExtension): Using ViewItemExt to directly wrap an item will add the 'view' extension ID to the item's stac_extensions. """ - def __init__(self, item: Item) -> None: - if item.stac_extensions is None: - item.stac_extensions = [str(Extensions.VIEW)] - elif str(Extensions.VIEW) not in item.stac_extensions: - item.stac_extensions.append(str(Extensions.VIEW)) - + def __init__(self, item: ps.Item) -> None: self.item = item def apply(self, @@ -53,7 +46,7 @@ def apply(self, """ if (off_nadir is None and incidence_angle is None and azimuth is None and sun_azimuth is None and sun_elevation is None): - raise pystac.STACError( + raise ps.STACError( 'Must provide at least one of: off_nadir, incidence_angle, azimuth, sun_azimuth, sun_elevation' # noqa: E501 ) if off_nadir: @@ -81,7 +74,7 @@ def off_nadir(self) -> Optional[float]: def off_nadir(self, v: Optional[float]) -> None: self.set_off_nadir(v) - def get_off_nadir(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_off_nadir(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset off_nadir. If an Asset is supplied and the Item property exists on the Asset, @@ -95,7 +88,7 @@ def get_off_nadir(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('view:off_nadir') - def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[Asset] = None) -> None: + def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset off_nadir. If an Asset is supplied, sets the property on the Asset. @@ -118,7 +111,7 @@ def incidence_angle(self) -> Optional[float]: def incidence_angle(self, v: Optional[float]) -> None: self.set_incidence_angle(v) - def get_incidence_angle(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_incidence_angle(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset incidence_angle. If an Asset is supplied and the Item property exists on the Asset, @@ -134,7 +127,7 @@ def get_incidence_angle(self, asset: Optional[Asset] = None) -> Optional[float]: def set_incidence_angle(self, incidence_angle: Optional[float], - asset: Optional[Asset] = None) -> None: + asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset incidence_angle. If an Asset is supplied, sets the property on the Asset. @@ -157,7 +150,7 @@ def azimuth(self) -> Optional[float]: def azimuth(self, v: Optional[float]) -> None: self.set_azimuth(v) - def get_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_azimuth(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset azimuth. If an Asset is supplied and the Item property exists on the Asset, @@ -171,7 +164,7 @@ def get_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('view:azimuth') - def set_azimuth(self, azimuth: Optional[float], asset: Optional[Asset] = None) -> None: + def set_azimuth(self, azimuth: Optional[float], asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset azimuth. If an Asset is supplied, sets the property on the Asset. @@ -193,7 +186,7 @@ def sun_azimuth(self) -> Optional[float]: def sun_azimuth(self, v: Optional[float]) -> None: self.set_sun_azimuth(v) - def get_sun_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_sun_azimuth(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset sun_azimuth. If an Asset is supplied and the Item property exists on the Asset, @@ -207,7 +200,9 @@ def get_sun_azimuth(self, asset: Optional[Asset] = None) -> Optional[float]: else: return asset.properties.get('view:sun_azimuth') - def set_sun_azimuth(self, sun_azimuth: Optional[float], asset: Optional[Asset] = None) -> None: + def set_sun_azimuth(self, + sun_azimuth: Optional[float], + asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset sun_azimuth. If an Asset is supplied, sets the property on the Asset. @@ -229,7 +224,7 @@ def sun_elevation(self) -> Optional[float]: def sun_elevation(self, v: Optional[float]) -> None: self.set_sun_elevation(v) - def get_sun_elevation(self, asset: Optional[Asset] = None) -> Optional[float]: + def get_sun_elevation(self, asset: Optional[ps.Asset] = None) -> Optional[float]: """Gets an Item or an Asset sun_elevation. If an Asset is supplied and the Item property exists on the Asset, @@ -245,7 +240,7 @@ def get_sun_elevation(self, asset: Optional[Asset] = None) -> Optional[float]: def set_sun_elevation(self, sun_elevation: Optional[float], - asset: Optional[Asset] = None) -> None: + asset: Optional[ps.Asset] = None) -> None: """Set an Item or an Asset sun_elevation. If an Asset is supplied, sets the property on the Asset. @@ -258,9 +253,5 @@ def _object_links(cls) -> List[str]: return [] @classmethod - def from_item(cls, item: Item) -> "ViewItemExt": + def from_item(cls, item: ps.Item) -> "ViewItemExt": return cls(item) - - -VIEW_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( - Extensions.VIEW, [ExtendedObject(Item, ViewItemExt)]) diff --git a/pystac/item.py b/pystac/item.py index a0c71bf82..c3ffbf3c3 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -1006,7 +1006,7 @@ def clone(self) -> "Item": return clone def _object_links(self) -> List[str]: - return ['collection'] + (ps.STAC_EXTENSIONS.get_extended_object_links(self)) + return ['collection'] + (ps.EXTENSION_HOOKS.get_extended_object_links(self)) @classmethod def from_dict(cls, diff --git a/pystac/link.py b/pystac/link.py index 27e86a01b..bc36085b3 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -108,7 +108,7 @@ def get_href(self) -> Optional[str]: if href and is_absolute_href(href) and self.owner and self.owner.get_root(): root = self.owner.get_root() rel_links = HIERARCHICAL_LINKS + \ - ps.STAC_EXTENSIONS.get_extended_object_links(self.owner) + ps.EXTENSION_HOOKS.get_extended_object_links(self.owner) # if a hierarchical link with an owner and root, and relative catalog if root and root.is_relative() and self.rel in rel_links: owner_href = self.owner.get_self_href() diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index 96b33ceff..015d6bc9b 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -40,7 +40,7 @@ def stac_object_from_dict(d: Dict[str, Any], info = identify_stac_object(d) - d, info = migrate_to_latest(d, info) + d = migrate_to_latest(d, info) if info.object_type == ps.STACObjectType.CATALOG: return ps.Catalog.from_dict(d, href=href, root=root, migrate=False) diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 45d7dbad4..8873e6ea2 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,14 +1,38 @@ -from functools import total_ordering -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast +from enum import Enum +from functools import total_ordering, lru_cache +from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast import pystac as ps from pystac.version import STACVersion -from pystac.extensions import Extensions if TYPE_CHECKING: from pystac.stac_object import STACObjectType as STACObjectType_Type +class OldExtensionShortIDs(str, Enum): + """Enumerates the IDs of common extensions.""" + def __str__(self) -> str: + return str(self.value) + + CHECKSUM = 'checksum' + COLLECTION_ASSETS = 'collection-assets' + DATACUBE = 'datacube' + EO = 'eo' + ITEM_ASSETS = 'item-assets' + LABEL = 'label' + POINTCLOUD = 'pointcloud' + PROJECTION = 'projection' + SAR = 'sar' + SAT = 'sat' + SCIENTIFIC = 'scientific' + SINGLE_FILE_STAC = 'single-file-stac' + TILED_ASSETS = 'tiled-assets' + TIMESTAMPS = 'timestamps' + VERSION = 'version' + VIEW = 'view' + FILE = 'file' + + @total_ordering class STACVersionID: """Defines STAC versions in an object that is orderable based on version number. @@ -110,6 +134,167 @@ def __repr__(self) -> str: return ''.format(self.min_version, self.max_version) +class OldExtensionSchemaUriMap: + """Implementation of SchemaUriMap that uses schemas hosted by https://schemas.stacspec.org. + + For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the + radiantearth/stac-spec GitHub repo. + """ + + # BASE_URIS contains a list of tuples, the first element is a version range and the + # second being the base URI for schemas for that range. The schema URI of a STAC + # for a particular version uses the base URI associated with the version range which + # contains it. If the version it outside of any VersionRange, there is no URI for the + # schema. + @lru_cache() + def get_base_uris(self) -> List[Tuple[STACVersionRange, Callable[[str], str]]]: + return [(STACVersionRange(min_version='1.0.0-beta.1'), + lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), + (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: + 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] + + # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema URIs + # based on the stac object type and the stac version, using a similar technique as BASE_URIS. + # Uris are contained in a tuple whose first element represents the URI of the latest + # version, so that a search through version ranges is avoided if the STAC being validated + # is the latest version. If it's a previous version, the stac_version that matches + # the listed version range is used, or else the URI from the latest version is used if + # there are no overrides for previous versions. + def get_schema_map(self) -> Dict[str, Any]: + return { + OldExtensionShortIDs.CHECKSUM: ({ + ps.STACObjectType.CATALOG: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/checksum/json-schema/schema.json' + }, None), + OldExtensionShortIDs.COLLECTION_ASSETS: ({ + ps.STACObjectType.COLLECTION: + 'extensions/collection-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.DATACUBE: ({ + ps.STACObjectType.COLLECTION: + 'extensions/datacube/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/datacube/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: None, + ps.STACObjectType.ITEM: None + })]), + OldExtensionShortIDs.EO: ({ + ps.STACObjectType.ITEM: + 'extensions/eo/json-schema/schema.json' + }, None), + OldExtensionShortIDs.ITEM_ASSETS: ({ + ps.STACObjectType.COLLECTION: + 'extensions/item-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.LABEL: ({ + ps.STACObjectType.ITEM: + 'extensions/label/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { + ps.STACObjectType.ITEM: 'extensions/label/schema.json' + })]), + OldExtensionShortIDs.POINTCLOUD: ( + { + ps.STACObjectType.ITEM: None # 'extensions/pointcloud/json-schema/schema.json' + }, + None), + OldExtensionShortIDs.PROJECTION: ({ + ps.STACObjectType.ITEM: + 'extensions/projection/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAR: ({ + ps.STACObjectType.ITEM: + 'extensions/sar/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAT: ({ + ps.STACObjectType.ITEM: + 'extensions/sat/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SCIENTIFIC: ({ + ps.STACObjectType.ITEM: + 'extensions/scientific/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/scientific/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SINGLE_FILE_STAC: ({ + ps.STACObjectType.CATALOG: + 'extensions/single-file-stac/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TILED_ASSETS: ({ + ps.STACObjectType.CATALOG: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/tiled-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TIMESTAMPS: ({ + ps.STACObjectType.ITEM: + 'extensions/timestamps/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VERSION: ({ + ps.STACObjectType.ITEM: + 'extensions/version/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/version/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VIEW: ({ + ps.STACObjectType.ITEM: + 'extensions/view/json-schema/schema.json' + }, None), + + # Removed or renamed extensions. + 'dtr': (None, None), # Invalid schema + 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: + 'extensions/asset/json-schema/schema.json' + })]), + } + + def _append_base_uri_if_needed(self, uri: str, stac_version: str) -> Optional[str]: + # Only append the base URI if it's not already an absolute URI + if '://' not in uri: + base_uri = None + for version_range, f in self.get_base_uris(): + if version_range.contains(stac_version): + base_uri = f(stac_version) + return '{}/{}'.format(base_uri, uri) + + # No JSON Schema for the old extension + return None + else: + return uri + + def get_extension_schema_uri(self, extension_id: str, object_type: "STACObjectType_Type", + stac_version: str) -> Optional[str]: + uri = None + + is_latest = stac_version == ps.get_stac_version() + + ext_map = self.get_schema_map() + if extension_id in ext_map: + if ext_map[extension_id][0] and \ + object_type in ext_map[extension_id][0]: + uri = ext_map[extension_id][0][object_type] + + if not is_latest: + if ext_map[extension_id][1]: + for version_range, ext_uris in ext_map[extension_id][1]: + if version_range.contains(stac_version): + if object_type in ext_uris: + uri = ext_uris[object_type] + break + + if uri is None: + return uri + else: + return self._append_base_uri_if_needed(uri, stac_version) + + class STACJSONDescription: """Describes the STAC object information for a STAC object represented in JSON @@ -117,22 +302,18 @@ class STACJSONDescription: object_type (str): Describes the STAC object type. One of :class:`~pystac.STACObjectType`. version_range (STACVersionRange): The STAC version range that describes what has been identified as potential valid versions of the stac object. - common_extensions (List[str]): List of common extension IDs implemented by this - STAC object. - custom_extensions (List[str]): List of custom extensions (URIs to JSON Schemas) - used by this STAC Object. + extensions (List[str]): List of extension schema URIs for extensions this + object implements """ def __init__(self, object_type: "STACObjectType_Type", version_range: STACVersionRange, - common_extensions: List[str], custom_extensions: List[str]) -> None: + extensions: List[str]) -> None: self.object_type = object_type self.version_range = version_range - self.common_extensions = common_extensions - self.custom_extensions = custom_extensions + self.extensions = extensions def __repr__(self) -> str: - return '<{} {} common_ext={} custom_ext={}>'.format(self.object_type, self.version_range, - ','.join(self.common_extensions), - ','.join(self.custom_extensions)) + return '<{} {} ext={}>'.format(self.object_type, self.version_range, + ','.join(self.extensions)) def _identify_stac_extensions(object_type: str, d: Dict[str, Any], @@ -164,21 +345,21 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if any(prop.startswith('checksum:') for prop in link_props): found_checksum = True - stac_extensions.add(Extensions.CHECKSUM) + stac_extensions.add(OldExtensionShortIDs.CHECKSUM) if not found_checksum: if 'assets' in d: for asset in d['assets'].values(): asset_props = cast(Dict[str, Any], asset).keys() if any(prop.startswith('checksum:') for prop in asset_props): found_checksum = True - stac_extensions.add(Extensions.CHECKSUM) + stac_extensions.add(OldExtensionShortIDs.CHECKSUM) if found_checksum: version_range.set_min(STACVersionID('0.6.2')) # datacube if object_type == ps.STACObjectType.ITEM: if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(Extensions.DATACUBE) + stac_extensions.add(OldExtensionShortIDs.DATACUBE) version_range.set_min(STACVersionID('0.6.1')) # datetime-range (old extension) @@ -190,7 +371,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], # eo if object_type == ps.STACObjectType.ITEM: if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(Extensions.EO) + stac_extensions.add(OldExtensionShortIDs.EO) if 'eo:epsg' in d['properties']: if d['properties']['eo:epsg'] is None: version_range.set_min(STACVersionID('0.6.1')) @@ -199,19 +380,19 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if 'eo:constellation' in d['properties']: version_range.set_min(STACVersionID('0.6.0')) if 'eo:bands' in d: - stac_extensions.add(Extensions.EO) + stac_extensions.add(OldExtensionShortIDs.EO) version_range.set_max(STACVersionID('0.5.2')) # pointcloud if object_type == ps.STACObjectType.ITEM: if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(Extensions.POINTCLOUD) + stac_extensions.add(OldExtensionShortIDs.POINTCLOUD) version_range.set_min(STACVersionID('0.6.2')) # sar if object_type == ps.STACObjectType.ITEM: if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(Extensions.SAR) + stac_extensions.add(OldExtensionShortIDs.SAR) version_range.set_min(STACVersionID('0.6.2')) if version_range.contains('0.6.2'): for prop in [ @@ -240,13 +421,13 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if 'properties' in d: prop_keys = cast(Dict[str, Any], d['properties']).keys() if any(k.startswith('sci:') for k in prop_keys): - stac_extensions.add(Extensions.SCIENTIFIC) + stac_extensions.add(OldExtensionShortIDs.SCIENTIFIC) version_range.set_min(STACVersionID('0.6.0')) # Single File STAC if object_type == ps.STACObjectType.ITEMCOLLECTION: if 'collections' in d: - stac_extensions.add(Extensions.SINGLE_FILE_STAC) + stac_extensions.add(OldExtensionShortIDs.SINGLE_FILE_STAC) version_range.set_min(STACVersionID('0.8.0')) if 'stac_extensions' not in d: version_range.set_max(STACVersionID('0.8.1')) @@ -254,21 +435,6 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], return list(stac_extensions) -def _split_extensions(stac_extensions: List[str]) -> Tuple[List[str], List[str]]: - """Split extensions into common_extensions and custom_extensions""" - - common_extensions: List[str] = [] - custom_extensions: List[str] = [] - for ext in stac_extensions: - # Custom extensions are a URI - if ext.endswith('.json') or '/' in ext: - custom_extensions.append(ext) - else: - common_extensions.append(ext) - - return (common_extensions, custom_extensions) - - def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectType_Type": """Determines the STACObjectType of the provided JSON dict. @@ -339,6 +505,15 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: else: stac_extensions = [] + # Between 1.0.0-beta.2 and 1.0.0-RC1, STAC extensions changed from + # being split between 'common' and custom extensions, with common + # extensions having short names that were used in the stac_extensions + # property list, to being mostly externalized from the core spec and + # always identified with the schema URI as the identifier. This + # code translates the short name IDs used pre-1.0.0-RC1 to the + # relevant extension schema uri identifier. + + if not version_range.is_single_version(): # Final Checks @@ -353,5 +528,4 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: json_dict['links'])): version_range.set_min(STACVersionID('0.7.0')) - common_extensions, custom_extensions = _split_extensions(stac_extensions) - return STACJSONDescription(object_type, version_range, common_extensions, custom_extensions) + return STACJSONDescription(object_type, version_range, stac_extensions) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index a7f0f9bcf..6f519f3b1 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -4,8 +4,7 @@ import pystac as ps from pystac.version import STACVersion -from pystac.extensions import Extensions -from pystac.serialization.identify import (STACJSONDescription, STACVersionID, STACVersionRange) +from pystac.serialization.identify import (STACJSONDescription, STACVersionID, STACVersionRange, OldExtensionShortIDs) def _migrate_links(d: Dict[str, Any], version: STACVersionID) -> None: @@ -19,7 +18,7 @@ def _migrate_catalog(d: Dict[str, Any], version: STACVersionID, info: STACJSONDe _migrate_links(d, version) if version < '0.8': - d['stac_extensions'] = info.common_extensions + info.custom_extensions + d['stac_extensions'] = info.extensions def _migrate_collection(d: Dict[str, Any], version: STACVersionID, @@ -31,13 +30,13 @@ def _migrate_item(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescr _migrate_links(d, version) if version < '0.8': - d['stac_extensions'] = info.common_extensions + info.custom_extensions + d['stac_extensions'] = info.extensions def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: if version < '0.9.0': - d['stac_extensions'] = info.common_extensions + info.custom_extensions + d['stac_extensions'] = info.extensions # Extensions @@ -231,21 +230,6 @@ def _get_object_migrations( } -def _get_extension_migrations( -) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: - return { - Extensions.CHECKSUM: _migrate_checksum, - Extensions.DATACUBE: _migrate_datacube, - Extensions.EO: _migrate_eo, - Extensions.ITEM_ASSETS: _migrate_item_assets, - Extensions.LABEL: _migrate_label, - Extensions.POINTCLOUD: _migrate_pointcloud, - Extensions.SAR: _migrate_sar, - Extensions.SCIENTIFIC: _migrate_scientific, - Extensions.SINGLE_FILE_STAC: _migrate_single_file_stac - } - - def _get_removed_extension_migrations( ) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: return { @@ -256,12 +240,13 @@ def _get_removed_extension_migrations( } +# TODO: Item Assets def _get_extension_renames() -> Dict[str, str]: return {'asset': 'item-assets'} def migrate_to_latest(json_dict: Dict[str, Any], - info: STACJSONDescription) -> Tuple[Dict[str, Any], STACJSONDescription]: + info: STACJSONDescription) -> Dict[str, Any]: """Migrates the STAC JSON to the latest version Args: @@ -278,43 +263,17 @@ def migrate_to_latest(json_dict: Dict[str, Any], version = info.version_range.latest_valid_version() object_migrations = _get_object_migrations() - extension_migrations = _get_extension_migrations() - extension_renames = _get_extension_renames() removed_extension_migrations = _get_removed_extension_migrations() if version != STACVersion.DEFAULT_STAC_VERSION: object_migrations[info.object_type](result, version, info) + ps.EXTENSION_HOOKS.migrate(result, version, info) - extensions_to_add = set([]) - for ext in info.common_extensions: - if ext in extension_renames: - result['stac_extensions'].remove(ext) - ext = extension_renames[ext] - extensions_to_add.add(ext) - - if ext in extension_migrations: - added_extensions = extension_migrations[ext](result, version, info) - if added_extensions: - extensions_to_add |= added_extensions - + for ext in (result['stac_extensions'] or []): if ext in removed_extension_migrations: removed_extension_migrations[ext](result, version, info) result['stac_extensions'].remove(ext) - for ext in extensions_to_add: - result['stac_extensions'].append(ext) - - migrated_extensions = set(info.common_extensions) - migrated_extensions = migrated_extensions | set(extensions_to_add) - migrated_extensions = migrated_extensions - set(removed_extension_migrations.keys()) - migrated_extensions = migrated_extensions - set(extension_renames.keys()) - common_extensions = list(migrated_extensions) - else: - common_extensions = info.common_extensions - result['stac_version'] = STACVersion.DEFAULT_STAC_VERSION - return result, STACJSONDescription( - info.object_type, - STACVersionRange(STACVersion.DEFAULT_STAC_VERSION, STACVersion.DEFAULT_STAC_VERSION), - common_extensions, info.custom_extensions) + return result diff --git a/pystac/stac_object.py b/pystac/stac_object.py index efe8dddea..75e26bb52 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -7,11 +7,11 @@ from pystac.link import Link from pystac.stac_io import STAC_IO from pystac.utils import (is_absolute_href, make_absolute_href) -from pystac.extensions import ExtensionError +#from pystac.extensions import ExtensionError if TYPE_CHECKING: from pystac.catalog import Catalog as Catalog_Type - from pystac.extensions.base import STACObjectExtension as STACObjectExtension_Type + #from pystac.extensions.base import STACObjectExtension as STACObjectExtension_Type class STACObjectType(str, Enum): @@ -356,22 +356,22 @@ def full_copy(self, return clone - @property - def ext(self) -> "ExtensionIndex": - """Access extensions for this STACObject. + # @property + # def ext(self) -> "ExtensionIndex": + # """Access extensions for this STACObject. - Example: - This example shows accessing a Item's EO extension functionality - that gets the band information for an asset:: + # Example: + # This example shows accessing a Item's EO extension functionality + # that gets the band information for an asset:: - item = pystac.read_file("eo_item.json") - bands = item.ext.eo.get_asset_bands(item.assets["image"]) + # item = pystac.read_file("eo_item.json") + # bands = item.ext.eo.get_asset_bands(item.assets["image"]) - Returns: - ExtensionIndex: The object that can be used to access extension information - and functionality. - """ - return ExtensionIndex(self) + # Returns: + # ExtensionIndex: The object that can be used to access extension information + # and functionality. + # """ + # return ExtensionIndex(self) def resolve_links(self) -> None: """Ensure all STACObjects linked to by this STACObject are @@ -475,73 +475,73 @@ def from_dict(cls, pass -class ExtensionIndex: - """Defines methods for accessing extension functionality. +# class ExtensionIndex: +# """Defines methods for accessing extension functionality. - To access a specific extension, use the __getitem__ on this class with the - extension ID:: +# To access a specific extension, use the __getitem__ on this class with the +# extension ID:: - # Access the "bands" property on the eo extension. - item.ext['eo'].bands - """ - def __init__(self, stac_object: STACObject) -> None: - self.stac_object = stac_object +# # Access the "bands" property on the eo extension. +# item.ext['eo'].bands +# """ +# def __init__(self, stac_object: STACObject) -> None: +# self.stac_object = stac_object - def __getitem__(self, extension_id: str) -> "STACObjectExtension_Type": - """Gets the extension object for the given extension. +# def __getitem__(self, extension_id: str) -> "STACObjectExtension_Type": +# """Gets the extension object for the given extension. - Returns: - CatalogExtension or CollectionExtension or ItemExtension: The extension object - through which you can access the extension functionality for the extension represented - by the extension_id. - """ - # Check to make sure this is a registered extension. - if not ps.STAC_EXTENSIONS.is_registered_extension(extension_id): - raise ExtensionError("'{}' is not an extension " - "registered with PySTAC".format(extension_id)) +# Returns: +# CatalogExtension or CollectionExtension or ItemExtension: The extension object +# through which you can access the extension functionality for the extension represented +# by the extension_id. +# """ +# # Check to make sure this is a registered extension. +# if not ps.STAC_EXTENSIONS.is_registered_extension(extension_id): +# raise ExtensionError("'{}' is not an extension " +# "registered with PySTAC".format(extension_id)) - if not self.implements(extension_id): - raise ExtensionError("{} does not implement the {} extension. " - "Use the 'ext.enable' method to enable this extension " - "first.".format(self.stac_object, extension_id)) +# if not self.implements(extension_id): +# raise ExtensionError("{} does not implement the {} extension. " +# "Use the 'ext.enable' method to enable this extension " +# "first.".format(self.stac_object, extension_id)) - return ps.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) +# return ps.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) - def __getattr__(self, extension_id: str) -> "STACObjectExtension_Type": - """Gets an extension based on a dynamic attribute. +# def __getattr__(self, extension_id: str) -> "STACObjectExtension_Type": +# """Gets an extension based on a dynamic attribute. - This takes the attribute name and passes it to __getitem__. +# This takes the attribute name and passes it to __getitem__. - This allows the following two lines to be equivalent:: +# This allows the following two lines to be equivalent:: - item.ext["label"].label_properties - item.ext.label.label_properties - """ - if extension_id.startswith('__') and hasattr(ExtensionIndex, extension_id): - return self.__getattribute__(extension_id) - return self[extension_id] +# item.ext["label"].label_properties +# item.ext.label.label_properties +# """ +# if extension_id.startswith('__') and hasattr(ExtensionIndex, extension_id): +# return self.__getattribute__(extension_id) +# return self[extension_id] - def enable(self, extension_id: str) -> None: - """Enables a stac extension for the given object. If the object already - enables the extension, no action is taken. If it does not, the extension ID is - added to the object's stac_extension property. +# def enable(self, extension_id: str) -> None: +# """Enables a stac extension for the given object. If the object already +# enables the extension, no action is taken. If it does not, the extension ID is +# added to the object's stac_extension property. - Args: - extension_id (str): The extension ID representing the extension - the object should implement +# Args: +# extension_id (str): The extension ID representing the extension +# the object should implement - """ - ps.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) +# """ +# ps.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) - def implements(self, extension_id: str) -> bool: - """Returns true if the associated object implements the given extension. +# def implements(self, extension_id: str) -> bool: +# """Returns true if the associated object implements the given extension. - Args: - extension_id (str): The extension ID to check +# Args: +# extension_id (str): The extension ID to check - Returns: - [bool]: True if the object implements the extensions - i.e. if - the extension ID is in the "stac_extensions" property. - """ - return (self.stac_object.stac_extensions is not None - and extension_id in self.stac_object.stac_extensions) +# Returns: +# [bool]: True if the object implements the extensions - i.e. if +# the extension ID is in the "stac_extensions" property. +# """ +# return (self.stac_object.stac_extensions is not None +# and extension_id in self.stac_object.stac_extensions) diff --git a/pystac/utils.py b/pystac/utils.py index b8ea00576..27f1f9c9e 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -1,6 +1,6 @@ import os import posixpath -from typing import Any, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from urllib.parse import (urlparse, ParseResult as URLParseResult) from datetime import datetime, timezone import dateutil.parser @@ -202,3 +202,31 @@ def extract_coords(coords: List[Union[List[float], List[List[Any]]]]) -> None: bbox = [lats[0], lons[0], lats[-1], lons[-1]] return bbox + + +T = TypeVar('T') +U = TypeVar('U') + + +def map_opt(fn: Callable[[T], U], v: Optional[T]) -> Optional[U]: + """Maps the value of an option to another value, returning + None if the input option is None. + """ + return v if v is None else fn(v) + + +def get_opt(option: Optional[T]) -> T: + """ Retrieves the value of the Optional type. + + If the Optional is None, this will raise a value error. + Use this to get a propertly typed value from an optional + in contexts where you can be certain the value is not None. + If there is potential for a non-None value, it's best to handle + the None case of the optional instead of using this method. + + Returns: + The value of type T wrapped by the Optional[T] + """ + if option is None: + raise ValueError("Cannot get value from None") + return option diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 29ba9b349..df19a9e63 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,9 +1,10 @@ from abc import (ABC, abstractmethod) from typing import Any, Callable, Dict, List, Optional, Tuple -import pystac -from pystac import (STACObjectType, Extensions) +import pystac as ps from pystac.serialization import STACVersionRange +from pystac.serialization.identify import OldExtensionShortIDs +from pystac.stac_object import STACObjectType class SchemaUriMap(ABC): @@ -78,60 +79,68 @@ class DefaultSchemaUriMap(SchemaUriMap): ]) }, 'extension': { - Extensions.CHECKSUM: ({ - STACObjectType.CATALOG: 'extensions/checksum/json-schema/schema.json', - STACObjectType.COLLECTION: 'extensions/checksum/json-schema/schema.json', - STACObjectType.ITEM: 'extensions/checksum/json-schema/schema.json' + OldExtensionShortIDs.CHECKSUM: ({ + STACObjectType.CATALOG: + 'extensions/checksum/json-schema/schema.json', + STACObjectType.COLLECTION: + 'extensions/checksum/json-schema/schema.json', + STACObjectType.ITEM: + 'extensions/checksum/json-schema/schema.json' }, None), - Extensions.COLLECTION_ASSETS: ({ + OldExtensionShortIDs.COLLECTION_ASSETS: ({ STACObjectType.COLLECTION: 'extensions/collection-assets/json-schema/schema.json' }, None), - Extensions.DATACUBE: ({ - STACObjectType.COLLECTION: 'extensions/datacube/json-schema/schema.json', - STACObjectType.ITEM: 'extensions/datacube/json-schema/schema.json' + OldExtensionShortIDs.DATACUBE: ({ + STACObjectType.COLLECTION: + 'extensions/datacube/json-schema/schema.json', + STACObjectType.ITEM: + 'extensions/datacube/json-schema/schema.json' }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { STACObjectType.COLLECTION: None, STACObjectType.ITEM: None })]), - Extensions.EO: ({ + OldExtensionShortIDs.EO: ({ STACObjectType.ITEM: 'extensions/eo/json-schema/schema.json' }, None), - Extensions.ITEM_ASSETS: ({ + OldExtensionShortIDs.ITEM_ASSETS: ({ STACObjectType.COLLECTION: 'extensions/item-assets/json-schema/schema.json' }, None), - Extensions.LABEL: ({ - STACObjectType.ITEM: 'extensions/label/json-schema/schema.json' + OldExtensionShortIDs.LABEL: ({ + STACObjectType.ITEM: + 'extensions/label/json-schema/schema.json' }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { STACObjectType.ITEM: 'extensions/label/schema.json' })]), - Extensions.POINTCLOUD: ( + OldExtensionShortIDs.POINTCLOUD: ( { STACObjectType.ITEM: None # 'extensions/pointcloud/json-schema/schema.json' }, None), - Extensions.PROJECTION: ({ + OldExtensionShortIDs.PROJECTION: ({ STACObjectType.ITEM: 'extensions/projection/json-schema/schema.json' }, None), - Extensions.SAR: ({ - STACObjectType.ITEM: 'extensions/sar/json-schema/schema.json' + OldExtensionShortIDs.SAR: ({ + STACObjectType.ITEM: + 'extensions/sar/json-schema/schema.json' }, None), - Extensions.SAT: ({ - STACObjectType.ITEM: 'extensions/sat/json-schema/schema.json' + OldExtensionShortIDs.SAT: ({ + STACObjectType.ITEM: + 'extensions/sat/json-schema/schema.json' }, None), - Extensions.SCIENTIFIC: ({ + OldExtensionShortIDs.SCIENTIFIC: ({ STACObjectType.ITEM: 'extensions/scientific/json-schema/schema.json', STACObjectType.COLLECTION: 'extensions/scientific/json-schema/schema.json' }, None), - Extensions.SINGLE_FILE_STAC: ({ + OldExtensionShortIDs.SINGLE_FILE_STAC: ({ STACObjectType.CATALOG: 'extensions/single-file-stac/json-schema/schema.json' }, None), - Extensions.TILED_ASSETS: ({ + OldExtensionShortIDs.TILED_ASSETS: ({ STACObjectType.CATALOG: 'extensions/tiled-assets/json-schema/schema.json', STACObjectType.COLLECTION: @@ -139,18 +148,19 @@ class DefaultSchemaUriMap(SchemaUriMap): STACObjectType.ITEM: 'extensions/tiled-assets/json-schema/schema.json' }, None), - Extensions.TIMESTAMPS: ({ + OldExtensionShortIDs.TIMESTAMPS: ({ STACObjectType.ITEM: 'extensions/timestamps/json-schema/schema.json' }, None), - Extensions.VERSION: ({ + OldExtensionShortIDs.VERSION: ({ STACObjectType.ITEM: 'extensions/version/json-schema/schema.json', STACObjectType.COLLECTION: 'extensions/version/json-schema/schema.json' }, None), - Extensions.VIEW: ({ - STACObjectType.ITEM: 'extensions/view/json-schema/schema.json' + OldExtensionShortIDs.VIEW: ({ + STACObjectType.ITEM: + 'extensions/view/json-schema/schema.json' }, None), # Removed or renamed extensions. @@ -178,7 +188,7 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: uri = None - is_latest = stac_version == pystac.get_stac_version() + is_latest = stac_version == ps.get_stac_version() if object_type not in self.DEFAULT_SCHEMA_MAP['core']: raise KeyError('Unknown STAC object type {}'.format(object_type)) @@ -197,7 +207,7 @@ def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectTyp stac_version: str) -> Optional[str]: uri = None - is_latest = stac_version == pystac.get_stac_version() + is_latest = stac_version == ps.get_stac_version() ext_map = self.DEFAULT_SCHEMA_MAP['extension'] if extension_id in ext_map: diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index ff012ddc1..6a34680ce 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -1,9 +1,10 @@ import json import unittest -import pystac +import pystac as ps from pystac import Item -from pystac.extensions.eo import Band +from pystac.utils import get_opt +from pystac.extensions.eo import eo_ext, Band from tests.utils import (TestCases, test_to_from_dict) @@ -20,17 +21,18 @@ def test_to_from_dict(self): test_to_from_dict(self, Item, item_dict) def test_validate_eo(self): - item = pystac.read_file(self.LANDSAT_EXAMPLE_URI) - item2 = pystac.read_file(self.BANDS_IN_ITEM_URI) + item = ps.read_file(self.LANDSAT_EXAMPLE_URI) + item2 = ps.read_file(self.BANDS_IN_ITEM_URI) item.validate() item2.validate() def test_bands(self): - eo_item = pystac.read_file(self.BANDS_IN_ITEM_URI) + item = ps.Item.from_file(self.BANDS_IN_ITEM_URI) # Get - self.assertIn("eo:bands", eo_item.properties) - bands = eo_item.ext.eo.bands + self.assertIn("eo:bands", item.properties) + bands = eo_ext(item).bands + assert bands is not None self.assertEqual(list(map(lambda x: x.name, bands)), ['band1', 'band2', 'band3', 'band4']) # Set @@ -40,41 +42,41 @@ def test_bands(self): Band.create(name="blue", description=Band.band_description("blue")), ] - eo_item.ext.eo.bands = new_bands + eo_ext(item).bands = new_bands self.assertEqual('Common name: red, Range: 0.6 to 0.7', - eo_item.properties['eo:bands'][0]['description']) - self.assertEqual(len(eo_item.ext.eo.bands), 3) - eo_item.validate() + item.properties['eo:bands'][0]['description']) + self.assertEqual(len(eo_ext(item).bands or []), 3) + item.validate() def test_asset_bands(self): - eo_item = pystac.read_file(self.LANDSAT_EXAMPLE_URI) + item = ps.Item.from_file(self.LANDSAT_EXAMPLE_URI) # Get - b1_asset = eo_item.assets['B1'] - asset_bands = eo_item.ext.eo.get_bands(b1_asset) - self.assertIsNot(None, asset_bands) + b1_asset = item.assets['B1'] + asset_bands = eo_ext(b1_asset).bands + assert asset_bands is not None self.assertEqual(len(asset_bands), 1) self.assertEqual(asset_bands[0].name, 'B1') - index_asset = eo_item.assets['index'] - asset_bands = eo_item.ext.eo.get_bands(index_asset) + index_asset = item.assets['index'] + asset_bands = eo_ext(index_asset).bands self.assertIs(None, asset_bands) # No asset specified - asset_bands = eo_item.ext.eo.get_bands() - self.assertIsNot(None, asset_bands) + item_bands = eo_ext(item).bands + self.assertIsNot(None, item_bands) # Set - b2_asset = eo_item.assets['B2'] - self.assertEqual(eo_item.ext.eo.get_bands(b2_asset)[0].name, "B2") - eo_item.ext.eo.set_bands(eo_item.ext.eo.get_bands(b1_asset), b2_asset) + b2_asset = item.assets['B2'] + self.assertEqual(get_opt(eo_ext(b2_asset).bands)[0].name, "B2") + eo_ext(b2_asset).bands = eo_ext(b1_asset).bands - new_b2_asset_bands = eo_item.ext.eo.get_bands(eo_item.assets['B2']) + new_b2_asset_bands = eo_ext(item.assets['B2']).bands - self.assertEqual(new_b2_asset_bands[0].name, 'B1') + self.assertEqual(get_opt(new_b2_asset_bands)[0].name, 'B1') - eo_item.validate() + item.validate() # Check adding a new asset new_bands = [ @@ -82,39 +84,39 @@ def test_asset_bands(self): Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] - asset = pystac.Asset(href="some/path.tif", media_type=pystac.MediaType.GEOTIFF) - eo_item.ext.eo.set_bands(new_bands, asset) - eo_item.add_asset("test", asset) + asset = ps.Asset(href="some/path.tif", media_type=ps.MediaType.GEOTIFF) + eo_ext(asset).bands = new_bands + item.add_asset("test", asset) - self.assertEqual(len(eo_item.assets["test"].properties["eo:bands"]), 3) + self.assertEqual(len(item.assets["test"].properties["eo:bands"]), 3) def test_cloud_cover(self): - item = pystac.read_file(self.LANDSAT_EXAMPLE_URI) + item = ps.Item.from_file(self.LANDSAT_EXAMPLE_URI) # Get self.assertIn("eo:cloud_cover", item.properties) - cloud_cover = item.ext.eo.cloud_cover + cloud_cover = eo_ext(item).cloud_cover self.assertEqual(cloud_cover, 78) # Set - item.ext.eo.cloud_cover = 50 + eo_ext(item).cloud_cover = 50 self.assertEqual(item.properties['eo:cloud_cover'], 50) # Get from Asset b2_asset = item.assets['B2'] - self.assertEqual(item.ext.eo.get_cloud_cover(b2_asset), item.ext.eo.get_cloud_cover()) + self.assertEqual(eo_ext(b2_asset).cloud_cover, eo_ext(item).cloud_cover) b3_asset = item.assets['B3'] - self.assertEqual(item.ext.eo.get_cloud_cover(b3_asset), 20) + self.assertEqual(eo_ext(b3_asset).cloud_cover, 20) # Set on Asset - item.ext.eo.set_cloud_cover(10, b2_asset) - self.assertEqual(item.ext.eo.get_cloud_cover(b2_asset), 10) + eo_ext(b2_asset).cloud_cover = 10 + self.assertEqual(eo_ext(b2_asset).cloud_cover, 10) item.validate() def test_read_pre_09_fields_into_common_metadata(self): - eo_item = pystac.read_file( + eo_item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.8.1/item-spec/examples/' 'landsat8-sample.json')) @@ -122,17 +124,17 @@ def test_read_pre_09_fields_into_common_metadata(self): self.assertEqual(eo_item.common_metadata.instruments, ["oli_tirs"]) def test_reads_asset_bands_in_pre_1_0_version(self): - eo_item = pystac.read_file( + item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.9.0/item-spec/examples/' 'landsat8-sample.json')) - bands = eo_item.ext.eo.get_bands(eo_item.assets['B9']) + bands = eo_ext(item.assets['B9']).bands - self.assertEqual(len(bands), 1) - self.assertEqual(bands[0].common_name, 'cirrus') + self.assertEqual(len(bands or []), 1) + self.assertEqual(get_opt(bands)[0].common_name, 'cirrus') def test_reads_gsd_in_pre_1_0_version(self): - eo_item = pystac.read_file( + eo_item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.9.0/item-spec/examples/' 'landsat8-sample.json')) diff --git a/tests/extensions/test_extensions.py b/tests/extensions/test_extensions.py index cbb32b10f..f2e3cdcf0 100644 --- a/tests/extensions/test_extensions.py +++ b/tests/extensions/test_extensions.py @@ -62,9 +62,9 @@ def _object_links(cls): class ExtensionsTest(unittest.TestCase): def test_can_add_custom_extension(self): - prev_extensions = pystac.STAC_EXTENSIONS.get_registered_extensions() + prev_extensions = pystac.EXTENSION_HOOKS.get_registered_extensions() - pystac.STAC_EXTENSIONS.add_extension( + pystac.EXTENSION_HOOKS.add_extension_hooks( ExtensionDefinition("test", [ ExtendedObject(Catalog, TestCatalogExt), ExtendedObject(Collection, TestCollectionExt), @@ -85,10 +85,10 @@ def test_can_add_custom_extension(self): self.assertEqual(item.ext.test.asset_keys, set(item.assets)) finally: - pystac.STAC_EXTENSIONS.remove_extension("test") + pystac.EXTENSION_HOOKS.remove_extension("test") - self.assertFalse(pystac.STAC_EXTENSIONS.is_registered_extension("test")) - self.assertEqual(pystac.STAC_EXTENSIONS.get_registered_extensions(), prev_extensions) + self.assertFalse(pystac.EXTENSION_HOOKS.is_registered_extension("test")) + self.assertEqual(pystac.EXTENSION_HOOKS.get_registered_extensions(), prev_extensions) def test_getattribute_overload(self): catalog = Catalog(id='test', description='test') diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index c6c014e5c..a5849c6af 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -1,9 +1,9 @@ import json import unittest -import pystac -from pystac import Item +import pystac as ps from tests.utils import (TestCases, test_to_from_dict) +from pystac.extensions import file_ext from pystac.extensions.file import FileDataType @@ -16,61 +16,61 @@ def setUp(self): def test_to_from_dict(self): with open(self.FILE_EXAMPLE_URI) as f: item_dict = json.load(f) - test_to_from_dict(self, Item, item_dict) + test_to_from_dict(self, ps.Item, item_dict) def test_validate_file(self): - item = pystac.read_file(self.FILE_EXAMPLE_URI) + item = ps.Item.from_file(self.FILE_EXAMPLE_URI) item.validate() def test_asset_size(self): - item = pystac.read_file(self.FILE_EXAMPLE_URI) + item = ps.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get - self.assertEqual(146484, item.ext.file.get_size(asset)) + self.assertEqual(146484, file_ext(asset).size) # Set new_size = 1 - item.ext.file.set_size(new_size, asset) - self.assertEqual(new_size, item.ext.file.get_size(asset)) + file_ext(asset).size = new_size + self.assertEqual(new_size, file_ext(asset).size) item.validate() def test_asset_checksum(self): - item = pystac.read_file(self.FILE_EXAMPLE_URI) + item = ps.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", - item.ext.file.get_checksum(asset)) + file_ext(asset).checksum) # Set new_checksum = "90e40210163700a8a6501eccd00b6d3b44ddaed0" - item.ext.file.set_checksum(new_checksum, asset) - self.assertEqual(new_checksum, item.ext.file.get_checksum(asset)) + file_ext(asset).checksum = new_checksum + self.assertEqual(new_checksum, file_ext(asset).checksum) item.validate() def test_asset_data_type(self): - item = pystac.read_file(self.FILE_EXAMPLE_URI) + item = ps.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get - self.assertEqual(FileDataType.UINT8, item.ext.file.get_data_type(asset)) + self.assertEqual(FileDataType.UINT8, file_ext(asset).data_type) # Set new_data_type = FileDataType.UINT16 - item.ext.file.set_data_type(new_data_type, asset) - self.assertEqual(new_data_type, item.ext.file.get_data_type(asset)) + file_ext(asset).data_type = new_data_type + self.assertEqual(new_data_type, file_ext(asset).data_type) item.validate() def test_asset_nodata(self): - item = pystac.read_file(self.FILE_EXAMPLE_URI) + item = ps.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get - self.assertEqual([], item.ext.file.get_nodata(asset)) + self.assertEqual([], file_ext(asset).nodata) # Set new_nodata = [-1] - item.ext.file.set_nodata(new_nodata, asset) - self.assertEqual(new_nodata, item.ext.file.get_nodata(asset)) + file_ext(asset).nodata = new_nodata + self.assertEqual(new_nodata, file_ext(asset).nodata) item.validate() diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index b3a007f2a..a4308d9f7 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -1,11 +1,13 @@ import json import os +from pystac.extensions.label import LabelClasses, LabelCount, LabelOverview, LabelStatistics, LabelType +from pystac.utils import get_opt import unittest from tempfile import TemporaryDirectory import pystac as ps from pystac import (Catalog, Item, CatalogType, STAC_IO) -from pystac.extensions import label +from pystac.extensions import label_ext import pystac.validation from tests.utils import (TestCases, test_to_from_dict) @@ -25,11 +27,13 @@ def test_to_from_dict(self): def test_from_file(self): label_example_1 = Item.from_file(self.label_example_1_uri) - self.assertEqual(len(label_example_1.ext.label.label_overviews[0].counts), 2) + overviews = get_opt(label_ext(label_example_1).label_overviews) + self.assertEqual(len(get_opt(overviews[0].counts)), 2) label_example_1.validate() label_example_2 = Item.from_file(self.label_example_2_uri) - self.assertEqual(len(label_example_2.ext.label.label_overviews[0].counts), 2) + overviews2 = get_opt(label_ext(label_example_2).label_overviews) + self.assertEqual(len(get_opt(overviews2[0].counts)), 2) label_example_2.validate() @@ -47,7 +51,7 @@ def test_from_file_pre_081(self): d['properties'].pop('label:tasks') label_example_1 = ps.Item.from_dict(d, migrate=True) - self.assertEqual(len(label_example_1.ext.label.label_tasks), 2) + self.assertEqual(len(label_ext(label_example_1).label_tasks or []), 2) def test_get_sources(self): cat = TestCases.test_case_1() @@ -56,15 +60,15 @@ def test_get_sources(self): item_ids = set([i.id for i in items]) for li in items: - if li.ext.implements('label'): - sources = li.ext.label.get_sources() + if label_ext(li).has_extension: + sources = list(label_ext(li).get_sources() or []) self.assertEqual(len(sources), 1) self.assertTrue(sources[0].id in item_ids) def test_validate_label(self): with open(self.label_example_1_uri) as f: label_example_1_dict = json.load(f) - pystac.validation.validate_dict(label_example_1_dict, "ITEM") + pystac.validation.validate_dict(label_example_1_dict, ps.STACObjectType.ITEM) with TemporaryDirectory() as tmp_dir: cat_dir = os.path.join(tmp_dir, 'catalog') @@ -76,7 +80,7 @@ def test_validate_label(self): label_item_read.validate() def test_read_label_item_owns_asset(self): - item = next(x for x in TestCases.test_case_2().get_all_items() if x.ext.implements("label")) + item = next(x for x in TestCases.test_case_2().get_all_items() if label_ext(x).has_extension) assert len(item.assets) > 0 for asset_key in item.assets: self.assertEqual(item.assets[asset_key].owner, item) @@ -86,11 +90,11 @@ def test_label_description(self): # Get self.assertIn("label:description", label_item.properties) - label_desc = label_item.ext.label.label_description + label_desc = label_ext(label_item).label_description self.assertEqual(label_desc, label_item.properties['label:description']) # Set - label_item.ext.label.label_description = "A detailed description" + label_ext(label_item).label_description = "A detailed description" self.assertEqual("A detailed description", label_item.properties['label:description']) label_item.validate() @@ -99,12 +103,12 @@ def test_label_type(self): # Get self.assertIn("label:type", label_item.properties) - label_type = label_item.ext.label.label_type + label_type = label_ext(label_item).label_type self.assertEqual(label_type, label_item.properties['label:type']) # Set - label_item.ext.label.label_type = label.LabelType.RASTER - self.assertEqual(label.LabelType.RASTER, label_item.properties['label:type']) + label_ext(label_item).label_type = LabelType.RASTER + self.assertEqual(LabelType.RASTER, label_item.properties['label:type']) label_item.validate() def test_label_properties(self): @@ -113,31 +117,31 @@ def test_label_properties(self): # Get self.assertIn("label:properties", label_item.properties) - label_prop = label_item.ext.label.label_properties + label_prop = label_ext(label_item).label_properties self.assertEqual(label_prop, label_item.properties['label:properties']) - raster_label_prop = label_item2.ext.label.label_properties + raster_label_prop = label_ext(label_item2).label_properties self.assertEqual(raster_label_prop, None) # Set - label_item.ext.label.label_properties = ["prop1", "prop2"] + label_ext(label_item).label_properties = ["prop1", "prop2"] self.assertEqual(["prop1", "prop2"], label_item.properties['label:properties']) label_item.validate() def test_label_classes(self): # Get label_item = ps.Item.from_file(self.label_example_1_uri) - label_classes = label_item.ext.label.label_classes + label_classes = label_ext(label_item).label_classes - self.assertEqual(len(label_classes), 2) - self.assertEqual(label_classes[1].classes, ["three", "four"]) + self.assertEqual(len(get_opt(label_classes)), 2) + self.assertEqual(get_opt(label_classes)[1].classes, ["three", "four"]) # Set new_classes = [ - label.LabelClasses.create(name="label2", classes=["five", "six"]), - label.LabelClasses.create(name="label", classes=["seven", "eight"]) + LabelClasses.create(name="label2", classes=["five", "six"]), + LabelClasses.create(name="label", classes=["seven", "eight"]) ] - label_item.ext.label.label_classes = new_classes + label_ext(label_item).label_classes = new_classes self.assertEqual([ class_name for lc in label_item.properties["label:classes"] for class_name in lc["classes"] @@ -150,11 +154,11 @@ def test_label_tasks(self): # Get self.assertIn("label:tasks", label_item.properties) - label_prop = label_item.ext.label.label_tasks + label_prop = label_ext(label_item).label_tasks self.assertEqual(label_prop, ["classification", "regression"]) # Set - label_item.ext.label.label_tasks = ["classification"] + label_ext(label_item).label_tasks = ["classification"] self.assertEqual(["classification"], label_item.properties['label:tasks']) label_item.validate() @@ -163,52 +167,52 @@ def test_label_methods(self): # Get self.assertIn("label:methods", label_item.properties) - label_prop = label_item.ext.label.label_methods + label_prop = label_ext(label_item).label_methods self.assertEqual(label_prop, ["manual"]) # Set - label_item.ext.label.label_methods = ["manual", "automated"] + label_ext(label_item).label_methods = ["manual", "automated"] self.assertEqual(["manual", "automated"], label_item.properties['label:methods']) label_item.validate() def test_label_overviews(self): # Get label_item = ps.Item.from_file(self.label_example_1_uri) - label_overviews = label_item.ext.label.label_overviews + label_overviews = get_opt(label_ext(label_item).label_overviews) label_item2 = ps.Item.from_file(self.label_example_2_uri) - label_overviews2 = label_item2.ext.label.label_overviews + label_overviews2 = get_opt(label_ext(label_item2).label_overviews) self.assertEqual(len(label_overviews), 2) self.assertEqual(label_overviews[1].property_key, "label-reg") self.assertEqual(label_overviews2[1].property_key, None) # Raster - label_counts = label_overviews[0].counts + label_counts = get_opt(label_overviews[0].counts) self.assertEqual(label_counts[1].count, 17) - label_item.ext.label.label_overviews[0].counts[1].count = 18 + get_opt(label_ext(label_item).label_overviews)[0].counts[1].count = 18 self.assertEqual(label_item.properties['label:overviews'][0]['counts'][1]['count'], 18) label_statistics = label_overviews[1].statistics self.assertEqual(label_statistics[0].name, "mean") - label_item.ext.label.label_overviews[1].statistics[0].name = "avg" + get_opt(label_ext(label_item).label_overviews)[1].statistics[0].name = "avg" self.assertEqual(label_item.properties['label:overviews'][1]['statistics'][0]['name'], "avg") # Set new_overviews = [ - label.LabelOverview.create(property_key="label2", + LabelOverview.create(property_key="label2", counts=[ - label.LabelCount.create(name="one", count=1), - label.LabelCount.create(name="two", count=1), + LabelCount.create(name="one", count=1), + LabelCount.create(name="two", count=1), ]), - label.LabelOverview.create(property_key="label-reg", + LabelOverview.create(property_key="label-reg", statistics=[ - label.LabelStatistics.create(name="min", value=0.1), - label.LabelStatistics.create(name="max", value=1.0), + LabelStatistics.create(name="min", value=0.1), + LabelStatistics.create(name="max", value=1.0), ]) ] - label_item.ext.label.label_overviews = new_overviews + label_ext(label_item).label_overviews = new_overviews self.assertEqual([(count['name'], count['count']) for count in label_item.properties["label:overviews"][0]['counts']], [("one", 1), ("two", 1)]) diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 22dd3db40..be033f40e 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -3,7 +3,7 @@ # from copy import deepcopy import pystac -from pystac import (Item, Extensions) +from pystac import (Item, _OldExtensionShortIDs) from pystac.extensions import ExtensionError from pystac.extensions.pointcloud import PointcloudSchema, PointcloudStatistic from tests.utils import (TestCases, test_to_from_dict) @@ -26,7 +26,7 @@ def test_apply(self): with self.assertRaises(ExtensionError): item.ext.pointcloud - item.ext.enable(Extensions.POINTCLOUD) + item.ext.enable(_OldExtensionShortIDs.POINTCLOUD) item.ext.pointcloud.apply(1000, 'lidar', 'laszip', [PointcloudSchema({ 'name': 'X', diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 7e420b3bb..787f70bdb 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -3,7 +3,7 @@ from copy import deepcopy import pystac -from pystac import (Item, Extensions) +from pystac import (Item, _OldExtensionShortIDs) from pystac.extensions import ExtensionError from pystac.validation import STACValidationError from tests.utils import (TestCases, test_to_from_dict) @@ -83,7 +83,7 @@ def test_apply(self): with self.assertRaises(ExtensionError): item.ext.proj - item.ext.enable(Extensions.PROJECTION) + item.ext.enable(_OldExtensionShortIDs.PROJECTION) item.ext.projection.apply( 4326, wkt2=WKT2, diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index 86d7f8dc8..1917f9e54 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -13,7 +13,7 @@ def make_item() -> pystac.Item: start = datetime.datetime(2020, 11, 7) item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) - item.ext.enable(pystac.Extensions.SAR) + item.ext.enable(pystac._OldExtensionShortIDs.SAR) return item @@ -21,10 +21,10 @@ class SarItemExtTest(unittest.TestCase): def setUp(self): super().setUp() self.item = make_item() - self.item.ext.enable(pystac.Extensions.SAR) + self.item.ext.enable(pystac._OldExtensionShortIDs.SAR) def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.SAR], self.item.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.SAR], self.item.stac_extensions) def test_required(self): mode: str = 'Nonesense mode' diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index 9484b184e..99b8c561e 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -13,7 +13,7 @@ def make_item() -> pystac.Item: start = datetime.datetime(2018, 1, 2) item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) - item.ext.enable(pystac.Extensions.SAT) + item.ext.enable(pystac._OldExtensionShortIDs.SAT) return item @@ -23,7 +23,7 @@ def setUp(self): self.item = make_item() def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.SAT], self.item.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.SAT], self.item.stac_extensions) def test_no_args_fails(self): with self.assertRaises(pystac.STACError): diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index be32f7741..07f10eb53 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -32,7 +32,7 @@ def make_item() -> pystac.Item: item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % 2011) - item.ext.enable(pystac.Extensions.SCIENTIFIC) + item.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) return item @@ -40,10 +40,10 @@ class ScientificItemExtTest(unittest.TestCase): def setUp(self): super().setUp() self.item = make_item() - self.item.ext.enable(pystac.Extensions.SCIENTIFIC) + self.item.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.SCIENTIFIC], self.item.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.SCIENTIFIC], self.item.stac_extensions) def test_doi(self): self.item.ext.scientific.apply(DOI) @@ -182,7 +182,7 @@ def make_collection() -> pystac.Collection: collection = pystac.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % 2019) - collection.ext.enable(pystac.Extensions.SCIENTIFIC) + collection.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) return collection @@ -190,10 +190,10 @@ class ScientificCollectionExtTest(unittest.TestCase): def setUp(self): super().setUp() self.collection = make_collection() - self.collection.ext.enable(pystac.Extensions.SCIENTIFIC) + self.collection.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.SCIENTIFIC], self.collection.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.SCIENTIFIC], self.collection.stac_extensions) def test_doi(self): self.collection.ext.scientific.apply(DOI) diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index 7f484e477..1d9573ade 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -3,7 +3,7 @@ import unittest from datetime import datetime -from pystac import (Extensions, Item) +from pystac import (_OldExtensionShortIDs, Item) from pystac.extensions import ExtensionError from pystac.utils import (str_to_datetime, datetime_to_str) from tests.utils import (TestCases, test_to_from_dict) @@ -26,8 +26,8 @@ def test_apply(self): with self.assertRaises(ExtensionError): item.ext.timestamps - item.ext.enable(Extensions.TIMESTAMPS) - self.assertIn(Extensions.TIMESTAMPS, item.stac_extensions) + item.ext.enable(_OldExtensionShortIDs.TIMESTAMPS) + self.assertIn(_OldExtensionShortIDs.TIMESTAMPS, item.stac_extensions) item.ext.timestamps.apply(published=str_to_datetime("2020-01-03T06:45:55Z"), expires=str_to_datetime("2020-02-03T06:45:55Z"), unpublished=str_to_datetime("2020-03-03T06:45:55Z")) diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index a0068e492..0a93c0823 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -18,7 +18,7 @@ def make_item(year: int) -> pystac.Item: item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % year) - item.ext.enable(pystac.Extensions.VERSION) + item.ext.enable(pystac._OldExtensionShortIDs.VERSION) return item @@ -30,10 +30,10 @@ def setUp(self): super().setUp() self.item = make_item(2011) - self.item.ext.enable(pystac.Extensions.VERSION) + self.item.ext.enable(pystac._OldExtensionShortIDs.VERSION) def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.VERSION], self.item.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.VERSION], self.item.stac_extensions) def test_add_version(self): self.item.ext.version.apply(self.version) @@ -117,8 +117,8 @@ def test_full_copy(self): # Enable the version extension on each, and link them # as if they are different versions of the same Item - item1.ext.enable(pystac.Extensions.VERSION) - item2.ext.enable(pystac.Extensions.VERSION) + item1.ext.enable(pystac._OldExtensionShortIDs.VERSION) + item2.ext.enable(pystac._OldExtensionShortIDs.VERSION) item1.ext.version.apply(version='2.0', predecessor=item2) item2.ext.version.apply(version='1.0', successor=item1, latest=item1) @@ -206,7 +206,7 @@ def make_collection(year: int) -> pystac.Collection: collection = pystac.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % year) - collection.ext.enable(pystac.Extensions.VERSION) + collection.ext.enable(pystac._OldExtensionShortIDs.VERSION) return collection @@ -219,7 +219,7 @@ def setUp(self): self.collection = make_collection(2011) def test_stac_extensions(self): - self.assertEqual([pystac.Extensions.VERSION], self.collection.stac_extensions) + self.assertEqual([pystac._OldExtensionShortIDs.VERSION], self.collection.stac_extensions) def test_add_version(self): self.collection.ext.version.apply(self.version) @@ -303,8 +303,8 @@ def test_full_copy(self): # Enable the version extension on each, and link them # as if they are different versions of the same Collection - col1.ext.enable(pystac.Extensions.VERSION) - col2.ext.enable(pystac.Extensions.VERSION) + col1.ext.enable(pystac._OldExtensionShortIDs.VERSION) + col2.ext.enable(pystac._OldExtensionShortIDs.VERSION) col1.ext.version.apply(version='2.0', predecessor=col2) col2.ext.version.apply(version='1.0', successor=col1, latest=col1) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 24030899f..d8aff3529 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -3,7 +3,7 @@ import pystac from pystac.extensions import ExtensionError -from pystac import (Item, Extensions) +from pystac import (Item, _OldExtensionShortIDs) from tests.utils import (TestCases, test_to_from_dict) @@ -22,7 +22,7 @@ def test_apply(self): with self.assertRaises(ExtensionError): item.ext.view - item.ext.enable(Extensions.VIEW) + item.ext.enable(_OldExtensionShortIDs.VIEW) item.ext.view.apply(off_nadir=1.0, incidence_angle=2.0, azimuth=3.0, @@ -34,7 +34,7 @@ def test_apply_one(self): with self.assertRaises(ExtensionError): item.ext.view - item.ext.enable(Extensions.VIEW) + item.ext.enable(_OldExtensionShortIDs.VIEW) item.ext.view.apply(off_nadir=1.0) @unittest.expectedFailure @@ -43,7 +43,7 @@ def test_apply_none(self): with self.assertRaises(ExtensionError): item.ext.view - item.ext.enable(Extensions.VIEW) + item.ext.enable(_OldExtensionShortIDs.VIEW) item.ext.view.apply( off_nadir=None, incidence_angle=None, diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 19caab8b8..fcd1c670f 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,7 +7,7 @@ from collections import defaultdict import pystac as ps -from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, Extensions, +from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, _OldExtensionShortIDs, HIERARCHICAL_LINKS) from pystac.extensions.label import LabelClasses from pystac.validation import STACValidationError @@ -500,7 +500,7 @@ def create_label_item(item: ps.Item) -> List[ps.Item]: bbox=item.bbox, datetime=datetime.utcnow(), properties={}) - label_item.ext.enable(Extensions.LABEL) + label_item.ext.enable(_OldExtensionShortIDs.LABEL) label_ext = label_item.ext.label label_ext.apply( label_description='labels', @@ -910,7 +910,7 @@ def test_full_copy_2(self): bbox=RANDOM_BBOX, datetime=datetime.utcnow(), properties={}, - stac_extensions=[Extensions.LABEL]) + stac_extensions=[_OldExtensionShortIDs.LABEL]) label_ext = label_item.ext.label label_ext.apply( label_description='labels', diff --git a/tests/test_writing.py b/tests/test_writing.py index 1ebbc6e7f..258e96388 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -46,7 +46,7 @@ def validate_asset_href_type(item: ps.Item, item_href: str): def validate_item_link_type(href: str, link_type: str, should_include_self: bool): item_dict = STAC_IO.read_json(href) item = ps.Item.from_file(href) - rel_links = HIERARCHICAL_LINKS + ps.STAC_EXTENSIONS.get_extended_object_links(item) + rel_links = HIERARCHICAL_LINKS + ps.EXTENSION_HOOKS.get_extended_object_links(item) for link in item.get_links(): if not link.rel == 'self': if link_type == 'RELATIVE' and link.rel in rel_links: diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 3b3f8f2f3..c14049253 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -4,7 +4,7 @@ from typing import Any, Dict, List from pystac import (Catalog, Collection, Item, Asset, Extent, TemporalExtent, SpatialExtent, - MediaType, Extensions) + MediaType) from pystac.extensions.label import (LabelOverview, LabelClasses, LabelCount) TEST_LABEL_CATALOG = { @@ -133,7 +133,7 @@ def test_case_3() -> Catalog: datetime=datetime.utcnow(), properties={}) - label_item.ext.enable(Extensions.LABEL) + label_item.ext.enable("label") label_item.ext.label.apply( label_description='ML Labels', label_type='vector', From a72413a9b0722e99508a2469055f4de40fae0e8f Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 26 Apr 2021 23:59:09 -0400 Subject: [PATCH 08/51] Implement collection assets; extension summaries; more fixes. Still a work in progress. --- pystac/__init__.py | 22 +- pystac/asset.py | 155 +++++++++ pystac/collection.py | 123 ++++++- pystac/errors.py | 22 ++ pystac/extensions/__init__.py | 1 + pystac/extensions/base.py | 50 ++- pystac/extensions/eo.py | 219 +++++++----- pystac/extensions/file.py | 66 +++- pystac/extensions/label.py | 72 ++-- pystac/extensions/pointcloud.py | 251 ++++--------- pystac/extensions/version.py | 2 +- pystac/extensions/view.py | 2 + pystac/item.py | 197 ++--------- pystac/serialization/identify.py | 165 +-------- pystac/serialization/migrate.py | 329 ++++++++++-------- pystac/validation/schema_uri_map.py | 162 +-------- pystac/validation/stac_validator.py | 5 +- pystac/version.py | 2 +- tests/data-files/eo/eo-collection.json | 154 ++++++++ tests/data-files/eo/eo-landsat-example.json | 7 +- .../eo/sample-bands-in-item-properties.json | 23 +- tests/data-files/file/file-example.json | 4 +- .../pointcloud/example-laz-no-statistics.json | 4 +- tests/data-files/pointcloud/example-laz.json | 4 +- tests/extensions/test_eo.py | 26 +- tests/extensions/test_pointcloud.py | 85 ++--- 26 files changed, 1103 insertions(+), 1049 deletions(-) create mode 100644 pystac/asset.py create mode 100644 pystac/errors.py create mode 100644 tests/data-files/eo/eo-collection.json diff --git a/pystac/__init__.py b/pystac/__init__.py index ec3fd5119..4a9a7a8a3 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -4,22 +4,7 @@ # flake8: noqa - -class STACError(Exception): - """A STACError is raised for errors relating to STAC, e.g. for - invalid formats or trying to operate on a STAC that does not have - the required information available. - """ - pass - - -class STACTypeError(Exception): - """A STACTypeError is raised when encountering a representation of - a STAC entity that is not correct for the context; for example, if - a Catalog JSON was read in as an Item. - """ - pass - +from pystac.errors import (STACError, STACTypeError, RequiredValueMissing) # type:ignore from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore @@ -33,10 +18,13 @@ class STACTypeError(Exception): Extent, # type:ignore SpatialExtent, # type:ignore TemporalExtent, # type:ignore - Provider) # type:ignore + Provider, # type:ignore + Summaries, # type:ignore + RangeSummary) # type:ignore from pystac.item import (Item, Asset, CommonMetadata) # type:ignore import pystac.validation +from pystac.validation import STACValidationError # type:ignore import pystac.extensions.hooks import pystac.extensions.eo diff --git a/pystac/asset.py b/pystac/asset.py new file mode 100644 index 000000000..6458ad40e --- /dev/null +++ b/pystac/asset.py @@ -0,0 +1,155 @@ +from copy import copy +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union + +import pystac as ps +from pystac.utils import is_absolute_href, make_absolute_href + +if TYPE_CHECKING: + from pystac.collection import Collection as Collection_Type + from pystac.item import Item as Item_Type + +class Asset: + """An object that contains a link to data associated with an Item or Collection that + can be downloaded or streamed. + + Args: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + description (str): A description of the Asset providing additional details, such as + how it was processed or created. CommonMark 0.29 syntax MAY be used for rich + text representation. + media_type (str): Optional description of the media type. Registered Media Types + are preferred. See :class:`~pystac.MediaType` for common media types. + roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) + of the asset. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. + + Attributes: + href (str): Link to the asset object. Relative and absolute links are both allowed. + title (str): Optional displayed title for clients and users. + description (str): A description of the Asset providing additional details, such as + how it was processed or created. CommonMark 0.29 syntax MAY be used for rich + text representation. + media_type (str): Optional description of the media type. Registered Media Types + are preferred. See :class:`~pystac.MediaType` for common media types. + properties (dict): Optional, additional properties for this asset. This is used by + extensions as a way to serialize and deserialize properties on asset + object JSON. + owner: The Item or Collection this asset belongs to, or None if it has no owner. + """ + def __init__(self, + href: str, + title: Optional[str] = None, + description: Optional[str] = None, + media_type: Optional[str] = None, + roles: Optional[List[str]] = None, + properties: Optional[Dict[str, Any]] = None) -> None: + self.href = href + self.title = title + self.description = description + self.media_type = media_type + self.roles = roles + + if properties is not None: + self.properties = properties + else: + self.properties = {} + + # The Item which owns this Asset. + self.owner: Optional[Union[ps.Item, ps.Collection]] = None + + def set_owner(self, obj: Union["Collection_Type", "Item_Type"]) -> None: + """Sets the owning item of this Asset. + + The owning item will be used to resolve relative HREFs of this asset. + + Args: + obj: The Collection or Item that owns this asset. + """ + self.owner = obj + + def get_absolute_href(self) -> Optional[str]: + """Gets the absolute href for this asset, if possible. + + If this Asset has no associated Item, this will return whatever the + href is (as it cannot determine the absolute path, if the asset + href is relative). + + Returns: + str: The absolute HREF of this asset, or a relative HREF is an absolute HREF + cannot be determined. + """ + if not is_absolute_href(self.href): + if self.owner is not None: + return make_absolute_href(self.href, self.owner.get_self_href()) + + return self.href + + def to_dict(self) -> Dict[str, Any]: + """Generate a dictionary representing the JSON of this Asset. + + Returns: + dict: A serialization of the Asset that can be written out as JSON. + """ + + d: Dict[str, Any] = {'href': self.href} + + if self.media_type is not None: + d['type'] = self.media_type + + if self.title is not None: + d['title'] = self.title + + if self.description is not None: + d['description'] = self.description + + if self.properties is not None and len(self.properties) > 0: + for k, v in self.properties.items(): + d[k] = v + + if self.roles is not None: + d['roles'] = self.roles + + return d + + def clone(self) -> "Asset": + """Clones this asset. + + Returns: + Asset: The clone of this asset. + """ + return Asset(href=self.href, + title=self.title, + description=self.description, + media_type=self.media_type, + roles=self.roles, + properties=self.properties) + + def __repr__(self) -> str: + return ''.format(self.href) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "Asset": + """Constructs an Asset from a dict. + + Returns: + Asset: The Asset deserialized from the JSON dict. + """ + d = copy(d) + href = d.pop('href') + media_type = d.pop('type', None) + title = d.pop('title', None) + description = d.pop('description', None) + roles = d.pop('roles', None) + properties = None + if any(d): + properties = d + + return Asset(href=href, + media_type=media_type, + title=title, + description=description, + roles=roles, + properties=properties) diff --git a/pystac/collection.py b/pystac/collection.py index c4a710e35..fb5b49fdd 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,12 +1,13 @@ from copy import (copy, deepcopy) from datetime import datetime as Datetime -from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING, Tuple, Union, cast +from typing import Any, Dict, Generic, Iterable, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast import dateutil.parser from dateutil import tz import pystac as ps from pystac import (STACObjectType, CatalogType) +from pystac.asset import Asset from pystac.catalog import Catalog from pystac.layout import HrefLayoutStrategy from pystac.link import Link @@ -15,6 +16,8 @@ if TYPE_CHECKING: from pystac.item import Item as Item_Type +T = TypeVar('T') + class SpatialExtent: """Describes the spatial extent of a Collection. @@ -371,6 +374,83 @@ def from_dict(d: Dict[str, Any]) -> "Provider": url=d.get('url')) +class RangeSummary(Generic[T]): + def __init__(self, minimum: T, maximum: T): + self.minimum = minimum + self.maximum = maximum + + def to_dict(self) -> Dict[str, Any]: + return {'minimum': self.minimum, 'maximum': self.maximum} + + @classmethod + def from_dict(cls, d: Dict[str, Any], typ: Type[T] = Any) -> "RangeSummary[T]": + minimum: Optional[T] = d.get('minimum') + if minimum is None: + raise ps.RequiredValueMissing("Range summary does not have 'minimum' property") + maximum: Optional[T] = d.get("maximum") + if maximum is None: + raise ps.RequiredValueMissing("Range summary does not have 'maximum' property") + return cls(minimum=minimum, maximum=maximum) + + +class Summaries: + def __init__(self, summaries: Dict[str, Any]) -> None: + self._summaries = summaries + + self.lists: Dict[str, List[Any]] = {} + self.ranges: Dict[str, RangeSummary[Any]] = {} + self.schemas: Dict[str, Dict[str, Any]] = {} + self.other: Dict[str, Any] = {} + + for prop_key, summary in summaries.items(): + self.add(prop_key, summary) + + def get_list(self, prop: str, typ: Type[T]) -> Optional[List[T]]: + return self.lists.get(prop) + + def get_range(self, prop: str, typ: Type[T]) -> Optional[RangeSummary[T]]: + return self.ranges.get(prop) + + def get_schema(self, prop: str) -> Optional[Dict[str, Any]]: + return self.schemas.get(prop) + + def add(self, prop_key: str, summary: Union[List[Any], RangeSummary[Any], Dict[str, + Any]]) -> None: + if isinstance(summary, list): + self.lists[prop_key] = summary + elif isinstance(summary, dict): + if 'minimum' in summary: + self.ranges[prop_key] = RangeSummary[Any].from_dict(cast(Dict[str, Any], summary)) + else: + self.schemas[prop_key] = summary + elif isinstance(summary, RangeSummary): + self.ranges[prop_key] = summary + else: + self.other[prop_key] = summary + + def remove(self, prop_key: str) -> None: + self.lists.pop(prop_key, None) + self.ranges.pop(prop_key, None) + self.schemas.pop(prop_key, None) + self.other.pop(prop_key, None) + + def is_empty(self): + return not (any(self.lists) or any(self.ranges) or any(self.schemas) or any(self.other)) + + def to_dict(self) -> Dict[str, Any]: + return { + **self.lists, + **{k: v.to_dict() + for k, v in self.ranges.items()}, + **self.schemas, + **self.other + } + + @classmethod + def empty(cls) -> "Summaries": + return Summaries({}) + + class Collection(Catalog): """A Collection extends the Catalog spec with additional metadata that helps enable discovery. @@ -394,7 +474,6 @@ class Collection(Catalog): Defaults to 'proprietary'. keywords (List[str]): Optional list of keywords describing the collection. providers (List[Provider]): Optional list of providers of this Collection. - properties (dict): Optional dict of common fields across referenced items. summaries (dict): An optional map of property summaries, either a set of values or statistics such as a range. extra_fields (dict or None): Extra fields that are part of the top-level JSON properties @@ -409,7 +488,7 @@ class Collection(Catalog): stac_extensions (List[str]): Optional list of extensions the Collection implements. keywords (List[str] or None): Optional list of keywords describing the collection. providers (List[Provider] or None): Optional list of providers of this Collection. - properties (dict or None): Optional dict of common fields across referenced items. + assets (Optional[Dict[str, Asset]]): Optional map of Assets summaries (dict or None): An optional map of property summaries, either a set of values or statistics such as a range. links (List[Link]): A list of :class:`~pystac.Link` objects representing @@ -434,7 +513,7 @@ def __init__(self, license: str = 'proprietary', keywords: Optional[List[str]] = None, providers: Optional[List[Provider]] = None, - summaries: Optional[Dict[str, Any]] = None): + summaries: Optional[Summaries] = None): super().__init__(id, description, title, stac_extensions, extra_fields, href, catalog_type or CatalogType.ABSOLUTE_PUBLISHED) self.extent = extent @@ -443,7 +522,9 @@ def __init__(self, self.stac_extensions: List[str] = stac_extensions or [] self.keywords = keywords self.providers = providers - self.summaries = summaries + self.summaries = summaries or Summaries.empty() + + self.assets: Dict[str, Asset] = {} def __repr__(self) -> str: return ''.format(self.id) @@ -465,8 +546,10 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d['keywords'] = self.keywords if self.providers is not None: d['providers'] = list(map(lambda x: x.to_dict(), self.providers)) - if self.summaries is not None: - d['summaries'] = self.summaries + if not self.summaries.is_empty(): + d['summaries'] = self.summaries.to_dict() + if any(self.assets): + d['assets'] = {k: v.to_dict() for k, v in self.assets.items() } return d @@ -524,6 +607,10 @@ def from_dict(cls, if providers is not None: providers = list(map(lambda x: Provider.from_dict(x), providers)) summaries = d.get('summaries') + if summaries is not None: + summaries = Summaries(summaries) + + assets: Optional[Dict[str, Any]] = d.get('assets', None) links = d.pop('links') d.pop('stac_version') @@ -549,8 +636,30 @@ def from_dict(cls, if link['rel'] != 'self' or href is None: collection.add_link(Link.from_dict(link)) + if assets is not None: + for asset_key, asset_dict in assets: + collection.add_asset(asset_key, Asset(asset_dict)) + return collection + def get_assets(self) -> Dict[str, Asset]: + """Get this item's assets. + + Returns: + Dict[str, Asset]: A copy of the dictionary of this item's assets. + """ + return dict(self.assets.items()) + + def add_asset(self, key: str, asset: Asset) -> None: + """Adds an Asset to this item. + + Args: + key (str): The unique key of this asset. + asset (Asset): The Asset to add. + """ + asset.set_owner(self) + self.assets[key] = asset + def update_extent_from_items(self) -> None: """ Update datetime and bbox based on all items to a single bbox and time window. diff --git a/pystac/errors.py b/pystac/errors.py new file mode 100644 index 000000000..fcc1d6c52 --- /dev/null +++ b/pystac/errors.py @@ -0,0 +1,22 @@ +class STACError(Exception): + """A STACError is raised for errors relating to STAC, e.g. for + invalid formats or trying to operate on a STAC that does not have + the required information available. + """ + pass + + +class STACTypeError(Exception): + """A STACTypeError is raised when encountering a representation of + a STAC entity that is not correct for the context; for example, if + a Catalog JSON was read in as an Item. + """ + pass + +class RequiredValueMissing(Exception): + """ This error is raised when a required value was expected + to be there but was missing or None. This will happen, for example, + in an extension that has required properties, where the required + property is missing from the extended object + """ + pass \ No newline at end of file diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index 46ff4be3c..2c1f8b2eb 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -9,3 +9,4 @@ class ExtensionError(Exception): from pystac.extensions.eo import eo_ext # type:ignore from pystac.extensions.file import file_ext # type:ignore from pystac.extensions.label import label_ext # type:ignore +from pystac.extensions.pointcloud import pointcloud_ext # type:ignore diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 872ba160f..2fee8a0d0 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,5 +1,5 @@ -from abc import ABC -from typing import Generic, Iterable, Optional, Dict, Any, Type, TypeVar +from abc import ABC, abstractmethod +from typing import Generic, Iterable, List, Optional, Dict, Any, Type, TypeVar, Union import pystac as ps @@ -8,8 +8,21 @@ class ExtensionException(Exception): pass +class SummariesExtension: + def __init__(self, collection: ps.Collection) -> None: + self.summaries = collection.summaries + + def _set_summary(self, prop_key: str, v: Optional[Union[List[Any], ps.RangeSummary[Any], + Dict[str, Any]]]) -> None: + if v is None: + self.summaries.remove(prop_key) + else: + self.summaries.add(prop_key, v) + + P = TypeVar('P') + class PropertiesExtension(ABC): properties: Dict[str, Any] additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None @@ -35,23 +48,26 @@ def _set_property(self, prop_name: str, v: Optional[Any], pop_if_none: bool = Tr S = TypeVar('S', bound=ps.STACObject) -class EnableExtensionMixin(Generic[S]): - obj: S - schema_uri: str +class ExtensionManagementMixin(Generic[S], ABC): + @classmethod + @abstractmethod + def get_schema_uri(cls) -> str: + pass - def add_extension(self) -> None: - if self.obj.stac_extensions is None: - self.obj.stac_extensions = [self.schema_uri] + @classmethod + def add_to(cls, obj: S) -> None: + if obj.stac_extensions is None: + obj.stac_extensions = [cls.get_schema_uri()] else: - self.obj.stac_extensions.append(self.schema_uri) + obj.stac_extensions.append(cls.get_schema_uri()) - def remove_extension(self) -> None: - if self.obj.stac_extensions is not None: - self.obj.stac_extensions = [ - uri for uri in self.obj.stac_extensions if uri != self.schema_uri + @classmethod + def remove_from(cls, obj: S) -> None: + if obj.stac_extensions is not None: + obj.stac_extensions = [ + uri for uri in obj.stac_extensions if uri != cls.get_schema_uri() ] - @property - def has_extension(self) -> bool: - return (self.obj.stac_extensions is not None - and self.schema_uri in self.obj.stac_extensions) + @classmethod + def has_extension(cls, obj: S) -> bool: + return (obj.stac_extensions is not None and cls.get_schema_uri() in obj.stac_extensions) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 1026069c6..5e84ed7cd 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -1,14 +1,15 @@ +from pystac.collection import RangeSummary import re -from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, Union, cast +from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar, cast import pystac as ps -from pystac.extensions.base import EnableExtensionMixin, ExtensionException, PropertiesExtension +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension from pystac.extensions.hooks import ExtensionHooks +from pystac.extensions import view from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.utils import map_opt - -T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -16,6 +17,92 @@ CLOUD_COVER_PROP = "eo:cloud_cover" +class EOExtensionHooks(ExtensionHooks): + schema_uri = SCHEMA_URI + + def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if not 'properties' in d: + return + + if version < '0.5': + if 'eo:crs' in d['properties']: + # Try to pull out the EPSG code. + # Otherwise, just leave it alone. + wkt = d['properties']['eo:crs'] + matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) + if len(matches) > 0: + epsg_code = matches[-1].group(1) + d['properties'].pop('eo:crs') + d['properties']['eo:epsg'] = int(epsg_code) + + if version < '0.6': + # Change eo:bands from a dict to a list. eo:bands on an asset + # is an index instead of a dict key. eo:bands is in properties. + bands_dict = d['eo:bands'] + keys_to_indices: Dict[str, int] = {} + bands: List[Dict[str, Any]] = [] + for i, (k, band) in enumerate(bands_dict.items()): + keys_to_indices[k] = i + bands.append(band) + + d.pop('eo:bands') + d['properties']['eo:bands'] = bands + for k, asset in d['assets'].items(): + if 'eo:bands' in asset: + asset_band_indices: List[int] = [] + for bk in asset['eo:bands']: + asset_band_indices.append(keys_to_indices[bk]) + asset['eo:bands'] = sorted(asset_band_indices) + + if version < '0.9': + # Some eo fields became common_metadata + if 'eo:platform' in d['properties'] and 'platform' not in d['properties']: + d['properties']['platform'] = d['properties']['eo:platform'] + del d['properties']['eo:platform'] + + if 'eo:instrument' in d['properties'] and 'instruments' not in d['properties']: + d['properties']['instruments'] = [d['properties']['eo:instrument']] + del d['properties']['eo:instrument'] + + if 'eo:constellation' in d['properties'] and 'constellation' not in d['properties']: + d['properties']['constellation'] = d['properties']['eo:constellation'] + del d['properties']['eo:constellation'] + + # Some eo fields became view extension fields + eo_to_view_fields = [ + 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' + ] + + for field in eo_to_view_fields: + if 'eo:{}'.format(field) in d['properties']: + if 'stac_extensions' not in d: + d['stac_extensions'] = [] + if not view.SCHEMA_URI in d['stac_extensions']: + d['stac_extensions'].append(view.SCHEMA_URI) + if not 'view:{}'.format(field) in d['properties']: + d['properties']['view:{}'.format(field)] = \ + d['properties']['eo:{}'.format(field)] + del d['properties']['eo:{}'.format(field)] + + if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: + # gsd moved from eo to common metadata + if 'eo:gsd' in d['properties']: + d['properties']['gsd'] = d['properties']['eo:gsd'] + del d['properties']['eo:gsd'] + + # The way bands were declared in assets changed. + # In 1.0.0-beta.1 they are inlined into assets as + # opposed to having indices back into a property-level array. + if 'eo:bands' in d['properties']: + bands = d['properties']['eo:bands'] + for asset in d['assets'].values(): + if 'eo:bands' in asset: + new_bands: List[Dict[str, Any]] = [] + for band_index in asset['eo:bands']: + new_bands.append(bands[band_index]) + asset['eo:bands'] = new_bands + + class Band: """Represents Band information attached to an Item that implements the eo extension. @@ -218,7 +305,7 @@ def band_description(common_name: str) -> Optional[str]: return None -class EOExtension(Generic[T], PropertiesExtension): +class EOExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """EOItemExt is the extension of the Item in the eo extension which represents a snapshot of the earth for a single date and time. @@ -273,12 +360,14 @@ def cloud_cover(self) -> Optional[float]: def cloud_cover(self, v: Optional[float]) -> None: self._set_property(CLOUD_COVER_PROP, v) + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI -class ItemEOExtension(EnableExtensionMixin[ps.Item], EOExtension[ps.Item]): - schema_uri = SCHEMA_URI +class ItemEOExtension(EOExtension[ps.Item]): def __init__(self, item: ps.Item): - self.obj = item + self.item = item self.properties = item.properties def _get_bands(self) -> Optional[List[Band]]: @@ -290,7 +379,7 @@ def _get_bands(self) -> Optional[List[Band]]: # get assets with eo:bands even if not in item if bands is None: asset_bands: List[Dict[str, Any]] = [] - for _, value in self.obj.get_assets().items(): + for _, value in self.item.get_assets().items(): if BANDS_PROP in value.properties: asset_bands.extend(cast(List[Dict[str, Any]], value.properties.get(BANDS_PROP))) if any(asset_bands): @@ -301,21 +390,44 @@ def _get_bands(self) -> Optional[List[Band]]: return None def __repr__(self) -> str: - return ''.format(self.obj.id) + return ''.format(self.item.id) class AssetEOExtension(EOExtension[ps.Asset]): def __init__(self, asset: ps.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner: + if asset.owner and isinstance(asset.owner, ps.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: return ''.format(self.asset_href) -def eo_ext(obj: Union[ps.Item, ps.Asset]) -> EOExtension[T]: +class SummariesEOExtension(SummariesExtension): + @property + def bands(self) -> Optional[List[Band]]: + """Get or sets a list of :class:`~pystac.Band` objects that represent + the available bands. + """ + return map_opt(lambda bands: [Band(b) for b in bands], + self.summaries.get_list(BANDS_PROP, Dict[str, Any])) + + @bands.setter + def bands(self, v: Optional[List[Band]]) -> None: + self._set_summary(BANDS_PROP, map_opt(lambda x: [b.to_dict() for b in x], v)) + + @property + def cloud_cover(self) -> Optional[RangeSummary[float]]: + """Get or sets the range of cloud cover from the summary.""" + return self.summaries.get_range(CLOUD_COVER_PROP, float) + + @cloud_cover.setter + def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: + self._set_summary(CLOUD_COVER_PROP, v) + + +def eo_ext(obj: T) -> EOExtension[T]: if isinstance(obj, ps.Item): return ItemEOExtension(obj) elif isinstance(obj, ps.Asset): @@ -323,88 +435,9 @@ def eo_ext(obj: Union[ps.Item, ps.Asset]) -> EOExtension[T]: else: raise ExtensionException(f"EO extension does not apply to type {type(obj)}") -class EOExtensionHooks(ExtensionHooks): - schema_uri = SCHEMA_URI - def migrate(self, d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - added_extensions: Set[str] = set([]) - if version < '0.5': - if 'eo:crs' in d['properties']: - # Try to pull out the EPSG code. - # Otherwise, just leave it alone. - wkt = d['properties']['eo:crs'] - matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) - if len(matches) > 0: - epsg_code = matches[-1].group(1) - d['properties'].pop('eo:crs') - d['properties']['eo:epsg'] = int(epsg_code) - - if version < '0.6': - # Change eo:bands from a dict to a list. eo:bands on an asset - # is an index instead of a dict key. eo:bands is in properties. - bands_dict = d['eo:bands'] - keys_to_indices: Dict[str, int] = {} - bands: List[Dict[str, Any]] = [] - for i, (k, band) in enumerate(bands_dict.items()): - keys_to_indices[k] = i - bands.append(band) +def eo_summaries(obj: ps.Collection) -> SummariesEOExtension: + return SummariesEOExtension(obj) - d.pop('eo:bands') - d['properties']['eo:bands'] = bands - for k, asset in d['assets'].items(): - if 'eo:bands' in asset: - asset_band_indices: List[int] = [] - for bk in asset['eo:bands']: - asset_band_indices.append(keys_to_indices[bk]) - asset['eo:bands'] = sorted(asset_band_indices) - - if version < '0.9': - # Some eo fields became common_metadata - if 'eo:platform' in d['properties'] and 'platform' not in d['properties']: - d['properties']['platform'] = d['properties']['eo:platform'] - del d['properties']['eo:platform'] - - if 'eo:instrument' in d['properties'] and 'instruments' not in d['properties']: - d['properties']['instruments'] = [d['properties']['eo:instrument']] - del d['properties']['eo:instrument'] - - if 'eo:constellation' in d['properties'] and 'constellation' not in d['properties']: - d['properties']['constellation'] = d['properties']['eo:constellation'] - del d['properties']['eo:constellation'] - - # Some eo fields became view extension fields - eo_to_view_fields = [ - 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' - ] - - view_enabled = 'view' in d['stac_extensions'] - for field in eo_to_view_fields: - if 'eo:{}'.format(field) in d['properties']: - if not view_enabled: - added_extensions.add('view') - view_enabled = True - if not 'view:{}'.format(field) in d['properties']: - d['properties']['view:{}'.format(field)] = \ - d['properties']['eo:{}'.format(field)] - del d['properties']['eo:{}'.format(field)] - - if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: - # gsd moved from eo to common metadata - if 'eo:gsd' in d['properties']: - d['properties']['gsd'] = d['properties']['eo:gsd'] - del d['properties']['eo:gsd'] - - # The way bands were declared in assets changed. - # In 1.0.0-beta.1 they are inlined into assets as - # opposed to having indices back into a property-level array. - if 'eo:bands' in d['properties']: - bands = d['properties']['eo:bands'] - for asset in d['assets'].values(): - if 'eo:bands' in asset: - new_bands: List[Dict[str, Any]] = [] - for band_index in asset['eo:bands']: - new_bands.append(bands[band_index]) - asset['eo:bands'] = new_bands EO_EXTENSION_HOOKS: ExtensionHooks = EOExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 3c3b06cbe..4c629c6e6 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,11 +1,11 @@ import enum -from typing import Any, Generic, List, Optional, TypeVar, Union +from typing import Any, Generic, List, Optional, TypeVar import pystac as ps -from pystac.extensions.base import EnableExtensionMixin, ExtensionException, PropertiesExtension +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension from pystac.utils import map_opt -T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -37,7 +37,7 @@ def __str__(self) -> str: OTHER = "other" -class FileExtension(Generic[T], PropertiesExtension): +class FileExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """FileItemExt is the extension of the Item in the file extension which adds file related details such as checksum, data type and size for assets. @@ -118,33 +118,77 @@ def checksum(self) -> Optional[str]: def checksum(self, v: Optional[str]) -> None: self._set_property(CHECKSUM_PROP, v) + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI -class ItemFileExtension(EnableExtensionMixin[ps.Item], FileExtension[ps.Item]): - schema_uri = SCHEMA_URI +class ItemFileExtension(FileExtension[ps.Item]): def __init__(self, item: ps.Item): - self.obj = item + self.item = item self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.obj.id) + return ''.format(self.item.id) class AssetFileExtension(FileExtension[ps.Asset]): def __init__(self, asset: ps.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner: + if asset.owner and isinstance(asset.owner, ps.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: return ''.format(self.asset_href) -def file_ext(obj: Union[ps.Item, ps.Asset]) -> FileExtension[T]: +class SummariesFileExtension(SummariesExtension): + @property + def data_type(self) -> Optional[List[FileDataType]]: + """Get or sets the data_type of the file. + + Returns: + FileDataType + """ + return map_opt(lambda x: [FileDataType(t) for t in x], + self.summaries.get_list(DATA_TYPE_PROP, str)) + + @data_type.setter + def data_type(self, v: Optional[List[FileDataType]]) -> None: + self._set_summary(DATA_TYPE_PROP, map_opt(lambda x: [str(t) for t in x], v)) + + @property + def size(self) -> Optional[ps.RangeSummary[int]]: + """Get or sets the size in bytes of the file + + Returns: + int or None + """ + return self.summaries.get_range(SIZE_PROP, int) + + @size.setter + def size(self, v: Optional[ps.RangeSummary[int]]) -> None: + self._set_summary(SIZE_PROP, v) + + @property + def nodata(self) -> Optional[List[Any]]: + """Get or sets the list of no data values""" + return self.summaries.get_list(NODATA_PROP, List[Any]) + + @nodata.setter + def nodata(self, v: Optional[List[Any]]) -> None: + self._set_summary(NODATA_PROP, v) + + +def file_ext(obj: T) -> FileExtension[T]: if isinstance(obj, ps.Item): return ItemFileExtension(obj) elif isinstance(obj, ps.Asset): return AssetFileExtension(obj) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + + +def file_summaries(obj: ps.Collection) -> SummariesFileExtension: + return SummariesFileExtension(obj) \ No newline at end of file diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 3ceb8147e..fed104e86 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -1,16 +1,46 @@ """STAC Model classes for Label extension. """ from enum import Enum +from pystac.extensions.base import ExtensionManagementMixin from typing import Any, Dict, Iterable, List, Optional, Union, cast import pystac as ps from pystac.serialization.identify import STACJSONDescription, STACVersionID -from pystac.extensions.base import EnableExtensionMixin from pystac.extensions.hooks import ExtensionHooks SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json" +class LabelExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + + def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: + if isinstance(so, ps.Item): + return ['source'] + return None + + def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': + props = d['properties'] + # Migrate 0.8.0-rc1 non-pluralized forms + # As it's a common mistake, convert for any pre-1.0.0 version. + if 'label:property' in props and 'label:properties' not in props: + props['label:properties'] = props['label:property'] + del props['label:property'] + + if 'label:task' in props and 'label:tasks' not in props: + props['label:tasks'] = props['label:task'] + del props['label:task'] + + if 'label:overview' in props and 'label:overviews' not in props: + props['label:overviews'] = props['label:overview'] + del props['label:overview'] + + if 'label:method' in props and 'label:methods' not in props: + props['label:methods'] = props['label:method'] + del props['label:method'] + + class LabelType(str, Enum): """Enumerates valid label types (RASTER or VECTOR).""" def __str__(self) -> str: @@ -403,7 +433,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class ItemLabelExtension(EnableExtensionMixin[ps.Item]): +class LabelExtension(ExtensionManagementMixin[ps.Item]): """A LabelItemExt is the extension of the Item in the label extension which represents a polygon, set of polygons, or raster data defining labels and label metadata and should be part of a Collection. @@ -445,7 +475,7 @@ def apply(self, Feature of the label asset's FeatureCollection that contains the classes (keywords from label:classes if the property defines classes). If labels are rasters, this should be None. - label_classes (List[LabelClass]): Optional, but required if ussing categorical data. + label_classes (List[LabelClass]): Optional, but required if using categorical data. A list of LabelClasses defining the list of possible class names for each label:properties. (e.g., tree, building, car, hippo) label_tasks (List[str]): Recommended to be a subset of 'regression', 'classification', @@ -684,39 +714,13 @@ def add_geojson_labels(self, """ self.add_labels(href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON) -def label_ext(item: ps.Item) -> ItemLabelExtension: - return ItemLabelExtension(item) - - -class LabelExtensionHooks(ExtensionHooks): - schema_uri: str = SCHEMA_URI - - def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: - if isinstance(so, ps.Item): - return ['source'] - return None - - def migrate(self, d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': - props = d['properties'] - # Migrate 0.8.0-rc1 non-pluralized forms - # As it's a common mistake, convert for any pre-1.0.0 version. - if 'label:property' in props and 'label:properties' not in props: - props['label:properties'] = props['label:property'] - del props['label:property'] - - if 'label:task' in props and 'label:tasks' not in props: - props['label:tasks'] = props['label:task'] - del props['label:task'] + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI - if 'label:overview' in props and 'label:overviews' not in props: - props['label:overviews'] = props['label:overview'] - del props['label:overview'] - if 'label:method' in props and 'label:methods' not in props: - props['label:methods'] = props['label:method'] - del props['label:method'] +def label_ext(item: ps.Item) -> LabelExtension: + return LabelExtension(item) LABEL_EXTENSION_HOOKS: ExtensionHooks = LabelExtensionHooks() diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index dcb7b91f9..4a0867d03 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -1,13 +1,21 @@ -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union +from typing import Any, Dict, Generic, List, Optional, TypeVar import pystac as ps -from pystac.extensions.base import EnableExtensionMixin +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.utils import map_opt - -T = TypeVar('T', contravariant=True, bound=Union[ps.Item, ps.Asset]) +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) SCHEMA_URI = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" +COUNT_PROP = 'pc:count' +TYPE_PROP = "pc:type" +ENCODING_PROP = 'pc:encoding' +SCHEMAS_PROP = 'pc:schemas' +DENSITY_PROP = 'pc:density' +STATISTICS_PROP = 'pc:statistics' + + class PointcloudSchema: """Defines a schema for dimension of a pointcloud (e.g., name, size, type) @@ -190,7 +198,8 @@ def name(self) -> str: """ result = self.properties.get('name') if result is None: - raise ps.STACError(f"Pointcloud statistics does not have name property: {self.properties}") + raise ps.STACError( + f"Pointcloud statistics does not have name property: {self.properties}") return result @name.setter @@ -324,7 +333,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class PointcloudExtension(Generic[T]): +class PointcloudExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """PointcloudItemExt is the extension of an Item in the PointCloud Extension. The Pointclout extension adds pointcloud information to STAC Items. @@ -335,9 +344,6 @@ class PointcloudExtension(Generic[T]): item (Item): The Item that is being extended. """ - def __init__(self, item: ps.Item) -> None: - self.item = item - def apply(self, count: int, type: str, @@ -376,38 +382,14 @@ def count(self) -> int: Returns: int """ - return self.get_count() - - @count.setter - def count(self, v: int) -> None: - self.set_count(v) - - def get_count(self, asset: Optional[ps.Asset] = None) -> int: - """Gets an Item or an Asset count. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'pc:count' not in asset.properties: - result = self.item.properties.get('pc:count') - else: - result = asset.properties.get('pc:count') - + result = self._get_property(COUNT_PROP, int) if result is None: - raise ps.STACError(f"pc:count not found on point cloud item with ID {self.item.id}") - + raise ps.RequiredValueMissing(f'No {COUNT_PROP} found') return result - def set_count(self, count: int, asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset count. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:count', count, asset) + @count.setter + def count(self, v: int) -> None: + self._set_property(COUNT_PROP, v, pop_if_none=False) @property def type(self) -> str: @@ -416,38 +398,14 @@ def type(self) -> str: Returns: str """ - return self.get_type() - - @type.setter - def type(self, v: str) -> None: - self.set_type(v) - - def get_type(self, asset: Optional[ps.Asset] = None) -> str: - """Gets an Item or an Asset type. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - str - """ - if asset is None or 'pc:type' not in asset.properties: - result = self.item.properties.get('pc:type') - else: - result = asset.properties.get('pc:type') - + result = self._get_property(TYPE_PROP, str) if result is None: - raise ps.STACError(f"pc:type not found on point cloud item with ID {self.item.id}") - + raise ps.RequiredValueMissing(f'No {TYPE_PROP} found') return result - def set_type(self, type: str, asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset type. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:type', type, asset) + @type.setter + def type(self, v: str) -> None: + self._set_property(TYPE_PROP, v, pop_if_none=False) @property def encoding(self) -> str: @@ -459,38 +417,14 @@ def encoding(self) -> str: Returns: str """ - return self.get_encoding() - - @encoding.setter - def encoding(self, v: str) -> None: - self.set_encoding(v) - - def get_encoding(self, asset: Optional[ps.Asset] = None) -> str: - """Gets an Item or an Asset encoding. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - str - """ - if asset is None or 'pc:encoding' not in asset.properties: - result = self.item.properties.get('pc:encoding') - else: - result = asset.properties.get('pc:encoding') - + result = self._get_property(ENCODING_PROP, str) if result is None: - raise ps.STACError(f"pc:encoding not found on point cloud item with ID {self.item.id}") - + raise ps.RequiredValueMissing(f'No {ENCODING_PROP} found') return result - def set_encoding(self, encoding: str, asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset encoding. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:encoding', encoding, asset) + @encoding.setter + def encoding(self, v: str) -> None: + self._set_property(ENCODING_PROP, v, pop_if_none=False) @property def schemas(self) -> List[PointcloudSchema]: @@ -503,39 +437,14 @@ def schemas(self) -> List[PointcloudSchema]: Returns: List[PointcloudSchema] """ - return self.get_schemas() + result = self._get_property(SCHEMAS_PROP, List[Dict[str, Any]]) + if result is None: + raise ps.RequiredValueMissing(f'No {SCHEMAS_PROP} found') + return [PointcloudSchema(s) for s in result] @schemas.setter def schemas(self, v: List[PointcloudSchema]) -> None: - self.set_schemas(v) - - def get_schemas(self, asset: Optional[ps.Asset] = None) -> List[PointcloudSchema]: - """Gets an Item or an Asset projection geometry. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - List[PointcloudSchema] - """ - if asset is None or 'pc:schemas' not in asset.properties: - schemas = self.item.properties.get('pc:schemas') - else: - schemas = asset.properties.get('pc:schemas') - - if schemas is None: - return [] - else: - return [PointcloudSchema(s) for s in schemas] - - def set_schemas(self, schemas: List[PointcloudSchema], asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset schema - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - dicts = [s.to_dict() for s in schemas] - self._set_property('pc:schemas', dicts, asset) + self._set_property(SCHEMAS_PROP, [x.to_dict() for x in v], pop_if_none=False) @property def density(self) -> Optional[float]: @@ -546,33 +455,11 @@ def density(self) -> Optional[float]: Returns: int """ - return self.get_density() + return self._get_property(DENSITY_PROP, float) @density.setter def density(self, v: Optional[float]) -> None: - self.set_density(v) - - def get_density(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset density. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'pc:density' not in asset.properties: - return self.item.properties.get('pc:density') - else: - return asset.properties.get('pc:density') - - def set_density(self, density: Optional[float], asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset density property. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('pc:density', density, asset) + self._set_property(DENSITY_PROP, v) @property def statistics(self) -> Optional[List[PointcloudStatistic]]: @@ -584,47 +471,41 @@ def statistics(self) -> Optional[List[PointcloudStatistic]]: item.ext.pointcloud.statistics = [{ 'name': 'red', 'min': 0, 'max': 255 }] """ - return self.get_statistics() + result = self._get_property(STATISTICS_PROP, List[Dict[str, Any]]) + return map_opt(lambda stats: [PointcloudStatistic(s) for s in stats], result) @statistics.setter def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None: - self.set_statistics(v) - - def get_statistics(self, asset: Optional[ps.Asset] = None) -> Optional[List[PointcloudStatistic]]: - """Gets an Item or an Asset centroid. + set_value = map_opt(lambda stats: [s.to_dict() for s in stats], v) + self._set_property(STATISTICS_PROP, set_value) - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI - Returns: - List[PointCloudStatistics] or None - """ - if asset is None or 'pc:statistics' not in asset.properties: - stats = self.item.properties.get('pc:statistics') - if stats: - return [PointcloudStatistic(s) for s in stats] - else: - return None - else: - return [PointcloudStatistic.create(s) for s in asset.properties['pc:statistics']] +class ItemPointcloudExtension(PointcloudExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties - def set_statistics(self, - statistics: Optional[List[PointcloudStatistic]], - asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset centroid. + def __repr__(self) -> str: + return ''.format(self.item.id) - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if statistics is not None: - self._set_property('pc:statistics', [s.to_dict() for s in statistics], asset) - else: - self._set_property('pc:statistics', None, asset) - @classmethod - def _object_links(cls) -> List[str]: - return [] +class AssetFileExtension(PointcloudExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] - @classmethod - def from_item(cls, item: ps.Item) -> "ItemPointcloudExtension": - return cls(item) + def __repr__(self) -> str: + return ''.format(self.asset_href) + +def pointcloud_ext(obj: T) -> PointcloudExtension[T]: + if isinstance(obj, ps.Item): + return ItemPointcloudExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetFileExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index c15e0b3e8..40dcf6ccf 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -308,7 +308,7 @@ def apply(self, class VersionExtensionHooks(ExtensionHooks): - extension_schema = VERSION_EXT_SCHEMA + schema_uri = VERSION_EXT_SCHEMA def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: if isinstance(so, ps.Collection) or isinstance(so, ps.Item): diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index ef5a98460..5c227bbe5 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -2,6 +2,8 @@ import pystac as ps +SCHEMA_URI = "https://stac-extensions.github.io/view/v1.0.0/schema.json" + class ViewItemExt(): """ViewItemExt is the extension of the Item in the View Geometry Extension. diff --git a/pystac/item.py b/pystac/item.py index c3ffbf3c3..a6ac91d32 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -7,6 +7,7 @@ import pystac as ps from pystac import (STACError, STACObjectType) +from pystac.asset import Asset from pystac.link import Link from pystac.stac_object import STACObject from pystac.utils import (is_absolute_href, make_absolute_href, make_relative_href, datetime_to_str, @@ -66,7 +67,7 @@ def start_datetime(self) -> Optional[Datetime]: def start_datetime(self, v: Optional[Datetime]) -> None: self.set_start_datetime(v) - def get_start_datetime(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + def get_start_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Gets an Item or an Asset start_datetime. If an Asset is supplied and the Item property exists on the Asset, @@ -87,7 +88,7 @@ def get_start_datetime(self, asset: Optional["Asset"] = None) -> Optional[Dateti def set_start_datetime(self, start_datetime: Optional[Datetime], - asset: Optional["Asset"] = None) -> None: + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset start_datetime. If an Asset is supplied, sets the property on the Asset. @@ -116,7 +117,7 @@ def end_datetime(self) -> Optional[Datetime]: def end_datetime(self, v: Optional[Datetime]) -> None: self.set_end_datetime(v) - def get_end_datetime(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + def get_end_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Gets an Item or an Asset end_datetime. If an Asset is supplied and the Item property exists on the Asset, @@ -137,7 +138,7 @@ def get_end_datetime(self, asset: Optional["Asset"] = None) -> Optional[Datetime def set_end_datetime(self, end_datetime: Optional[Datetime], - asset: Optional["Asset"] = None) -> None: + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset end_datetime. If an Asset is supplied, sets the property on the Asset. @@ -164,7 +165,7 @@ def license(self) -> Optional[str]: def license(self, v: Optional[str]) -> None: self.set_license(v) - def get_license(self, asset: Optional["Asset"] = None) -> Optional[str]: + def get_license(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset license. If an Asset is supplied and the Item property exists on the Asset, @@ -178,7 +179,7 @@ def get_license(self, asset: Optional["Asset"] = None) -> Optional[str]: else: return asset.properties.get('license') - def set_license(self, license: Optional[str], asset: Optional["Asset"] = None) -> None: + def set_license(self, license: Optional[str], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset license. If an Asset is supplied, sets the property on the Asset. @@ -205,7 +206,7 @@ def providers(self) -> Optional[List[Provider]]: def providers(self, v: Optional[List[Provider]]) -> None: self.set_providers(v) - def get_providers(self, asset: Optional["Asset"] = None) -> Optional[List[Provider]]: + def get_providers(self, asset: Optional[Asset] = None) -> Optional[List[Provider]]: """Gets an Item or an Asset providers. If an Asset is supplied and the Item property exists on the Asset, @@ -226,7 +227,7 @@ def get_providers(self, asset: Optional["Asset"] = None) -> Optional[List[Provid def set_providers(self, providers: Optional[List[Provider]], - asset: Optional["Asset"] = None) -> None: + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset providers. If an Asset is supplied, sets the property on the Asset. @@ -260,7 +261,7 @@ def platform(self) -> Optional[str]: def platform(self, v: Optional[str]) -> None: self.set_platform(v) - def get_platform(self, asset: Optional["Asset"] = None) -> Optional[str]: + def get_platform(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset platform. If an Asset is supplied and the Item property exists on the Asset, @@ -274,7 +275,7 @@ def get_platform(self, asset: Optional["Asset"] = None) -> Optional[str]: else: return asset.properties.get('platform') - def set_platform(self, platform: Optional[str], asset: Optional["Asset"] = None) -> None: + def set_platform(self, platform: Optional[str], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset platform. If an Asset is supplied, sets the property on the Asset. @@ -298,7 +299,7 @@ def instruments(self) -> Optional[List[str]]: def instruments(self, v: Optional[List[str]]) -> None: self.set_instruments(v) - def get_instruments(self, asset: Optional["Asset"] = None) -> Optional[List[str]]: + def get_instruments(self, asset: Optional[Asset] = None) -> Optional[List[str]]: """Gets an Item or an Asset instruments. If an Asset is supplied and the Item property exists on the Asset, @@ -314,7 +315,7 @@ def get_instruments(self, asset: Optional["Asset"] = None) -> Optional[List[str] def set_instruments(self, instruments: Optional[List[str]], - asset: Optional["Asset"] = None) -> None: + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset instruments. If an Asset is supplied, sets the property on the Asset. @@ -338,7 +339,7 @@ def constellation(self) -> Optional[str]: def constellation(self, v: Optional[str]) -> None: self.set_constellation(v) - def get_constellation(self, asset: Optional["Asset"] = None) -> Optional[str]: + def get_constellation(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset constellation. If an Asset is supplied and the Item property exists on the Asset, @@ -354,7 +355,7 @@ def get_constellation(self, asset: Optional["Asset"] = None) -> Optional[str]: def set_constellation(self, constellation: Optional[str], - asset: Optional["Asset"] = None) -> None: + asset: Optional[Asset] = None) -> None: """Set an Item or an Asset constellation. If an Asset is supplied, sets the property on the Asset. @@ -378,7 +379,7 @@ def mission(self) -> Optional[str]: def mission(self, v: Optional[str]) -> None: self.set_mission(v) - def get_mission(self, asset: Optional["Asset"] = None) -> Optional[str]: + def get_mission(self, asset: Optional[Asset] = None) -> Optional[str]: """Gets an Item or an Asset mission. If an Asset is supplied and the Item property exists on the Asset, @@ -392,7 +393,7 @@ def get_mission(self, asset: Optional["Asset"] = None) -> Optional[str]: else: return asset.properties.get('mission') - def set_mission(self, mission: Optional[str], asset: Optional["Asset"] = None) -> None: + def set_mission(self, mission: Optional[str], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset mission. If an Asset is supplied, sets the property on the Asset. @@ -416,7 +417,7 @@ def gsd(self) -> Optional[float]: def gsd(self, v: Optional[float]) -> None: self.set_gsd(v) - def get_gsd(self, asset: Optional["Asset"] = None) -> Optional[float]: + def get_gsd(self, asset: Optional[Asset] = None) -> Optional[float]: """Gets an Item or an Asset gsd. If an Asset is supplied and the Item property exists on the Asset, @@ -430,7 +431,7 @@ def get_gsd(self, asset: Optional["Asset"] = None) -> Optional[float]: else: return asset.properties.get('gsd') - def set_gsd(self, gsd: Optional[float], asset: Optional["Asset"] = None) -> None: + def set_gsd(self, gsd: Optional[float], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset gsd. If an Asset is supplied, sets the property on the Asset. @@ -457,7 +458,7 @@ def created(self) -> Optional[Datetime]: def created(self, v: Optional[Datetime]) -> None: self.set_created(v) - def get_created(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + def get_created(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Gets an Item or an Asset created time. If an Asset is supplied and the Item property exists on the Asset, @@ -483,7 +484,7 @@ def get_created(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: return created - def set_created(self, created: Optional[Datetime], asset: Optional["Asset"] = None) -> None: + def set_created(self, created: Optional[Datetime], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset created time. If an Asset is supplied, sets the property on the Asset. @@ -518,7 +519,7 @@ def updated(self) -> Optional[Datetime]: def updated(self, v: Optional[Datetime]) -> None: self.set_updated(v) - def get_updated(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: + def get_updated(self, asset: Optional[Asset] = None) -> Optional[Datetime]: """Gets an Item or an Asset updated time. If an Asset is supplied and the Item property exists on the Asset, @@ -544,7 +545,7 @@ def get_updated(self, asset: Optional["Asset"] = None) -> Optional[Datetime]: return updated - def set_updated(self, updated: Optional[Datetime], asset: Optional["Asset"] = None) -> None: + def set_updated(self, updated: Optional[Datetime], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset updated time. If an Asset is supplied, sets the property on the Asset. @@ -556,153 +557,6 @@ def set_updated(self, updated: Optional[Datetime], asset: Optional["Asset"] = No asset.properties['updated'] = None if updated is None else datetime_to_str(updated) -class Asset: - """An object that contains a link to data associated with the Item that can be - downloaded or streamed. - - Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) - of the asset. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. - - Attributes: - href (str): Link to the asset object. Relative and absolute links are both allowed. - title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. - owner (Item or None): The Item this asset belongs to. - """ - def __init__(self, - href: str, - title: Optional[str] = None, - description: Optional[str] = None, - media_type: Optional[str] = None, - roles: Optional[List[str]] = None, - properties: Optional[Dict[str, Any]] = None) -> None: - self.href = href - self.title = title - self.description = description - self.media_type = media_type - self.roles = roles - - if properties is not None: - self.properties = properties - else: - self.properties = {} - - # The Item which owns this Asset. - self.owner: Optional[Item] = None - - def set_owner(self, item: "Item") -> None: - """Sets the owning item of this Asset. - - The owning item will be used to resolve relative HREFs of this asset. - - Args: - item (Item): The Item that owns this asset. - """ - self.owner = item - - def get_absolute_href(self) -> Optional[str]: - """Gets the absolute href for this asset, if possible. - - If this Asset has no associated Item, this will return whatever the - href is (as it cannot determine the absolute path, if the asset - href is relative). - - Returns: - str: The absolute HREF of this asset, or a relative HREF is an absolute HREF - cannot be determined. - """ - if not is_absolute_href(self.href): - if self.owner is not None: - return make_absolute_href(self.href, self.owner.get_self_href()) - - return self.href - - def to_dict(self) -> Dict[str, Any]: - """Generate a dictionary representing the JSON of this Asset. - - Returns: - dict: A serialization of the Asset that can be written out as JSON. - """ - - d: Dict[str, Any] = {'href': self.href} - - if self.media_type is not None: - d['type'] = self.media_type - - if self.title is not None: - d['title'] = self.title - - if self.description is not None: - d['description'] = self.description - - if self.properties is not None and len(self.properties) > 0: - for k, v in self.properties.items(): - d[k] = v - - if self.roles is not None: - d['roles'] = self.roles - - return d - - def clone(self) -> "Asset": - """Clones this asset. - - Returns: - Asset: The clone of this asset. - """ - return Asset(href=self.href, - title=self.title, - description=self.description, - media_type=self.media_type, - roles=self.roles, - properties=self.properties) - - def __repr__(self) -> str: - return ''.format(self.href) - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "Asset": - """Constructs an Asset from a dict. - - Returns: - Asset: The Asset deserialized from the JSON dict. - """ - d = copy(d) - href = d.pop('href') - media_type = d.pop('type', None) - title = d.pop('title', None) - description = d.pop('description', None) - roles = d.pop('roles', None) - properties = None - if any(d): - properties = d - - return Asset(href=href, - media_type=media_type, - title=title, - description=description, - roles=roles, - properties=properties) - - class Item(STACObject): """An Item is the core granular entity in a STAC, containing the core metadata that enables any client to search or crawl online catalogs of spatial 'assets' - @@ -867,7 +721,7 @@ def get_assets(self) -> Dict[str, Asset]: """ return dict(self.assets.items()) - def add_asset(self, key: str, asset: Asset) -> "Item": + def add_asset(self, key: str, asset: Asset) -> None: """Adds an Asset to this item. Args: @@ -876,7 +730,6 @@ def add_asset(self, key: str, asset: Asset) -> "Item": """ asset.set_owner(self) self.assets[key] = asset - return self def make_asset_hrefs_relative(self) -> "Item": """Modify each asset's HREF to be relative to this item's self HREF. @@ -958,7 +811,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: if not include_self_link: links = [x for x in links if x.rel != 'self'] - assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items())) + assets = {k: v.to_dict() for k, v in self.assets.items() } if self.datetime is not None: self.properties['datetime'] = datetime_to_str(self.datetime) diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 8873e6ea2..3070b4186 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,6 +1,6 @@ from enum import Enum -from functools import total_ordering, lru_cache -from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast +from functools import total_ordering +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union, cast import pystac as ps from pystac.version import STACVersion @@ -134,167 +134,6 @@ def __repr__(self) -> str: return ''.format(self.min_version, self.max_version) -class OldExtensionSchemaUriMap: - """Implementation of SchemaUriMap that uses schemas hosted by https://schemas.stacspec.org. - - For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the - radiantearth/stac-spec GitHub repo. - """ - - # BASE_URIS contains a list of tuples, the first element is a version range and the - # second being the base URI for schemas for that range. The schema URI of a STAC - # for a particular version uses the base URI associated with the version range which - # contains it. If the version it outside of any VersionRange, there is no URI for the - # schema. - @lru_cache() - def get_base_uris(self) -> List[Tuple[STACVersionRange, Callable[[str], str]]]: - return [(STACVersionRange(min_version='1.0.0-beta.1'), - lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), - (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: - 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] - - # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema URIs - # based on the stac object type and the stac version, using a similar technique as BASE_URIS. - # Uris are contained in a tuple whose first element represents the URI of the latest - # version, so that a search through version ranges is avoided if the STAC being validated - # is the latest version. If it's a previous version, the stac_version that matches - # the listed version range is used, or else the URI from the latest version is used if - # there are no overrides for previous versions. - def get_schema_map(self) -> Dict[str, Any]: - return { - OldExtensionShortIDs.CHECKSUM: ({ - ps.STACObjectType.CATALOG: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/checksum/json-schema/schema.json' - }, None), - OldExtensionShortIDs.COLLECTION_ASSETS: ({ - ps.STACObjectType.COLLECTION: - 'extensions/collection-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.DATACUBE: ({ - ps.STACObjectType.COLLECTION: - 'extensions/datacube/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/datacube/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: None, - ps.STACObjectType.ITEM: None - })]), - OldExtensionShortIDs.EO: ({ - ps.STACObjectType.ITEM: - 'extensions/eo/json-schema/schema.json' - }, None), - OldExtensionShortIDs.ITEM_ASSETS: ({ - ps.STACObjectType.COLLECTION: - 'extensions/item-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.LABEL: ({ - ps.STACObjectType.ITEM: - 'extensions/label/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { - ps.STACObjectType.ITEM: 'extensions/label/schema.json' - })]), - OldExtensionShortIDs.POINTCLOUD: ( - { - ps.STACObjectType.ITEM: None # 'extensions/pointcloud/json-schema/schema.json' - }, - None), - OldExtensionShortIDs.PROJECTION: ({ - ps.STACObjectType.ITEM: - 'extensions/projection/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAR: ({ - ps.STACObjectType.ITEM: - 'extensions/sar/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAT: ({ - ps.STACObjectType.ITEM: - 'extensions/sat/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SCIENTIFIC: ({ - ps.STACObjectType.ITEM: - 'extensions/scientific/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/scientific/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SINGLE_FILE_STAC: ({ - ps.STACObjectType.CATALOG: - 'extensions/single-file-stac/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TILED_ASSETS: ({ - ps.STACObjectType.CATALOG: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/tiled-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TIMESTAMPS: ({ - ps.STACObjectType.ITEM: - 'extensions/timestamps/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VERSION: ({ - ps.STACObjectType.ITEM: - 'extensions/version/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/version/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VIEW: ({ - ps.STACObjectType.ITEM: - 'extensions/view/json-schema/schema.json' - }, None), - - # Removed or renamed extensions. - 'dtr': (None, None), # Invalid schema - 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: - 'extensions/asset/json-schema/schema.json' - })]), - } - - def _append_base_uri_if_needed(self, uri: str, stac_version: str) -> Optional[str]: - # Only append the base URI if it's not already an absolute URI - if '://' not in uri: - base_uri = None - for version_range, f in self.get_base_uris(): - if version_range.contains(stac_version): - base_uri = f(stac_version) - return '{}/{}'.format(base_uri, uri) - - # No JSON Schema for the old extension - return None - else: - return uri - - def get_extension_schema_uri(self, extension_id: str, object_type: "STACObjectType_Type", - stac_version: str) -> Optional[str]: - uri = None - - is_latest = stac_version == ps.get_stac_version() - - ext_map = self.get_schema_map() - if extension_id in ext_map: - if ext_map[extension_id][0] and \ - object_type in ext_map[extension_id][0]: - uri = ext_map[extension_id][0][object_type] - - if not is_latest: - if ext_map[extension_id][1]: - for version_range, ext_uris in ext_map[extension_id][1]: - if version_range.contains(stac_version): - if object_type in ext_uris: - uri = ext_uris[object_type] - break - - if uri is None: - return uri - else: - return self._append_base_uri_if_needed(uri, stac_version) - - class STACJSONDescription: """Describes the STAC object information for a STAC object represented in JSON diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 6f519f3b1..0025c7cee 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -1,10 +1,14 @@ -import re +from functools import lru_cache from copy import deepcopy -from typing import Any, Callable, Dict, List, Optional, Set, Tuple +from typing import Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING, Tuple import pystac as ps from pystac.version import STACVersion -from pystac.serialization.identify import (STACJSONDescription, STACVersionID, STACVersionRange, OldExtensionShortIDs) +from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID, + STACVersionRange) + +if TYPE_CHECKING: + from pystac.stac_object import STACObjectType as STACObjectType_Type def _migrate_links(d: Dict[str, Any], version: STACVersionID) -> None: @@ -42,6 +46,176 @@ def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, # Extensions +class OldExtensionSchemaUriMap: + """Ties old extension IDs to schemas hosted by https://schemas.stacspec.org. + + For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the + radiantearth/stac-spec GitHub repo. + """ + + # BASE_URIS contains a list of tuples, the first element is a version range and the + # second being the base URI for schemas for that range. The schema URI of a STAC + # for a particular version uses the base URI associated with the version range which + # contains it. If the version it outside of any VersionRange, there is no URI for the + # schema. + @classmethod + @lru_cache() + def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: + return [(STACVersionRange(min_version='1.0.0-beta.1'), + lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), + (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: + 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] + + # DEFAULT_SCHEMA_MAP contains a structure that matches extension schema URIs + # based on the stac object type, extension ID and the stac version. + # Uris are contained in a tuple whose first element represents the URI of the latest + # version, so that a search through version ranges is avoided if the STAC being validated + # is the latest version. If it's a previous version, the stac_version that matches + # the listed version range is used, or else the URI from the latest version is used if + # there are no overrides for previous versions. + @classmethod + @lru_cache() + def get_schema_map(cls) -> Dict[str, Any]: + return { + OldExtensionShortIDs.CHECKSUM: ({ + ps.STACObjectType.CATALOG: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/checksum/json-schema/schema.json' + }, None), + OldExtensionShortIDs.COLLECTION_ASSETS: ({ + ps.STACObjectType.COLLECTION: + 'extensions/collection-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.DATACUBE: ({ + ps.STACObjectType.COLLECTION: + 'extensions/datacube/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/datacube/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: None, + ps.STACObjectType.ITEM: None + })]), + OldExtensionShortIDs.EO: ({ + ps.STACObjectType.ITEM: + 'extensions/eo/json-schema/schema.json' + }, None), + OldExtensionShortIDs.ITEM_ASSETS: ({ + ps.STACObjectType.COLLECTION: + 'extensions/item-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.LABEL: ({ + ps.STACObjectType.ITEM: + 'extensions/label/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { + ps.STACObjectType.ITEM: 'extensions/label/schema.json' + })]), + OldExtensionShortIDs.POINTCLOUD: ( + { + # Poincloud schema was broken in 1.0.0-beta.2 and prior; + # Use this schema version (corresponding to 1.0.0-rc.1) + # to allow for proper validation + ps.STACObjectType.ITEM: + 'https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json' + }, + None), + OldExtensionShortIDs.PROJECTION: ({ + ps.STACObjectType.ITEM: + 'extensions/projection/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAR: ({ + ps.STACObjectType.ITEM: + 'extensions/sar/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAT: ({ + ps.STACObjectType.ITEM: + 'extensions/sat/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SCIENTIFIC: ({ + ps.STACObjectType.ITEM: + 'extensions/scientific/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/scientific/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SINGLE_FILE_STAC: ({ + ps.STACObjectType.CATALOG: + 'extensions/single-file-stac/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TILED_ASSETS: ({ + ps.STACObjectType.CATALOG: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/tiled-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TIMESTAMPS: ({ + ps.STACObjectType.ITEM: + 'extensions/timestamps/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VERSION: ({ + ps.STACObjectType.ITEM: + 'extensions/version/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/version/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VIEW: ({ + ps.STACObjectType.ITEM: + 'extensions/view/json-schema/schema.json' + }, None), + + # Removed or renamed extensions. + 'dtr': (None, None), # Invalid schema + 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: + 'extensions/asset/json-schema/schema.json' + })]), + } + + @classmethod + def _append_base_uri_if_needed(cls, uri: str, stac_version: STACVersionID) -> Optional[str]: + # Only append the base URI if it's not already an absolute URI + if '://' not in uri: + base_uri = None + for version_range, f in cls.get_base_uris(): + if version_range.contains(stac_version): + base_uri = f(stac_version) + return '{}/{}'.format(base_uri, uri) + + # No JSON Schema for the old extension + return None + else: + return uri + + @classmethod + def get_extension_schema_uri(cls, extension_id: str, object_type: "STACObjectType_Type", + stac_version: STACVersionID) -> Optional[str]: + uri = None + + is_latest = stac_version == ps.get_stac_version() + + ext_map = cls.get_schema_map() + if extension_id in ext_map: + if ext_map[extension_id][0] and \ + object_type in ext_map[extension_id][0]: + uri = ext_map[extension_id][0][object_type] + + if not is_latest: + if ext_map[extension_id][1]: + for version_range, ext_uris in ext_map[extension_id][1]: + if version_range.contains(stac_version): + if object_type in ext_uris: + uri = ext_uris[object_type] + break + + if uri is None: + return uri + else: + return cls._append_base_uri_if_needed(uri, stac_version) + + def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: if version < '1.0.0-beta.2': @@ -77,139 +251,6 @@ def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, return None -def _migrate_eo(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - added_extensions: Set[str] = set([]) - if version < '0.5': - if 'eo:crs' in d['properties']: - # Try to pull out the EPSG code. - # Otherwise, just leave it alone. - wkt = d['properties']['eo:crs'] - matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) - if len(matches) > 0: - epsg_code = matches[-1].group(1) - d['properties'].pop('eo:crs') - d['properties']['eo:epsg'] = int(epsg_code) - - if version < '0.6': - # Change eo:bands from a dict to a list. eo:bands on an asset - # is an index instead of a dict key. eo:bands is in properties. - bands_dict = d['eo:bands'] - keys_to_indices: Dict[str, int] = {} - bands: List[Dict[str, Any]] = [] - for i, (k, band) in enumerate(bands_dict.items()): - keys_to_indices[k] = i - bands.append(band) - - d.pop('eo:bands') - d['properties']['eo:bands'] = bands - for k, asset in d['assets'].items(): - if 'eo:bands' in asset: - asset_band_indices: List[int] = [] - for bk in asset['eo:bands']: - asset_band_indices.append(keys_to_indices[bk]) - asset['eo:bands'] = sorted(asset_band_indices) - - if version < '0.9': - # Some eo fields became common_metadata - if 'eo:platform' in d['properties'] and 'platform' not in d['properties']: - d['properties']['platform'] = d['properties']['eo:platform'] - del d['properties']['eo:platform'] - - if 'eo:instrument' in d['properties'] and 'instruments' not in d['properties']: - d['properties']['instruments'] = [d['properties']['eo:instrument']] - del d['properties']['eo:instrument'] - - if 'eo:constellation' in d['properties'] and 'constellation' not in d['properties']: - d['properties']['constellation'] = d['properties']['eo:constellation'] - del d['properties']['eo:constellation'] - - # Some eo fields became view extension fields - eo_to_view_fields = [ - 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' - ] - - view_enabled = 'view' in d['stac_extensions'] - for field in eo_to_view_fields: - if 'eo:{}'.format(field) in d['properties']: - if not view_enabled: - added_extensions.add('view') - view_enabled = True - if not 'view:{}'.format(field) in d['properties']: - d['properties']['view:{}'.format(field)] = \ - d['properties']['eo:{}'.format(field)] - del d['properties']['eo:{}'.format(field)] - - if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: - # gsd moved from eo to common metadata - if 'eo:gsd' in d['properties']: - d['properties']['gsd'] = d['properties']['eo:gsd'] - del d['properties']['eo:gsd'] - - # The way bands were declared in assets changed. - # In 1.0.0-beta.1 they are inlined into assets as - # opposed to having indices back into a property-level array. - if 'eo:bands' in d['properties']: - bands = d['properties']['eo:bands'] - for asset in d['assets'].values(): - if 'eo:bands' in asset: - new_bands: List[Dict[str, Any]] = [] - for band_index in asset['eo:bands']: - new_bands.append(bands[band_index]) - asset['eo:bands'] = new_bands - - return added_extensions - - -def _migrate_label(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': - props = d['properties'] - # Migrate 0.8.0-rc1 non-pluralized forms - # As it's a common mistake, convert for any pre-1.0.0 version. - if 'label:property' in props and 'label:properties' not in props: - props['label:properties'] = props['label:property'] - del props['label:property'] - - if 'label:task' in props and 'label:tasks' not in props: - props['label:tasks'] = props['label:task'] - del props['label:task'] - - if 'label:overview' in props and 'label:overviews' not in props: - props['label:overviews'] = props['label:overview'] - del props['label:overview'] - - if 'label:method' in props and 'label:methods' not in props: - props['label:methods'] = props['label:method'] - del props['label:method'] - - return None - - -def _migrate_pointcloud(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - return None - - -def _migrate_sar(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - if version < '0.9': - # Some sar fields became common_metadata - if 'sar:platform' in d['properties'] and 'platform' not in d['properties']: - d['properties']['platform'] = d['properties']['sar:platform'] - del d['properties']['sar:platform'] - - if 'sar:instrument' in d['properties'] and 'instruments' not in d['properties']: - d['properties']['instruments'] = [d['properties']['sar:instrument']] - del d['properties']['sar:instrument'] - - if 'sar:constellation' in d['properties'] and 'constellation' not in d['properties']: - d['properties']['constellation'] = d['properties']['sar:constellation'] - del d['properties']['sar:constellation'] - - return None - - def _migrate_scientific(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: return None @@ -245,8 +286,7 @@ def _get_extension_renames() -> Dict[str, str]: return {'asset': 'item-assets'} -def migrate_to_latest(json_dict: Dict[str, Any], - info: STACJSONDescription) -> Dict[str, Any]: +def migrate_to_latest(json_dict: Dict[str, Any], info: STACJSONDescription) -> Dict[str, Any]: """Migrates the STAC JSON to the latest version Args: @@ -269,10 +309,21 @@ def migrate_to_latest(json_dict: Dict[str, Any], object_migrations[info.object_type](result, version, info) ps.EXTENSION_HOOKS.migrate(result, version, info) - for ext in (result['stac_extensions'] or []): + for ext in (result.get('stac_extensions') or []): if ext in removed_extension_migrations: removed_extension_migrations[ext](result, version, info) result['stac_extensions'].remove(ext) + else: + # Ensure old ID's are moved to schemas + # May need a better way to differentiate + # old ID's from schemas, but going with + # the file extension for now. + if not ext.lower().endswith('.json'): + result['stac_extensions'].remove(ext) + uri = OldExtensionSchemaUriMap.get_extension_schema_uri( + ext, info.object_type, version) + if uri is not None: + result['stac_extensions'].append(uri) result['stac_version'] = STACVersion.DEFAULT_STAC_VERSION diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index df19a9e63..2f53e4612 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -3,7 +3,6 @@ import pystac as ps from pystac.serialization import STACVersionRange -from pystac.serialization.identify import OldExtensionShortIDs from pystac.stac_object import STACObjectType @@ -14,7 +13,7 @@ def __init__(self) -> None: pass @abstractmethod - def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: + def get_object_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: """Get the schema URI for the given object type and stac version. Args: @@ -26,21 +25,6 @@ def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> """ pass - @abstractmethod - def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, - stac_version: str) -> Optional[str]: - """Get the extension's schema URI for the given object type, stac version. - - Args: - extension_id (str): The Extension ID of the extension of the schema. - object_type (str): STAC object type. One of :class:`~pystac.STACObjectType` - stac_version (str): The STAC version of the schema to return. - - Returns: - str: The URI of the schema, or None if not found. - """ - pass - class DefaultSchemaUriMap(SchemaUriMap): """Implementation of SchemaUriMap that uses schemas hosted by https://schemas.stacspec.org. @@ -68,107 +52,14 @@ class DefaultSchemaUriMap(SchemaUriMap): # is the latest version. If it's a previous version, the stac_version that matches # the listed version range is used, or else the URI from the latest version is used if # there are no overrides for previous versions. - DEFAULT_SCHEMA_MAP: Dict[str, Dict[str, Any]] = { - 'core': { - STACObjectType.CATALOG: ('catalog-spec/json-schema/catalog.json', None), - STACObjectType.COLLECTION: ('collection-spec/json-schema/collection.json', None), - STACObjectType.ITEM: ('item-spec/json-schema/item.json', None), - STACObjectType.ITEMCOLLECTION: (None, [ - STACVersionRange(min_version='v0.8.0-rc1', max_version='0.9.0'), - 'item-spec/json-schema/itemcollection.json' - ]) - }, - 'extension': { - OldExtensionShortIDs.CHECKSUM: ({ - STACObjectType.CATALOG: - 'extensions/checksum/json-schema/schema.json', - STACObjectType.COLLECTION: - 'extensions/checksum/json-schema/schema.json', - STACObjectType.ITEM: - 'extensions/checksum/json-schema/schema.json' - }, None), - OldExtensionShortIDs.COLLECTION_ASSETS: ({ - STACObjectType.COLLECTION: - 'extensions/collection-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.DATACUBE: ({ - STACObjectType.COLLECTION: - 'extensions/datacube/json-schema/schema.json', - STACObjectType.ITEM: - 'extensions/datacube/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { - STACObjectType.COLLECTION: None, - STACObjectType.ITEM: None - })]), - OldExtensionShortIDs.EO: ({ - STACObjectType.ITEM: 'extensions/eo/json-schema/schema.json' - }, None), - OldExtensionShortIDs.ITEM_ASSETS: ({ - STACObjectType.COLLECTION: - 'extensions/item-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.LABEL: ({ - STACObjectType.ITEM: - 'extensions/label/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { - STACObjectType.ITEM: 'extensions/label/schema.json' - })]), - OldExtensionShortIDs.POINTCLOUD: ( - { - STACObjectType.ITEM: None # 'extensions/pointcloud/json-schema/schema.json' - }, - None), - OldExtensionShortIDs.PROJECTION: ({ - STACObjectType.ITEM: - 'extensions/projection/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAR: ({ - STACObjectType.ITEM: - 'extensions/sar/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAT: ({ - STACObjectType.ITEM: - 'extensions/sat/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SCIENTIFIC: ({ - STACObjectType.ITEM: - 'extensions/scientific/json-schema/schema.json', - STACObjectType.COLLECTION: - 'extensions/scientific/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SINGLE_FILE_STAC: ({ - STACObjectType.CATALOG: - 'extensions/single-file-stac/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TILED_ASSETS: ({ - STACObjectType.CATALOG: - 'extensions/tiled-assets/json-schema/schema.json', - STACObjectType.COLLECTION: - 'extensions/tiled-assets/json-schema/schema.json', - STACObjectType.ITEM: - 'extensions/tiled-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TIMESTAMPS: ({ - STACObjectType.ITEM: - 'extensions/timestamps/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VERSION: ({ - STACObjectType.ITEM: - 'extensions/version/json-schema/schema.json', - STACObjectType.COLLECTION: - 'extensions/version/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VIEW: ({ - STACObjectType.ITEM: - 'extensions/view/json-schema/schema.json' - }, None), - - # Removed or renamed extensions. - 'dtr': (None, None), # Invalid schema - 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { - STACObjectType.COLLECTION: 'extensions/asset/json-schema/schema.json' - })]), - } + DEFAULT_SCHEMA_MAP: Dict[str, Any] = { + STACObjectType.CATALOG: ('catalog-spec/json-schema/catalog.json', None), + STACObjectType.COLLECTION: ('collection-spec/json-schema/collection.json', None), + STACObjectType.ITEM: ('item-spec/json-schema/item.json', None), + STACObjectType.ITEMCOLLECTION: (None, [ + STACVersionRange(min_version='v0.8.0-rc1', max_version='0.9.0'), + 'item-spec/json-schema/itemcollection.json' + ]) } @classmethod @@ -186,44 +77,19 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str else: return uri - def get_core_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: + def get_object_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: uri = None is_latest = stac_version == ps.get_stac_version() - if object_type not in self.DEFAULT_SCHEMA_MAP['core']: + if object_type not in self.DEFAULT_SCHEMA_MAP: raise KeyError('Unknown STAC object type {}'.format(object_type)) - uri = self.DEFAULT_SCHEMA_MAP['core'][object_type][0] + uri = self.DEFAULT_SCHEMA_MAP[object_type][0] if not is_latest: - if self.DEFAULT_SCHEMA_MAP['core'][object_type][1]: - for version_range, range_uri in self.DEFAULT_SCHEMA_MAP['core'][object_type][1]: + if self.DEFAULT_SCHEMA_MAP[object_type][1]: + for version_range, range_uri in self.DEFAULT_SCHEMA_MAP[object_type][1]: if version_range.contains(stac_version): uri = range_uri break return self._append_base_uri_if_needed(uri, stac_version) - - def get_extension_schema_uri(self, extension_id: str, object_type: STACObjectType, - stac_version: str) -> Optional[str]: - uri = None - - is_latest = stac_version == ps.get_stac_version() - - ext_map = self.DEFAULT_SCHEMA_MAP['extension'] - if extension_id in ext_map: - if ext_map[extension_id][0] and \ - object_type in ext_map[extension_id][0]: - uri = ext_map[extension_id][0][object_type] - - if not is_latest: - if ext_map[extension_id][1]: - for version_range, ext_uris in ext_map[extension_id][1]: - if version_range.contains(stac_version): - if object_type in ext_uris: - uri = ext_uris[object_type] - break - - if uri is None: - return uri - else: - return self._append_base_uri_if_needed(uri, stac_version) diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index f7ce27794..2dc68bfe8 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -183,7 +183,7 @@ def validate_core(self, str: URI for the JSON schema that was validated against, or None if no validation occurred. """ - schema_uri = self.schema_uri_map.get_core_schema_uri(stac_object_type, stac_version) + schema_uri = self.schema_uri_map.get_object_schema_uri(stac_object_type, stac_version) if schema_uri is None: return None @@ -218,8 +218,7 @@ def validate_extension(self, str: URI for the JSON schema that was validated against, or None if no validation occurred. """ - schema_uri = self.schema_uri_map.get_extension_schema_uri(extension_id, stac_object_type, - stac_version) + schema_uri = extension_id if schema_uri is None: return None diff --git a/pystac/version.py b/pystac/version.py index 7758fda73..8898abd20 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -6,7 +6,7 @@ class STACVersion: - DEFAULT_STAC_VERSION = '1.0.0-beta.2' + DEFAULT_STAC_VERSION = '1.0.0-rc.2' """Latest STAC version supported by PySTAC""" # Version that holds a user-set STAC version to use. diff --git a/tests/data-files/eo/eo-collection.json b/tests/data-files/eo/eo-collection.json new file mode 100644 index 000000000..181ecc4b4 --- /dev/null +++ b/tests/data-files/eo/eo-collection.json @@ -0,0 +1,154 @@ +{ + "stac_version": "1.0.0-rc.2", + "stac_extensions": [], + "id": "landsat-8-l1", + "title": "Landsat 8 L1", + "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", + "keywords": [ + "landsat" + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2013-06-01T00:00:00Z", + null + ] + ] + } + }, + "providers": [ + { + "name": "USGS", + "url": "https://landsat.usgs.gov/", + "roles": [ + "producer", + "licensor" + ] + }, + { + "name": "Planet Labs", + "url": "https://github.com/landsat-pds/landsat_ingestor", + "roles": [ + "processor" + ] + }, + { + "name": "AWS", + "url": "https://landsatonaws.com/", + "roles": [ + "host" + ] + }, + { + "name": "Development Seed", + "url": "https://developmentseed.org/", + "roles": [ + "processor" + ] + } + ], + "license": "PDDL-1.0", + "summaries": { + "eo:cloud_cover": { + "minimum": 0.0, + "maximum": 80.0 + }, + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 0.48, + "full_width_half_max": 0.06 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.06 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 0.65, + "full_width_half_max": 0.04 + }, + { + "name": "B5", + "common_name": "nir", + "center_wavelength": 0.86, + "full_width_half_max": 0.03 + }, + { + "name": "B6", + "common_name": "swir16", + "center_wavelength": 1.6, + "full_width_half_max": 0.08 + }, + { + "name": "B7", + "common_name": "swir22", + "center_wavelength": 2.2, + "full_width_half_max": 0.2 + }, + { + "name": "B8", + "common_name": "pan", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + }, + { + "name": "B9", + "common_name": "cirrus", + "center_wavelength": 1.37, + "full_width_half_max": 0.02 + }, + { + "name": "B10", + "common_name": "lwir11", + "center_wavelength": 10.9, + "full_width_half_max": 0.8 + }, + { + "name": "B11", + "common_name": "lwir12", + "center_wavelength": 12, + "full_width_half_max": 1 + } + ] + }, + "links": [ + { + "rel": "self", + "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" + }, + { + "rel": "parent", + "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" + }, + { + "rel": "root", + "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" + }, + { + "rel": "child", + "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/paths/catalog.json" + } + ] +} \ No newline at end of file diff --git a/tests/data-files/eo/eo-landsat-example.json b/tests/data-files/eo/eo-landsat-example.json index 75b110721..06fdbccb5 100644 --- a/tests/data-files/eo/eo-landsat-example.json +++ b/tests/data-files/eo/eo-landsat-example.json @@ -1,9 +1,8 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "eo", - "view", - "https://example.com/stac/landsat-extension/1.0/schema.json" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "id": "LC08_L1TP_107018_20181001_20181001_01_RT", "collection": "landsat-8-l1", diff --git a/tests/data-files/eo/sample-bands-in-item-properties.json b/tests/data-files/eo/sample-bands-in-item-properties.json index 8781005bb..c3917e4c2 100644 --- a/tests/data-files/eo/sample-bands-in-item-properties.json +++ b/tests/data-files/eo/sample-bands-in-item-properties.json @@ -1,9 +1,8 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "eo", - "view", - "https://example.com/cs-extension/1.0/schema.json" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "type": "Feature", "id" : "CS3-20160503_132131_05", @@ -73,7 +72,21 @@ "assets": { "analytic": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", - "title": "4-Band Analytic" + "title": "4-Band Analytic", + "eo:bands": [ + { + "name": "band1" + }, + { + "name": "band2" + }, + { + "name": "band3" + }, + { + "name": "band4" + } + ] }, "thumbnail": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", diff --git a/tests/data-files/file/file-example.json b/tests/data-files/file/file-example.json index 4533a4abc..4b107ac41 100644 --- a/tests/data-files/file/file-example.json +++ b/tests/data-files/file/file-example.json @@ -1,9 +1,9 @@ { "id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616", "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "file" + "https://stac-extensions.github.io/file/v1.0.0/schema.json" ], "bbox": [ -70.275032, diff --git a/tests/data-files/pointcloud/example-laz-no-statistics.json b/tests/data-files/pointcloud/example-laz-no-statistics.json index 102c94c07..989a78a15 100644 --- a/tests/data-files/pointcloud/example-laz-no-statistics.json +++ b/tests/data-files/pointcloud/example-laz-no-statistics.json @@ -1,7 +1,7 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "pointcloud" + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" ], "assets": {}, "bbox": [ diff --git a/tests/data-files/pointcloud/example-laz.json b/tests/data-files/pointcloud/example-laz.json index a9bfbf87e..f9a5cdf38 100644 --- a/tests/data-files/pointcloud/example-laz.json +++ b/tests/data-files/pointcloud/example-laz.json @@ -1,7 +1,7 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "pointcloud" + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" ], "assets": {}, "bbox": [ diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index 6a34680ce..3033dd5b7 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -1,16 +1,18 @@ import json +from pystac.collection import RangeSummary import unittest import pystac as ps from pystac import Item from pystac.utils import get_opt -from pystac.extensions.eo import eo_ext, Band +from pystac.extensions.eo import eo_ext, Band, eo_summaries from tests.utils import (TestCases, test_to_from_dict) class EOTest(unittest.TestCase): LANDSAT_EXAMPLE_URI = TestCases.get_path('data-files/eo/eo-landsat-example.json') BANDS_IN_ITEM_URI = TestCases.get_path('data-files/eo/sample-bands-in-item-properties.json') + EO_COLLECTION_URI = TestCases.get_path('data-files/eo/eo-collection.json') def setUp(self): self.maxDiff = None @@ -115,6 +117,28 @@ def test_cloud_cover(self): item.validate() + def test_summaries(self): + col = ps.Collection.from_file(self.EO_COLLECTION_URI) + + # Get + + cloud_cover_summaries = eo_summaries(col).cloud_cover + self.assertEqual(cloud_cover_summaries.minimum, 0.0) + self.assertEqual(cloud_cover_summaries.maximum, 80.0) + + bands = eo_summaries(col).bands + assert bands is not None + self.assertEqual(len(bands), 11) + + # Set + + eo_summaries(col).cloud_cover = RangeSummary(1.0, 2.0) + eo_summaries(col).bands = [Band.create(name='test')] + + col_dict = col.to_dict() + self.assertEqual(len(col_dict['summaries']['eo:bands']), 1) + self.assertEqual(col_dict['summaries']['eo:cloud_cover']['minimum'], 1.0) + def test_read_pre_09_fields_into_common_metadata(self): eo_item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.8.1/item-spec/examples/' diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index be033f40e..5e835974f 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -1,11 +1,11 @@ import json +from typing import Any, Dict import unittest # from copy import deepcopy -import pystac -from pystac import (Item, _OldExtensionShortIDs) -from pystac.extensions import ExtensionError -from pystac.extensions.pointcloud import PointcloudSchema, PointcloudStatistic +import pystac as ps +from pystac.extensions import pointcloud_ext +from pystac.extensions.pointcloud import PointcloudExtension, PointcloudSchema, PointcloudStatistic from tests.utils import (TestCases, test_to_from_dict) @@ -19,35 +19,36 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, Item, d) + test_to_from_dict(self, ps.Item, d) def test_apply(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.pointcloud - - item.ext.enable(_OldExtensionShortIDs.POINTCLOUD) - item.ext.pointcloud.apply(1000, 'lidar', 'laszip', - [PointcloudSchema({ - 'name': 'X', - 'size': 8, - 'type': 'floating' - })]) + item = next(iter(TestCases.test_case_2().get_all_items())) + + self.assertFalse(PointcloudExtension.has_extension(item)) + + PointcloudExtension.add_to(item) + pointcloud_ext(item).apply(1000, 'lidar', 'laszip', + [PointcloudSchema({ + 'name': 'X', + 'size': 8, + 'type': 'floating' + })]) + self.assertTrue(PointcloudExtension.has_extension(item)) def test_validate_pointcloud(self): - item = pystac.read_file(self.example_uri) + item = ps.read_file(self.example_uri) item.validate() def test_count(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:count", pc_item.properties) - pc_count = pc_item.ext.pointcloud.count + pc_count = pointcloud_ext(pc_item).count self.assertEqual(pc_count, pc_item.properties['pc:count']) # Set - pc_item.ext.pointcloud.count = pc_count + 100 + pointcloud_ext(pc_item).count = pc_count + 100 self.assertEqual(pc_count + 100, pc_item.properties['pc:count']) # Validate @@ -56,62 +57,62 @@ def test_count(self): # Cannot test validation errors until the pointcloud schema.json syntax is fixed # Ensure setting bad count fails validation - # with self.assertRaises(STACValidationError): - # pc_item.ext.pointcloud.count = 'not_an_int' - # pc_item.validate() + with self.assertRaises(ps.STACValidationError): + pointcloud_ext(pc_item).count = 'not_an_int' # type:ignore + pc_item.validate() def test_type(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:type", pc_item.properties) - pc_type = pc_item.ext.pointcloud.type + pc_type = pointcloud_ext(pc_item).type self.assertEqual(pc_type, pc_item.properties['pc:type']) # Set - pc_item.ext.pointcloud.type = 'sonar' + pointcloud_ext(pc_item).type = 'sonar' self.assertEqual('sonar', pc_item.properties['pc:type']) # Validate pc_item.validate def test_encoding(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:encoding", pc_item.properties) - pc_encoding = pc_item.ext.pointcloud.encoding + pc_encoding = pointcloud_ext(pc_item).encoding self.assertEqual(pc_encoding, pc_item.properties['pc:encoding']) # Set - pc_item.ext.pointcloud.encoding = 'binary' + pointcloud_ext(pc_item).encoding = 'binary' self.assertEqual('binary', pc_item.properties['pc:encoding']) # Validate pc_item.validate def test_schemas(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:schemas", pc_item.properties) - pc_schemas = [s.to_dict() for s in pc_item.ext.pointcloud.schemas] + pc_schemas = [s.to_dict() for s in pointcloud_ext(pc_item).schemas] self.assertEqual(pc_schemas, pc_item.properties['pc:schemas']) # Set schema = [PointcloudSchema({'name': 'X', 'size': 8, 'type': 'floating'})] - pc_item.ext.pointcloud.schemas = schema + pointcloud_ext(pc_item).schemas = schema self.assertEqual([s.to_dict() for s in schema], pc_item.properties['pc:schemas']) # Validate pc_item.validate def test_statistics(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:statistics", pc_item.properties) - pc_statistics = [s.to_dict() for s in pc_item.ext.pointcloud.statistics] + pc_statistics = [s.to_dict() for s in pointcloud_ext(pc_item).statistics] self.assertEqual(pc_statistics, pc_item.properties['pc:statistics']) # Set @@ -127,27 +128,27 @@ def test_statistics(self): "variance": 1 }) ] - pc_item.ext.pointcloud.statistics = stats + pointcloud_ext(pc_item).statistics = stats self.assertEqual([s.to_dict() for s in stats], pc_item.properties['pc:statistics']) # Validate pc_item.validate def test_density(self): - pc_item = pystac.read_file(self.example_uri) + pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:density", pc_item.properties) - pc_density = pc_item.ext.pointcloud.density + pc_density = pointcloud_ext(pc_item).density self.assertEqual(pc_density, pc_item.properties['pc:density']) # Set density = 100 - pc_item.ext.pointcloud.density = density + pointcloud_ext(pc_item).density = density self.assertEqual(density, pc_item.properties['pc:density']) # Validate pc_item.validate def test_pointcloud_schema(self): - props = { + props: Dict[str, Any] = { "name": "test", "size": 8, "type": "floating", @@ -165,7 +166,7 @@ def test_pointcloud_schema(self): self.assertEqual(getattr(schema, k), val) def test_pointcloud_statistics(self): - props = { + props: Dict[str, Any] = { "average": 1, "count": 1, "maximum": 1, @@ -188,5 +189,5 @@ def test_pointcloud_statistics(self): self.assertEqual(getattr(stat, k), val) def test_statistics_accessor_when_no_stats(self): - pc_item = pystac.read_file(self.example_uri_no_statistics) - self.assertEqual(pc_item.ext.pointcloud.statistics, None) + pc_item = ps.Item.from_file(self.example_uri_no_statistics) + self.assertEqual(pointcloud_ext(pc_item).statistics, None) From e96834985aa7e9bff67ec63ce4a96b6fb0e39437 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 27 Apr 2021 00:12:33 -0400 Subject: [PATCH 09/51] Rename examples with consistent version tag. This way we can avoid migrating them --- .../acc/665946-labels/665946-labels.json | 0 .../acc/665946/665946.json | 0 .../acc/a42435-labels/a42435-labels.json | 0 .../acc/a42435/a42435.json | 0 .../acc/ca041a-labels/ca041a-labels.json | 0 .../acc/ca041a/ca041a.json | 0 .../acc/collection.json | 0 .../acc/d41d81-labels/d41d81-labels.json | 0 .../acc/d41d81/d41d81.json | 0 .../{label_catalog_0_8_1 => label_catalog-v0.8.1}/catalog.json | 0 .../dar/0a4c40-labels/0a4c40-labels.json | 0 .../dar/0a4c40/0a4c40.json | 0 .../dar/353093-labels/353093-labels.json | 0 .../dar/353093/353093.json | 0 .../dar/42f235-labels/42f235-labels.json | 0 .../dar/42f235/42f235.json | 0 .../dar/a017f9-labels/a017f9-labels.json | 0 .../dar/a017f9/a017f9.json | 0 .../dar/b15fce-labels/b15fce-labels.json | 0 .../dar/b15fce/b15fce.json | 0 .../dar/collection.json | 0 .../dar/f883a0-labels/f883a0-labels.json | 0 .../dar/f883a0/f883a0.json | 0 .../kam/4e7c7f-labels/4e7c7f-labels.json | 0 .../kam/4e7c7f/4e7c7f.json | 0 .../kam/collection.json | 0 .../mon/207cc7-labels/207cc7-labels.json | 0 .../mon/207cc7/207cc7.json | 0 .../mon/401175-labels/401175-labels.json | 0 .../mon/401175/401175.json | 0 .../mon/493701-labels/493701-labels.json | 0 .../mon/493701/493701.json | 0 .../mon/collection.json | 0 .../mon/f15272-labels/f15272-labels.json | 0 .../mon/f15272/f15272.json | 0 .../nia/825a50-labels/825a50-labels.json | 0 .../nia/825a50/825a50.json | 0 .../nia/collection.json | 0 .../ptn/abe1a3-labels/abe1a3-labels.json | 0 .../ptn/abe1a3/abe1a3.json | 0 .../ptn/collection.json | 0 .../ptn/f49f31-labels/f49f31-labels.json | 0 .../ptn/f49f31/f49f31.json | 0 .../znz/06f252-labels/06f252-labels.json | 0 .../znz/06f252/06f252.json | 0 .../znz/076995-labels/076995-labels.json | 0 .../znz/076995/076995.json | 0 .../znz/33cae6-labels/33cae6-labels.json | 0 .../znz/33cae6/33cae6.json | 0 .../znz/3b20d4-labels/3b20d4-labels.json | 0 .../znz/3b20d4/3b20d4.json | 0 .../znz/3f8360-labels/3f8360-labels.json | 0 .../znz/3f8360/3f8360.json | 0 .../znz/425403-labels/425403-labels.json | 0 .../znz/425403/425403.json | 0 .../znz/75cdfa-labels/75cdfa-labels.json | 0 .../znz/75cdfa/75cdfa.json | 0 .../znz/9b8638-labels/9b8638-labels.json | 0 .../znz/9b8638/9b8638.json | 0 .../znz/aee7fd-labels/aee7fd-labels.json | 0 .../znz/aee7fd/aee7fd.json | 0 .../znz/bc32f1-labels/bc32f1-labels.json | 0 .../znz/bc32f1/bc32f1.json | 0 .../znz/bd5c14-labels/bd5c14-labels.json | 0 .../znz/bd5c14/bd5c14.json | 0 .../znz/c7415c-labels/c7415c-labels.json | 0 .../znz/c7415c/c7415c.json | 0 .../znz/collection.json | 0 .../znz/e52478-labels/e52478-labels.json | 0 .../znz/e52478/e52478.json | 0 .../collection.json | 0 .../hurricane-harvey/catalog.json | 0 .../20170831_162740_ssc1d1/20170831_162740_ssc1d1.json | 0 .../20170831_172754_101c/20170831_172754_101c.json | 0 .../20170831_195425_SS02/20170831_195425_SS02.json | 0 .../2017831_195552_SS02/2017831_195552_SS02.json | 0 .../Houston-East-20170831-103f-100d-0f4f-RGB.json | 0 .../hurricane-harvey/hurricane-harvey-0831/catalog.json | 0 78 files changed, 0 insertions(+), 0 deletions(-) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/665946-labels/665946-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/665946/665946.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/a42435-labels/a42435-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/a42435/a42435.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/ca041a-labels/ca041a-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/ca041a/ca041a.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/d41d81-labels/d41d81-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/acc/d41d81/d41d81.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/catalog.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/0a4c40-labels/0a4c40-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/0a4c40/0a4c40.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/353093-labels/353093-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/353093/353093.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/42f235-labels/42f235-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/42f235/42f235.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/a017f9-labels/a017f9-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/a017f9/a017f9.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/b15fce-labels/b15fce-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/b15fce/b15fce.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/f883a0-labels/f883a0-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/dar/f883a0/f883a0.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/kam/4e7c7f-labels/4e7c7f-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/kam/4e7c7f/4e7c7f.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/kam/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/207cc7-labels/207cc7-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/207cc7/207cc7.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/401175-labels/401175-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/401175/401175.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/493701-labels/493701-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/493701/493701.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/f15272-labels/f15272-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/mon/f15272/f15272.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/nia/825a50-labels/825a50-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/nia/825a50/825a50.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/nia/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/ptn/abe1a3-labels/abe1a3-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/ptn/abe1a3/abe1a3.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/ptn/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/ptn/f49f31-labels/f49f31-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/ptn/f49f31/f49f31.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/06f252-labels/06f252-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/06f252/06f252.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/076995-labels/076995-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/076995/076995.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/33cae6-labels/33cae6-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/33cae6/33cae6.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/3b20d4-labels/3b20d4-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/3b20d4/3b20d4.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/3f8360-labels/3f8360-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/3f8360/3f8360.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/425403-labels/425403-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/425403/425403.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/75cdfa-labels/75cdfa-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/75cdfa/75cdfa.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/9b8638-labels/9b8638-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/9b8638/9b8638.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/aee7fd-labels/aee7fd-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/aee7fd/aee7fd.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/bc32f1-labels/bc32f1-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/bc32f1/bc32f1.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/bd5c14-labels/bd5c14-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/bd5c14/bd5c14.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/c7415c-labels/c7415c-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/c7415c/c7415c.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/collection.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/e52478-labels/e52478-labels.json (100%) rename tests/data-files/catalogs/{label_catalog_0_8_1 => label_catalog-v0.8.1}/znz/e52478/e52478.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/collection.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/catalog.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json (100%) rename tests/data-files/catalogs/{planet-example-1.0.0-beta.2 => planet-example-v1.0.0-beta.2}/hurricane-harvey/hurricane-harvey-0831/catalog.json (100%) diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/665946-labels/665946-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/665946-labels/665946-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/665946/665946.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/665946/665946.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/a42435-labels/a42435-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/a42435-labels/a42435-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/a42435/a42435.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/a42435/a42435.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/ca041a-labels/ca041a-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/ca041a-labels/ca041a-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/ca041a/ca041a.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/ca041a/ca041a.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/d41d81-labels/d41d81-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/d41d81-labels/d41d81-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/acc/d41d81/d41d81.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/acc/d41d81/d41d81.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/catalog.json b/tests/data-files/catalogs/label_catalog-v0.8.1/catalog.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/catalog.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/catalog.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/0a4c40-labels/0a4c40-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/0a4c40-labels/0a4c40-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/0a4c40/0a4c40.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/0a4c40/0a4c40.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/353093-labels/353093-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/353093-labels/353093-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/353093/353093.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/353093/353093.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/42f235-labels/42f235-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/42f235-labels/42f235-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/42f235/42f235.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/42f235/42f235.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/a017f9-labels/a017f9-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/a017f9-labels/a017f9-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/a017f9/a017f9.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/a017f9/a017f9.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/b15fce-labels/b15fce-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/b15fce-labels/b15fce-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/b15fce/b15fce.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/b15fce/b15fce.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/f883a0-labels/f883a0-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/f883a0-labels/f883a0-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/dar/f883a0/f883a0.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/dar/f883a0/f883a0.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/kam/4e7c7f-labels/4e7c7f-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/kam/4e7c7f-labels/4e7c7f-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/kam/4e7c7f/4e7c7f.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/kam/4e7c7f/4e7c7f.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/kam/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/kam/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/207cc7-labels/207cc7-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/207cc7-labels/207cc7-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/207cc7/207cc7.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/207cc7/207cc7.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/401175-labels/401175-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/401175-labels/401175-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/401175/401175.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/401175/401175.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/493701-labels/493701-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/493701-labels/493701-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/493701/493701.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/493701/493701.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/f15272-labels/f15272-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/f15272-labels/f15272-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/mon/f15272/f15272.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/mon/f15272/f15272.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/nia/825a50-labels/825a50-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/nia/825a50-labels/825a50-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/nia/825a50/825a50.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/nia/825a50/825a50.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/nia/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/nia/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/ptn/abe1a3-labels/abe1a3-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/ptn/abe1a3-labels/abe1a3-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/ptn/abe1a3/abe1a3.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/ptn/abe1a3/abe1a3.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/ptn/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/ptn/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/ptn/f49f31-labels/f49f31-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/ptn/f49f31-labels/f49f31-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/ptn/f49f31/f49f31.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/ptn/f49f31/f49f31.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/06f252-labels/06f252-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/06f252-labels/06f252-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/06f252/06f252.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/06f252/06f252.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/076995-labels/076995-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/076995-labels/076995-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/076995/076995.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/076995/076995.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/33cae6-labels/33cae6-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/33cae6-labels/33cae6-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/33cae6/33cae6.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/33cae6/33cae6.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/3b20d4-labels/3b20d4-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/3b20d4-labels/3b20d4-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/3b20d4/3b20d4.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/3b20d4/3b20d4.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/3f8360-labels/3f8360-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/3f8360-labels/3f8360-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/3f8360/3f8360.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/3f8360/3f8360.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/425403-labels/425403-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/425403-labels/425403-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/425403/425403.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/425403/425403.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/75cdfa-labels/75cdfa-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/75cdfa-labels/75cdfa-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/75cdfa/75cdfa.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/75cdfa/75cdfa.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/9b8638-labels/9b8638-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/9b8638-labels/9b8638-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/9b8638/9b8638.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/9b8638/9b8638.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/aee7fd-labels/aee7fd-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/aee7fd-labels/aee7fd-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/aee7fd/aee7fd.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/aee7fd/aee7fd.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/bc32f1-labels/bc32f1-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/bc32f1-labels/bc32f1-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/bc32f1/bc32f1.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/bc32f1/bc32f1.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/bd5c14-labels/bd5c14-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/bd5c14-labels/bd5c14-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/bd5c14/bd5c14.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/bd5c14/bd5c14.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/c7415c-labels/c7415c-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/c7415c-labels/c7415c-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/c7415c/c7415c.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/c7415c/c7415c.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/collection.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/e52478-labels/e52478-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/e52478-labels/e52478-labels.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json diff --git a/tests/data-files/catalogs/label_catalog_0_8_1/znz/e52478/e52478.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json similarity index 100% rename from tests/data-files/catalogs/label_catalog_0_8_1/znz/e52478/e52478.json rename to tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/collection.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/collection.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/catalog.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/catalog.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json diff --git a/tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json similarity index 100% rename from tests/data-files/catalogs/planet-example-1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json rename to tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json From 8b873ad6f99116fedf96c44bf20c9cfae0d4a0d4 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 27 Apr 2021 00:14:37 -0400 Subject: [PATCH 10/51] Modify change_stac_version to perform migration --- tests/data-files/change_stac_version.py | 71 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index 28ae59997..e379159ac 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -3,34 +3,59 @@ This is used when upgrading to a new version of STAC. """ import os +import re import argparse import json +import pystac as ps + +TARGET_VERSION = ps.get_stac_version() + +def migrate(path: str) -> None: + try: + with open(path) as f: + stac_json = json.load(f) + except json.decoder.JSONDecodeError: + print(f'Cannot read {path}') + raise + + if 'stac_version' in stac_json: + cur_ver = stac_json['stac_version'] + if not cur_ver == TARGET_VERSION: + print(' - Migrating {} from {} to {}...'.format( + path, cur_ver, TARGET_VERSION)) + obj = ps.read_dict(stac_json) + migrated = obj.to_dict() + with open(path, 'w') as f: + json.dump(migrated, f, indent=2) + + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('stac_version', - metavar='STAC_VERSION', - help='The STAC_VERSION to ensure all STAC objects are using.') + parser.add_argument('--file', + metavar='FILE', + help='Only migrate this specific file.') args = parser.parse_args() - data_files_dir = os.path.dirname(os.path.realpath(__file__)) - - # Skip examples directory, which contains version specific STACs... - dirs_to_check = [x for x in os.listdir(data_files_dir) if x != 'examples'] - - for d in dirs_to_check: - for root, _, files in os.walk(d): - for fname in files: - if fname.endswith('.json'): - path = os.path.join(root, fname) - with open(path) as f: - stac_json = json.load(f) - if 'stac_version' in stac_json: - cur_ver = stac_json['stac_version'] - if not cur_ver == args.stac_version: - print('Changing {} version from {} to {}...'.format( - fname, cur_ver, args.stac_version)) - stac_json['stac_version'] = args.stac_version - with open(path, 'w') as f: - json.dump(stac_json, f, indent=2) + if args.file: + migrate(os.path.abspath(args.file)) + else: + + data_files_dir = os.path.dirname(os.path.realpath(__file__)) + + # Skip examples directory, which contains version specific STACs... + dirs_to_check = [ + os.path.join(data_files_dir, x) for x in os.listdir(data_files_dir) if x != 'examples' + ] + + for d in dirs_to_check: + print(f'checking in {d}..') + for root, _, files in os.walk(d): + # Skip directories with a version tag + if re.match(r".*-v\d.*", root): + continue + for fname in files: + if fname.endswith('.json'): + path = os.path.join(root, fname) + migrate(path) \ No newline at end of file From 1b9350087493d0922054614b974eb39e0615460b Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 27 Apr 2021 00:30:14 -0400 Subject: [PATCH 11/51] Set schema_uri during eo migration --- pystac/extensions/eo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 5e84ed7cd..6ba37e4f4 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -102,6 +102,10 @@ def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescr new_bands.append(bands[band_index]) asset['eo:bands'] = new_bands + # Update stac_extension entry + if 'stac_extensions' in d and 'eo' in d['stac_extensions']: + d['stac_extensions'].remove('eo') + d['stac_extensions'].append(SCHEMA_URI) class Band: """Represents Band information attached to an Item that implements the eo extension. From 421788f9c33b547fd84329564d6393d780c16f88 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 27 Apr 2021 23:41:42 -0400 Subject: [PATCH 12/51] More extension refactors --- pystac/__init__.py | 7 +- pystac/extensions/__init__.py | 5 - pystac/extensions/eo.py | 1 + pystac/extensions/pointcloud.py | 4 +- pystac/extensions/projection.py | 266 ++++-------------- pystac/extensions/sar.py | 223 ++++++++------- pystac/extensions/sat.py | 95 ++++--- pystac/extensions/scientific.py | 225 ++++----------- .../cbers-partial/CBERS4AWFI/collection.json | 218 +++++++------- .../projection/example-landsat8.json | 7 +- tests/extensions/test_file.py | 3 +- tests/extensions/test_label.py | 2 +- tests/extensions/test_pointcloud.py | 2 +- tests/extensions/test_projection.py | 194 ++++++------- tests/extensions/test_sar.py | 52 ++-- tests/extensions/test_sat.py | 93 +++--- tests/extensions/test_scientific.py | 137 +++++---- 17 files changed, 638 insertions(+), 896 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index 4a9a7a8a3..43dde5ffe 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -29,8 +29,6 @@ import pystac.extensions.hooks import pystac.extensions.eo import pystac.extensions.label -import pystac.extensions.pointcloud -import pystac.extensions.projection import pystac.extensions.sar import pystac.extensions.sat import pystac.extensions.scientific @@ -41,7 +39,10 @@ import pystac.extensions.file EXTENSION_HOOKS: pystac.extensions.hooks.RegisteredExtensionHooks = pystac.extensions.hooks.RegisteredExtensionHooks( - [pystac.extensions.eo.EO_EXTENSION_HOOKS, pystac.extensions.label.LABEL_EXTENSION_HOOKS]) + [ + pystac.extensions.eo.EO_EXTENSION_HOOKS, pystac.extensions.label.LABEL_EXTENSION_HOOKS, + pystac.extensions.sar.SAR_EXTENSION_HOOKS + ]) def read_file(href: str) -> STACObject: diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index 2c1f8b2eb..46e827d5d 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -5,8 +5,3 @@ class ExtensionError(Exception): """An error related to the construction of extensions. """ pass - -from pystac.extensions.eo import eo_ext # type:ignore -from pystac.extensions.file import file_ext # type:ignore -from pystac.extensions.label import label_ext # type:ignore -from pystac.extensions.pointcloud import pointcloud_ext # type:ignore diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 6ba37e4f4..066914888 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -107,6 +107,7 @@ def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescr d['stac_extensions'].remove('eo') d['stac_extensions'].append(SCHEMA_URI) + class Band: """Represents Band information attached to an Item that implements the eo extension. diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 4a0867d03..f0c131481 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -492,7 +492,7 @@ def __repr__(self) -> str: return ''.format(self.item.id) -class AssetFileExtension(PointcloudExtension[ps.Asset]): +class AssetPointcloudExtension(PointcloudExtension[ps.Asset]): def __init__(self, asset: ps.Asset): self.asset_href = asset.href self.properties = asset.properties @@ -506,6 +506,6 @@ def pointcloud_ext(obj: T) -> PointcloudExtension[T]: if isinstance(obj, ps.Item): return ItemPointcloudExtension(obj) elif isinstance(obj, ps.Asset): - return AssetFileExtension(obj) + return AssetPointcloudExtension(obj) else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 2059841dd..40984ffda 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,9 +1,23 @@ -from typing import Any, Dict, List, Optional +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from typing import Any, Dict, Generic, List, Optional, TypeVar -from pystac.item import Asset, Item +import pystac as ps +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) -class ProjectionItemExt(): +SCHEMA_URI = "https://stac-extensions.github.io/projection/v1.0.0/schema.json" + +EPSG_PROP = 'proj:epsg' +WKT2_PROP = 'proj:wkt2' +PROJJSON_PROP = 'proj:projjson' +GEOM_PROP = 'proj:geometry' +BBOX_PROP = 'proj:bbox' +CENTROID_PROP = 'proj:centroid' +SHAPE_PROP = 'proj:shape' +TRANSFORM_PROP = 'proj:transform' + + +class ProjectionExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """ProjectionItemExt is the extension of an Item in the Projection Extension. The Projection extension adds projection information to STAC Items. @@ -17,7 +31,7 @@ class ProjectionItemExt(): Using ProjectionItemExt to directly wrap an item will add the 'proj' extension ID to the item's stac_extensions. """ - def __init__(self, item: Item) -> None: + def __init__(self, item: ps.Item) -> None: self.item = item def apply(self, @@ -72,33 +86,11 @@ def epsg(self) -> Optional[int]: Returns: int """ - return self.get_epsg() + return self._get_property(EPSG_PROP, int) @epsg.setter def epsg(self, v: Optional[int]) -> None: - self.set_epsg(v) - - def get_epsg(self, asset: Optional[Asset] = None) -> Optional[int]: - """Gets an Item or an Asset epsg. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - int - """ - if asset is None or 'proj:epsg' not in asset.properties: - return self.item.properties.get('proj:epsg') - else: - return asset.properties.get('proj:epsg') - - def set_epsg(self, epsg: Optional[int], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset epsg. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:epsg', epsg, asset, pop_if_none=False) + self._set_property(EPSG_PROP, v, pop_if_none=False) @property def wkt2(self) -> Optional[str]: @@ -113,33 +105,11 @@ def wkt2(self) -> Optional[str]: Returns: str """ - return self.get_wkt2() + return self._get_property(WKT2_PROP, str) @wkt2.setter def wkt2(self, v: Optional[str]) -> None: - self.set_wkt2(v) - - def get_wkt2(self, asset: Optional[Asset] = None) -> Optional[str]: - """Gets an Item or an Asset wkt2. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - str - """ - if asset is None or 'proj:wkt2' not in asset.properties: - return self.item.properties.get('proj:wkt2') - else: - return asset.properties.get('proj:wkt2') - - def set_wkt2(self, wkt2: Optional[str], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset wkt2. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:wkt2', wkt2, asset) + self._set_property(WKT2_PROP, v) @property def projjson(self) -> Optional[Dict[str, Any]]: @@ -157,35 +127,11 @@ def projjson(self) -> Optional[Dict[str, Any]]: Returns: dict """ - return self.get_projjson() + return self._get_property(PROJJSON_PROP, Dict[str, Any]) @projjson.setter def projjson(self, v: Optional[Dict[str, Any]]) -> None: - self.set_projjson(v) - - def get_projjson(self, asset: Optional[Asset] = None) -> Optional[Dict[str, Any]]: - """Gets an Item or an Asset projjson. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - dict - """ - if asset is None or 'proj:projjson' not in asset.properties: - return self.item.properties.get('proj:projjson') - else: - return asset.properties.get('proj:projjson') - - def set_projjson(self, - projjson: Optional[Dict[str, Any]], - asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset projjson. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:projjson', projjson, asset) + self._set_property(PROJJSON_PROP, v) @property def geometry(self) -> Optional[Dict[str, Any]]: @@ -201,35 +147,11 @@ def geometry(self) -> Optional[Dict[str, Any]]: Returns: dict """ - return self.get_geometry() + return self._get_property(GEOM_PROP, Dict[str, Any]) @geometry.setter def geometry(self, v: Optional[Dict[str, Any]]) -> None: - self.set_geometry(v) - - def get_geometry(self, asset: Optional[Asset] = None) -> Optional[Dict[str, Any]]: - """Gets an Item or an Asset projection geometry. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - dict - """ - if asset is None or 'proj:geometry' not in asset.properties: - return self.item.properties.get('proj:geometry') - else: - return asset.properties.get('proj:geometry') - - def set_geometry(self, - geometry: Optional[Dict[str, Any]], - asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset projection geometry. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:geometry', geometry, asset) + self._set_property(GEOM_PROP, v) @property def bbox(self) -> Optional[List[float]]: @@ -246,33 +168,12 @@ def bbox(self) -> Optional[List[float]]: Returns: List[float] """ - return self.get_bbox() + return self._get_property(BBOX_PROP, List[float]) @bbox.setter def bbox(self, v: Optional[List[float]]) -> None: - self.set_bbox(v) + self._set_property(BBOX_PROP, v) - def get_bbox(self, asset: Optional[Asset] = None) -> Optional[List[float]]: - """Gets an Item or an Asset projection bbox. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - List[float] - """ - if asset is None or 'proj:bbox' not in asset.properties: - return self.item.properties.get('proj:bbox') - else: - return asset.properties.get('proj:bbox') - - def set_bbox(self, bbox: Optional[List[float]], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset projection bbox. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:bbox', bbox, asset) @property def centroid(self) -> Optional[Dict[str, float]]: @@ -288,35 +189,11 @@ def centroid(self) -> Optional[Dict[str, float]]: Returns: dict """ - return self.get_centroid() + return self._get_property(CENTROID_PROP, Dict[str, float]) @centroid.setter def centroid(self, v: Optional[Dict[str, float]]) -> None: - self.set_centroid(v) - - def get_centroid(self, asset: Optional[Asset] = None) -> Optional[Dict[str, float]]: - """Gets an Item or an Asset centroid. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - dict - """ - if asset is None or 'proj:centroid' not in asset.properties: - return self.item.properties.get('proj:centroid') - else: - return asset.properties.get('proj:centroid') - - def set_centroid(self, - centroid: Optional[Dict[str, float]], - asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset centroid. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:centroid', centroid, asset) + self._set_property(CENTROID_PROP, v) @property def shape(self) -> Optional[List[int]]: @@ -330,33 +207,11 @@ def shape(self) -> Optional[List[int]]: Returns: List[int] """ - return self.get_shape() + return self._get_property(SHAPE_PROP, List[int]) @shape.setter def shape(self, v: Optional[List[int]]) -> None: - self.set_shape(v) - - def get_shape(self, asset: Optional[Asset] = None) -> Optional[List[int]]: - """Gets an Item or an Asset shape. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - List[int] - """ - if asset is None or 'proj:shape' not in asset.properties: - return self.item.properties.get('proj:shape') - else: - return asset.properties.get('proj:shape') - - def set_shape(self, shape: Optional[List[int]], asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset shape. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:shape', shape, asset) + self._set_property(SHAPE_PROP, v) @property def transform(self) -> Optional[List[float]]: @@ -373,40 +228,39 @@ def transform(self) -> Optional[List[float]]: Returns: List[float] """ # noqa: E501 - return self.get_transform() + return self._get_property(TRANSFORM_PROP, List[float]) @transform.setter def transform(self, v: Optional[List[float]]) -> None: - self.set_transform(v) + self._set_property(TRANSFORM_PROP, v) - def get_transform(self, asset: Optional[Asset] = None) -> Optional[List[float]]: - """Gets an Item or an Asset transform. + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value +class ItemProjectionExtension(ProjectionExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties - Returns: - List[float] - """ - if asset is None or 'proj:transform' not in asset.properties: - return self.item.properties.get('proj:transform') - else: - return asset.properties.get('proj:transform') - - def set_transform(self, - transform: Optional[List[float]], - asset: Optional[Asset] = None) -> None: - """Set an Item or an Asset transform. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('proj:transform', transform, asset) + def __repr__(self) -> str: + return ''.format(self.item.id) - @classmethod - def _object_links(cls) -> List[str]: - return [] - @classmethod - def from_item(cls, item: Item) -> "ProjectionItemExt": - return cls(item) +class AssetProjectionExtension(ProjectionExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + +def projection_ext(obj: T) -> ProjectionExtension[T]: + if isinstance(obj, ps.Item): + return ItemProjectionExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetProjectionExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 04a24dbcd..a60782b62 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -4,10 +4,18 @@ """ import enum -from typing import List, Optional +from typing import Any, Dict, Generic, List, Optional, TypeVar import pystac as ps -from pystac import STACError +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin +from pystac.extensions.projection import ProjectionExtension +from pystac.extensions.hooks import ExtensionHooks +from pystac.utils import map_opt + +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) + +SCHEMA_URI = "https://stac-extensions.github.io/sar/v1.0.0/schema.json" # Required INSTRUMENT_MODE: str = 'sar:instrument_mode' @@ -26,6 +34,29 @@ LOOKS_EQUIVALENT_NUMBER: str = 'sar:looks_equivalent_number' OBSERVATION_DIRECTION: str = 'sar:observation_direction' +class SarExtensionHooks(ExtensionHooks): + schema_uri = SCHEMA_URI + + def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if version < '0.9': + # Some sar fields became common_metadata + if 'sar:platform' in d['properties'] and 'platform' not in d['properties']: + d['properties']['platform'] = d['properties']['sar:platform'] + del d['properties']['sar:platform'] + + if 'sar:instrument' in d['properties'] and 'instruments' not in d['properties']: + d['properties']['instruments'] = [d['properties']['sar:instrument']] + del d['properties']['sar:instrument'] + + if 'sar:constellation' in d['properties'] and 'constellation' not in d['properties']: + d['properties']['constellation'] = d['properties']['sar:constellation'] + del d['properties']['sar:constellation'] + + # Update stac_extension entry + if 'stac_extensions' in d and 'sar' in d['stac_extensions']: + d['stac_extensions'].remove('sar') + d['stac_extensions'].append(SCHEMA_URI) + class FrequencyBand(str, enum.Enum): P = 'P' @@ -50,8 +81,7 @@ class ObservationDirection(enum.Enum): RIGHT = 'right' -# TODO: Fix to work with Assets -class SarItemExt(): +class SarExtension(Generic[T], ProjectionExtension[T], ExtensionManagementMixin[ps.Item]): """SarItemExt extends Item to add sar properties to a STAC Item. Args: @@ -64,11 +94,6 @@ class SarItemExt(): Using SarItemExt to directly wrap an item will add the 'sar' extension ID to the item's stac_extensions. """ - item: ps.Item - - def __init__(self, an_item: ps.Item) -> None: - self.item = an_item - def apply(self, instrument_mode: str, frequency_band: FrequencyBand, @@ -136,14 +161,6 @@ def apply(self, if observation_direction: self.observation_direction = observation_direction - @classmethod - def from_item(cls, an_item: ps.Item) -> "SarItemExt": - return cls(an_item) - - @classmethod - def _object_links(cls) -> List[str]: - return [] - @property def instrument_mode(self) -> str: """Get or sets an instrument mode string for the item. @@ -151,15 +168,14 @@ def instrument_mode(self) -> str: Returns: str """ - result = self.item.properties.get(INSTRUMENT_MODE) + result = self._get_property(INSTRUMENT_MODE, str) if result is None: - raise STACError(f"Item with sar extension does not have property {INSTRUMENT_MODE}, " - f"id {self.item.id}") + raise ps.STACError(f"Property {INSTRUMENT_MODE} does not exist") return result @instrument_mode.setter def instrument_mode(self, v: str) -> None: - self.item.properties[INSTRUMENT_MODE] = v + self._set_property(INSTRUMENT_MODE, v, pop_if_none=False) @property def frequency_band(self) -> FrequencyBand: @@ -168,15 +184,14 @@ def frequency_band(self) -> FrequencyBand: Returns: FrequencyBand """ - result = self.item.properties.get(FREQUENCY_BAND) + result = self._get_property(FREQUENCY_BAND, str) if result is None: - raise STACError(f"Item with sar extension does not have property {FREQUENCY_BAND}, " - f"id {self.item.id}") + raise ps.STACError(f"Property {FREQUENCY_BAND} does not exist") return FrequencyBand(result) @frequency_band.setter def frequency_band(self, v: FrequencyBand) -> None: - self.item.properties[FREQUENCY_BAND] = v.value + self._set_property(FREQUENCY_BAND, v.value, pop_if_none=False) @property def polarizations(self) -> List[Polarization]: @@ -185,18 +200,16 @@ def polarizations(self) -> List[Polarization]: Returns: List[Polarization] """ - result = self.item.properties.get(POLARIZATIONS) + result = self._get_property(POLARIZATIONS, List[str]) if result is None: - raise STACError( - f"Item with sar extension does not have property {POLARIZATIONS}, id {self.item.id}" - ) + raise ps.STACError(f"Property {POLARIZATIONS} does not exist") return [Polarization(v) for v in result] @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: if not isinstance(values, list): raise ps.STACError(f'polarizations must be a list. Invalid "{values}"') - self.item.properties[POLARIZATIONS] = [v.value for v in values] + self._set_property(POLARIZATIONS, [v.value for v in values], pop_if_none=False) @property def product_type(self) -> str: @@ -205,132 +218,130 @@ def product_type(self) -> str: Returns: str """ - result = self.item.properties.get(PRODUCT_TYPE) + result = self._get_property(PRODUCT_TYPE, str) if result is None: - raise STACError( - f"Item with sar extension does not have property {PRODUCT_TYPE}, id {self.item.id}") + raise ps.STACError(f"Property {PRODUCT_TYPE} does not exist") return result @product_type.setter def product_type(self, v: str) -> None: - self.item.properties[PRODUCT_TYPE] = v + self._set_property(PRODUCT_TYPE, v, pop_if_none=False) @property def center_frequency(self) -> Optional[float]: - """Get or sets a center frequency for the item. - - Returns: - float - """ - return self.item.properties.get(CENTER_FREQUENCY) + """Get or sets a center frequency for the item.""" + return self._get_property(CENTER_FREQUENCY, float) @center_frequency.setter - def center_frequency(self, v: float) -> None: - self.item.properties[CENTER_FREQUENCY] = v + def center_frequency(self, v: Optional[float]) -> None: + self._set_property(CENTER_FREQUENCY, v) @property def resolution_range(self) -> Optional[float]: - """Get or sets a resolution range for the item. - - Returns: - float - """ - return self.item.properties.get(RESOLUTION_RANGE) + """Get or sets a resolution range for the item.""" + return self._get_property(RESOLUTION_RANGE, float) @resolution_range.setter - def resolution_range(self, v: float) -> None: - self.item.properties[RESOLUTION_RANGE] = v + def resolution_range(self, v: Optional[float]) -> None: + self._set_property(RESOLUTION_RANGE, v) @property def resolution_azimuth(self) -> Optional[float]: - """Get or sets a resolution azimuth for the item. - - Returns: - float - """ - return self.item.properties.get(RESOLUTION_AZIMUTH) + """Get or sets a resolution azimuth for the item.""" + return self._get_property(RESOLUTION_AZIMUTH, float) @resolution_azimuth.setter - def resolution_azimuth(self, v: float) -> None: - self.item.properties[RESOLUTION_AZIMUTH] = v + def resolution_azimuth(self, v: Optional[float]) -> None: + self._set_property(RESOLUTION_AZIMUTH, v) @property def pixel_spacing_range(self) -> Optional[float]: - """Get or sets a pixel spacing range for the item. - - Returns: - float - """ - return self.item.properties.get(PIXEL_SPACING_RANGE) + """Get or sets a pixel spacing range for the item.""" + return self._get_property(PIXEL_SPACING_RANGE, float) @pixel_spacing_range.setter - def pixel_spacing_range(self, v: float) -> None: - self.item.properties[PIXEL_SPACING_RANGE] = v + def pixel_spacing_range(self, v: Optional[float]) -> None: + self._set_property(PIXEL_SPACING_RANGE, v) @property def pixel_spacing_azimuth(self) -> Optional[float]: - """Get or sets a pixel spacing azimuth for the item. - - Returns: - float - """ - return self.item.properties.get(PIXEL_SPACING_AZIMUTH) + """Get or sets a pixel spacing azimuth for the item.""" + return self._get_property(PIXEL_SPACING_AZIMUTH, float) @pixel_spacing_azimuth.setter - def pixel_spacing_azimuth(self, v: float) -> None: - self.item.properties[PIXEL_SPACING_AZIMUTH] = v + def pixel_spacing_azimuth(self, v: Optional[float]) -> None: + self._set_property(PIXEL_SPACING_AZIMUTH, v) @property def looks_range(self) -> Optional[int]: - """Get or sets a looks range for the item. - - Returns: - int - """ - return self.item.properties.get(LOOKS_RANGE) + """Get or sets a looks range for the item.""" + return self._get_property(LOOKS_RANGE, int) @looks_range.setter - def looks_range(self, v: int) -> None: - self.item.properties[LOOKS_RANGE] = v + def looks_range(self, v: Optional[int]) -> None: + self._set_property(LOOKS_RANGE, v) @property def looks_azimuth(self) -> Optional[int]: - """Get or sets a looks azimuth for the item. - - Returns: - int - """ - return self.item.properties.get(LOOKS_AZIMUTH) + """Get or sets a looks azimuth for the item.""" + return self._get_property(LOOKS_AZIMUTH, int) @looks_azimuth.setter - def looks_azimuth(self, v: int) -> None: - self.item.properties[LOOKS_AZIMUTH] = v + def looks_azimuth(self, v: Optional[int]) -> None: + self._set_property(LOOKS_AZIMUTH, v) @property def looks_equivalent_number(self) -> Optional[float]: - """Get or sets a looks equivalent number for the item. - - Returns: - float - """ - return self.item.properties.get(LOOKS_EQUIVALENT_NUMBER) + """Get or sets a looks equivalent number for the item.""" + return self._get_property(LOOKS_EQUIVALENT_NUMBER, float) @looks_equivalent_number.setter - def looks_equivalent_number(self, v: float) -> None: - self.item.properties[LOOKS_EQUIVALENT_NUMBER] = v + def looks_equivalent_number(self, v: Optional[float]) -> None: + self._set_property(LOOKS_EQUIVALENT_NUMBER, v) @property def observation_direction(self) -> Optional[ObservationDirection]: - """Get or sets an observation direction for the item. - - Returns: - ObservationDirection - """ - result = self.item.properties.get(OBSERVATION_DIRECTION) + """Get or sets an observation direction for the item.""" + result = self._get_property(OBSERVATION_DIRECTION, str) if result is None: return None return ObservationDirection(result) @observation_direction.setter - def observation_direction(self, v: ObservationDirection) -> None: - self.item.properties[OBSERVATION_DIRECTION] = v.value + def observation_direction(self, v: Optional[ObservationDirection]) -> None: + self._set_property(OBSERVATION_DIRECTION, map_opt(lambda x: x.value, v)) + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + +class ItemSarExtension(SarExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties + + def __repr__(self) -> str: + return ''.format(self.item.id) + + +class AssetSarExtension(SarExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + + +def sar_ext(obj: T) -> SarExtension[T]: + if isinstance(obj, ps.Item): + return ItemSarExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetSarExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + +SAR_EXTENSION_HOOKS: ExtensionHooks = SarExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 0fb387e8f..8da213ba4 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -4,9 +4,15 @@ """ import enum -from typing import List, Optional +from typing import Generic, Optional, TypeVar -import pystac +import pystac as ps +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.utils import map_opt + +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) + +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' @@ -18,7 +24,7 @@ class OrbitState(enum.Enum): GEOSTATIONARY = 'geostationary' -class SatItemExt(): +class SatExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """SatItemExt extends Item to add sat properties to a STAC Item. Args: @@ -31,17 +37,13 @@ class SatItemExt(): 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: pystac.Item) -> None: - self.item = an_item - def apply(self, orbit_state: Optional[OrbitState] = None, relative_orbit: Optional[int] = None) -> None: """Applies ext extension properties to the extended Item. - Must specify at least one of orbit_state or relative_orbit. + 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 (OrbitState): Optional state of the orbit. Either ascending or descending @@ -49,20 +51,9 @@ def apply(self, 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: pystac.Item) -> "SatItemExt": - return cls(an_item) - - @classmethod - def _object_links(cls) -> List[str]: - return [] + self.orbit_state = orbit_state + self.relative_orbit = relative_orbit @property def orbit_state(self) -> Optional[OrbitState]: @@ -71,19 +62,11 @@ def orbit_state(self) -> Optional[OrbitState]: Returns: OrbitState or None """ - if ORBIT_STATE not in self.item.properties: - return None - return OrbitState(self.item.properties[ORBIT_STATE]) + return map_opt(lambda x: OrbitState(x), self._get_property(ORBIT_STATE, str)) @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 + self._set_property(ORBIT_STATE, map_opt(lambda x: x.value, v)) @property def relative_orbit(self) -> Optional[int]: @@ -92,17 +75,41 @@ def relative_orbit(self) -> Optional[int]: Returns: int or None """ - return self.item.properties.get(RELATIVE_ORBIT) + return self._get_property(RELATIVE_ORBIT, int) @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 + def relative_orbit(self, v: Optional[int]) -> None: + self._set_property(RELATIVE_ORBIT, v) + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + +class ItemSatExtension(SatExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties + + def __repr__(self) -> str: + return ''.format(self.item.id) + + +class AssetSatExtension(SatExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + + +def sat_ext(obj: T) -> SatExtension[T]: + if isinstance(obj, ps.Item): + return ItemSatExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetSatExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 931b0cf9a..132c26890 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,10 +8,16 @@ """ import copy -from typing import Any, Dict, List, Optional +from typing import Any, Dict, Generic, List, Optional, TypeVar, Union from urllib import parse import pystac as ps +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.utils import map_opt + +T = TypeVar('T', ps.Collection, ps.Item, contravariant=True) + +SCHEMA_URI = "https://stac-extensions.github.io/scientific/v1.0.0/schema.json" # STAC fields strings. PREFIX: str = 'sci:' @@ -65,23 +71,11 @@ def remove_link(links: List[ps.Link], doi: str) -> None: break -class ScientificItemExt(): - """ScientificItemExt extends Item to add citations and DOIs to a STAC Item. - - Args: - item (Item): The item to be extended. - - Attributes: - item (Item): The item that is being extended. - - Note: - Using ScientificItemExt to directly wrap an item will add the 'scientific' - extension ID to the item's stac_extensions. - """ - item: ps.Item - - def __init__(self, an_item: ps.Item) -> None: - self.item = an_item +class ScientificExtension(Generic[T], PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): + """ScientificItemExt extends Item to add citations and DOIs to a STAC Item.""" + def __init__(self, obj: ps.STACObject) -> None: + self.obj = obj def apply(self, doi: Optional[str] = None, @@ -102,14 +96,6 @@ def apply(self, if publications: self.publications = publications - @classmethod - def from_item(cls, an_item: ps.Item) -> "ScientificItemExt": - return cls(an_item) - - @classmethod - def _object_links(cls) -> List[str]: - return [] - @property def doi(self) -> Optional[str]: """Get or sets the DOI for the item. @@ -117,19 +103,19 @@ def doi(self) -> Optional[str]: Returns: str """ - return self.item.properties.get(DOI) + return self._get_property(DOI, str) @doi.setter def doi(self, v: Optional[str]) -> None: - if DOI in self.item.properties: - if v == self.item.properties[DOI]: + if DOI in self.properties: + if v == self.properties[DOI]: return - remove_link(self.item.links, self.item.properties[DOI]) + remove_link(self.obj.links, self.properties[DOI]) if v is not None: - self.item.properties[DOI] = v + self.properties[DOI] = v url = doi_to_url(v) - self.item.add_link(ps.Link(CITE_AS, url)) + self.obj.add_link(ps.Link(CITE_AS, url)) @property def citation(self) -> Optional[str]: @@ -138,14 +124,11 @@ def citation(self) -> Optional[str]: Returns: str """ - return self.item.properties.get(CITATION) + return self._get_property(CITATION, str) @citation.setter def citation(self, v: Optional[str]) -> None: - if v is None: - self.item.properties.pop(CITATION, None) - else: - self.item.properties[CITATION] = v + self._set_property(CITATION, v) @property def publications(self) -> Optional[List[Publication]]: @@ -154,18 +137,12 @@ def publications(self) -> Optional[List[Publication]]: Returns: List of Publication instances. """ - if PUBLICATIONS in self.item.properties: - return [Publication.from_dict(pub) for pub in self.item.properties[PUBLICATIONS]] - return None + return map_opt(lambda pubs: [Publication.from_dict(pub) for pub in pubs], + self._get_property(PUBLICATIONS, List[Dict[str, Any]])) @publications.setter def publications(self, v: Optional[List[Publication]]) -> None: - if v is None: - self.item.properties.pop(PUBLICATIONS, None) - else: - self.item.properties[PUBLICATIONS] = [pub.to_dict() for pub in v] - for pub in v: - self.item.add_link(pub.get_link()) + self._set_property(PUBLICATIONS, map_opt(lambda pubs: [pub.to_dict() for pub in v], v)) # None for publication will clear all. def remove_publication(self, publication: Optional[Publication] = None) -> None: @@ -174,149 +151,57 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: Args: publication (Publication): The specific publication to remove of None to remove all. """ - if PUBLICATIONS not in self.item.properties: + if PUBLICATIONS not in self.properties: return if not publication: pubs = self.publications if pubs is not None: for one_pub in pubs: - remove_link(self.item.links, one_pub.doi) + remove_link(self.obj.links, one_pub.doi) - del self.item.properties[PUBLICATIONS] + del self.properties[PUBLICATIONS] return # One publication and link to remove - remove_link(self.item.links, publication.doi) + remove_link(self.obj.links, publication.doi) to_remove = publication.to_dict() - self.item.properties[PUBLICATIONS].remove(to_remove) + self.properties[PUBLICATIONS].remove(to_remove) - if not self.item.properties[PUBLICATIONS]: - del self.item.properties[PUBLICATIONS] - - -class ScientificCollectionExt(): - """ScientificCollectionExt extends Collection to add citations and DOIs to a STAC Collection. - - Args: - collection (Collection): The collection to be extended. - - Attributes: - collection (Collection): The collection that is being extended. - - Note: - Using ScientificCollectionExt to directly wrap a collection will add the 'scientific' - extension ID to the collection's stac_extensions. - """ - collection: ps.Collection - - def __init__(self, a_collection: ps.Collection): - self.collection = a_collection - - def apply(self, - doi: Optional[str] = None, - citation: Optional[str] = None, - publications: Optional[List[Publication]] = None) -> None: - """Applies scientific extension properties to the extended Collection. - - Args: - doi (str): Optional DOI string for the collection. Must not be a DOI link. - citation (str): Optional human-readable reference. - publications (List[Publication]): Optional list of relevant publications - referencing and describing the data. - """ - if doi: - self.doi = doi - if citation: - self.citation = citation - if publications: - self.publications = publications - - @classmethod - def from_collection(cls, a_collection: ps.Collection) -> "ScientificCollectionExt": - return cls(a_collection) + if not self.properties[PUBLICATIONS]: + del self.properties[PUBLICATIONS] @classmethod - def _object_links(cls) -> List[str]: - return [] - - @property - def doi(self) -> Optional[str]: - """Get or sets the DOI for the collection. - - Returns: - str - """ - return self.collection.extra_fields.get(DOI) - - @doi.setter - def doi(self, v: Optional[str]) -> None: - if DOI in self.collection.extra_fields: - if v == self.collection.extra_fields[DOI]: - return - remove_link(self.collection.links, self.collection.extra_fields[DOI]) - if v is not None: - self.collection.extra_fields[DOI] = v - url = doi_to_url(v) - self.collection.add_link(ps.Link(CITE_AS, url)) - - @property - def citation(self) -> Optional[str]: - """Get or sets the citation for the collection. - - Returns: - str - """ - return self.collection.extra_fields.get(CITATION) + def get_schema_uri(cls) -> str: + return SCHEMA_URI - @citation.setter - def citation(self, v: Optional[str]) -> None: - if v is None: - self.collection.extra_fields.pop(CITATION, None) - else: - self.collection.extra_fields[CITATION] = v - @property - def publications(self) -> Optional[List[Publication]]: - """Get or sets the publication list for the collection. +class CollectionScientificExtension(ScientificExtension[ps.Collection]): + def __init__(self, collection: ps.Collection): + self.collection = collection + self.properties = collection.extra_fields + self.links = collection.links + super().__init__(self.collection) - Returns: - List of Publication instances. - """ - if PUBLICATIONS in self.collection.extra_fields: - return [Publication.from_dict(p) for p in self.collection.extra_fields[PUBLICATIONS]] - return None + def __repr__(self) -> str: + return ''.format(self.collection.id) - @publications.setter - def publications(self, v: Optional[List[Publication]]) -> None: - if v is None: - self.collection.extra_fields.pop(PUBLICATIONS, None) - else: - self.collection.extra_fields[PUBLICATIONS] = [pub.to_dict() for pub in v] - for pub in v: - self.collection.add_link(pub.get_link()) - # None for publication will clear all. - def remove_publication(self, publication: Optional[Publication] = None) -> None: - """Removes publications from the collection. +class ItemScientificExtension(ScientificExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties + self.links = item.links + super().__init__(self.item) - Args: - publication (Publication): The specific publication to remove of None to remove all. - """ - if PUBLICATIONS not in self.collection.extra_fields: - return - - if not publication: - for one_pub in self.publications or []: - remove_link(self.collection.links, one_pub.doi) - - del self.collection.extra_fields[PUBLICATIONS] - return + def __repr__(self) -> str: + return ''.format(self.item.id) - # One publication and link to remove - remove_link(self.collection.links, publication.doi) - to_remove = publication.to_dict() - self.collection.extra_fields[PUBLICATIONS].remove(to_remove) - if not self.collection.extra_fields[PUBLICATIONS]: - del self.collection.extra_fields[PUBLICATIONS] +def scientific_ext(obj: T) -> ScientificExtension[T]: + if isinstance(obj, ps.Collection): + return CollectionScientificExtension(obj) + if isinstance(obj, ps.Item): + return ItemScientificExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json b/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json index 3c7a38068..f426f1f78 100644 --- a/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json +++ b/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json @@ -1,119 +1,119 @@ { - "id": "CBERS4AWFI", - "stac_version": "1.0.0-beta.2", - "description": "CBERS4 AWFI camera catalog", - "links": [ - { - "rel": "root", - "href": "../catalog.json", - "type": "application/json" - }, + "id": "CBERS4AWFI", + "stac_version": "1.0.0-rc.2", + "description": "CBERS4 AWFI camera catalog", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://schemas.stacspec.org/v1.0.0-beta.2/extensions/item-assets/json-schema/schema.json" + ], + "providers": [ + { + "name": "Instituto Nacional de Pesquisas Espaciais, INPE", + "roles": [ + "producer" + ], + "url": "http://www.cbers.inpe.br" + }, + { + "name": "AMS Kepler", + "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", + "roles": [ + "processor" + ], + "url": "https://github.com/fredliporace/cbers-on-aws" + }, + { + "name": "Amazon Web Services", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/cbers/" + } + ], + "properties": { + "gsd": 64.0, + "platform": "CBERS-4", + "instruments": [ + "AWFI" + ] + }, + "item_assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/jpeg" + }, + "metadata": { + "title": "INPE original metadata", + "type": "text/xml" + }, + "B13": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" + "name": "B13", + "common_name": "blue" } - ], - "stac_extensions": [ - "eo", - "item-assets" - ], - "providers": [ - { - "name": "Instituto Nacional de Pesquisas Espaciais, INPE", - "roles": [ - "producer" - ], - "url": "http://www.cbers.inpe.br" - }, - { - "name": "AMS Kepler", - "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", - "roles": [ - "processor" - ], - "url": "https://github.com/fredliporace/cbers-on-aws" - }, + ] + }, + "B14": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ { - "name": "Amazon Web Services", - "roles": [ - "host" - ], - "url": "https://registry.opendata.aws/cbers/" + "name": "B14", + "common_name": "green" } - ], - "properties": { - "gsd": 64.0, - "platform": "CBERS-4", - "instruments": [ - "AWFI" - ] + ] }, - "item_assets": { - "thumbnail": { - "title": "Thumbnail", - "type": "image/jpeg" - }, - "metadata": { - "title": "INPE original metadata", - "type": "text/xml" - }, - "B13": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B13", - "common_name": "blue" - } - ] - }, - "B14": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B14", - "common_name": "green" - } - ] - }, - "B15": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B15", - "common_name": "red" - } - ] - }, - "B16": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B16", - "common_name": "nir" - } - ] + "B15": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B15", + "common_name": "red" } + ] }, - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -83.0, - 180.0, - 83.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2014-12-08T00:00:00Z", - null - ] - ] + "B16": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B16", + "common_name": "nir" } + ] + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] }, - "license": "CC-BY-SA-3.0" -} + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" +} \ No newline at end of file diff --git a/tests/data-files/projection/example-landsat8.json b/tests/data-files/projection/example-landsat8.json index 4e262cdea..2b060dc93 100644 --- a/tests/data-files/projection/example-landsat8.json +++ b/tests/data-files/projection/example-landsat8.json @@ -1,8 +1,8 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.2", "stac_extensions": [ - "eo", - "projection" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "id": "LC81530252014153LGN00", "type": "Feature", @@ -60,7 +60,6 @@ "eo:bands": [ { "name": "B8", - "common_name": "panchromatic", "center_wavelength": 0.59, "full_width_half_max": 0.18 } diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index a5849c6af..1e4131620 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -3,8 +3,7 @@ import pystac as ps from tests.utils import (TestCases, test_to_from_dict) -from pystac.extensions import file_ext -from pystac.extensions.file import FileDataType +from pystac.extensions.file import file_ext, FileDataType class FileTest(unittest.TestCase): diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index a4308d9f7..f161260cd 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -7,7 +7,7 @@ import pystac as ps from pystac import (Catalog, Item, CatalogType, STAC_IO) -from pystac.extensions import label_ext +from pystac.extensions.label import label_ext import pystac.validation from tests.utils import (TestCases, test_to_from_dict) diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 5e835974f..27231aadb 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -4,7 +4,7 @@ # from copy import deepcopy import pystac as ps -from pystac.extensions import pointcloud_ext +from pystac.extensions.pointcloud import pointcloud_ext from pystac.extensions.pointcloud import PointcloudExtension, PointcloudSchema, PointcloudStatistic from tests.utils import (TestCases, test_to_from_dict) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 787f70bdb..576bc934f 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -1,11 +1,12 @@ import json +from typing import Any, Dict import unittest from copy import deepcopy -import pystac -from pystac import (Item, _OldExtensionShortIDs) -from pystac.extensions import ExtensionError +import pystac as ps from pystac.validation import STACValidationError +from pystac.extensions.projection import ProjectionExtension, projection_ext +from pystac.utils import get_opt from tests.utils import (TestCases, test_to_from_dict) WKT2 = """ @@ -76,15 +77,14 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, Item, d) + test_to_from_dict(self, ps.Item, d) def test_apply(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.proj + item = next(iter(TestCases.test_case_2().get_all_items())) + self.assertFalse(ProjectionExtension.has_extension(item)) - item.ext.enable(_OldExtensionShortIDs.PROJECTION) - item.ext.projection.apply( + ProjectionExtension.add_to(item) + projection_ext(item).apply( 4326, wkt2=WKT2, projjson=PROJJSON, @@ -98,268 +98,268 @@ def test_apply(self): transform=[30.0, 0.0, 224985.0, 0.0, -30.0, 6790215.0, 0.0, 0.0, 1.0]) def test_partial_apply(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) - proj_item.ext.projection.apply(epsg=1111) + projection_ext(proj_item).apply(epsg=1111) - self.assertEqual(proj_item.ext.projection.epsg, 1111) + self.assertEqual(projection_ext(proj_item).epsg, 1111) proj_item.validate() def test_validate_proj(self): - item = pystac.read_file(self.example_uri) + item = ps.Item.from_file(self.example_uri) item.validate() def test_epsg(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:epsg", proj_item.properties) - proj_epsg = proj_item.ext.projection.epsg + proj_epsg = projection_ext(proj_item).epsg self.assertEqual(proj_epsg, proj_item.properties['proj:epsg']) # Set - proj_item.ext.projection.epsg = proj_epsg + 100 + projection_ext(proj_item).epsg = proj_epsg + 100 self.assertEqual(proj_epsg + 100, proj_item.properties['proj:epsg']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_epsg(asset_no_prop), - proj_item.ext.projection.get_epsg()) - self.assertEqual(proj_item.ext.projection.get_epsg(asset_prop), 9999) + self.assertEqual(projection_ext(asset_no_prop).epsg, + projection_ext(proj_item).epsg) + self.assertEqual(projection_ext(asset_prop).epsg, 9999) # Set to Asset - proj_item.ext.projection.set_epsg(8888, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_epsg(asset_no_prop), - proj_item.ext.projection.get_epsg()) - self.assertEqual(proj_item.ext.projection.get_epsg(asset_no_prop), 8888) + projection_ext(asset_no_prop).epsg = 8888 + self.assertNotEqual(projection_ext(asset_no_prop).epsg, + projection_ext(proj_item).epsg) + self.assertEqual(projection_ext(asset_no_prop).epsg, 8888) # Validate proj_item.validate def test_wkt2(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:wkt2", proj_item.properties) - proj_wkt2 = proj_item.ext.projection.wkt2 + proj_wkt2 = projection_ext(proj_item).wkt2 self.assertEqual(proj_wkt2, proj_item.properties['proj:wkt2']) # Set - proj_item.ext.projection.wkt2 = WKT2 + projection_ext(proj_item).wkt2 = WKT2 self.assertEqual(WKT2, proj_item.properties['proj:wkt2']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_wkt2(asset_no_prop), - proj_item.ext.projection.get_wkt2()) - self.assertTrue('TEST_TEXT' in proj_item.ext.projection.get_wkt2(asset_prop)) + self.assertEqual(projection_ext(asset_no_prop).wkt2, + projection_ext(proj_item).wkt2) + self.assertTrue('TEST_TEXT' in get_opt(projection_ext(asset_prop).wkt2)) # Set to Asset asset_value = "TEST TEXT 2" - proj_item.ext.projection.set_wkt2(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_wkt2(asset_no_prop), - proj_item.ext.projection.get_wkt2()) - self.assertEqual(proj_item.ext.projection.get_wkt2(asset_no_prop), asset_value) + projection_ext(asset_no_prop).wkt2 = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).wkt2, + projection_ext(proj_item).wkt2) + self.assertEqual(projection_ext(asset_no_prop).wkt2, asset_value) # Validate proj_item.validate() def test_projjson(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:projjson", proj_item.properties) - proj_projjson = proj_item.ext.projection.projjson + proj_projjson = projection_ext(proj_item).projjson self.assertEqual(proj_projjson, proj_item.properties['proj:projjson']) # Set - proj_item.ext.projection.projjson = PROJJSON + projection_ext(proj_item).projjson = PROJJSON self.assertEqual(PROJJSON, proj_item.properties['proj:projjson']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_projjson(asset_no_prop), - proj_item.ext.projection.get_projjson()) - self.assertEqual(proj_item.ext.projection.get_projjson(asset_prop)['id']['code'], 9999) + self.assertEqual(projection_ext(asset_no_prop).projjson, + projection_ext(proj_item).projjson) + self.assertEqual(projection_ext(asset_prop).projjson['id']['code'], 9999) # Set to Asset asset_value = deepcopy(PROJJSON) asset_value['id']['code'] = 7777 - proj_item.ext.projection.set_projjson(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_projjson(asset_no_prop), - proj_item.ext.projection.get_projjson()) - self.assertEqual(proj_item.ext.projection.get_projjson(asset_no_prop)['id']['code'], 7777) + projection_ext(asset_no_prop).projjson = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).projjson, + projection_ext(proj_item).projjson) + self.assertEqual(projection_ext(asset_no_prop).projjson['id']['code'], 7777) # Validate proj_item.validate() # Ensure setting bad projjson fails validation with self.assertRaises(STACValidationError): - proj_item.ext.projection.projjson = {"bad": "data"} + projection_ext(proj_item).projjson = {"bad": "data"} proj_item.validate() def test_geometry(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:geometry", proj_item.properties) - proj_geometry = proj_item.ext.projection.geometry + proj_geometry = projection_ext(proj_item).geometry self.assertEqual(proj_geometry, proj_item.properties['proj:geometry']) # Set - proj_item.ext.projection.geometry = proj_item.geometry + projection_ext(proj_item).geometry = proj_item.geometry self.assertEqual(proj_item.geometry, proj_item.properties['proj:geometry']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_geometry(asset_no_prop), - proj_item.ext.projection.get_geometry()) + self.assertEqual(projection_ext(asset_no_prop).geometry, + projection_ext(proj_item).geometry) self.assertEqual( - proj_item.ext.projection.get_geometry(asset_prop)['coordinates'][0][0], [0.0, 0.0]) + projection_ext(asset_prop).geometry['coordinates'][0][0], [0.0, 0.0]) # Set to Asset - asset_value = {'invalid': 'geom'} - proj_item.ext.projection.set_geometry(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_geometry(asset_no_prop), - proj_item.ext.projection.get_geometry()) - self.assertEqual(proj_item.ext.projection.get_geometry(asset_no_prop), asset_value) + asset_value: Dict[str, Any] = {'type': 'Point', 'coordinates': [1.0, 2.0]} + projection_ext(asset_no_prop).geometry = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).geometry, + projection_ext(proj_item).geometry) + self.assertEqual(projection_ext(asset_no_prop).geometry, asset_value) # Validate proj_item.validate() # Ensure setting bad geometry fails validation with self.assertRaises(STACValidationError): - proj_item.ext.projection.geometry = {"bad": "data"} + projection_ext(proj_item).geometry = {"bad": "data"} proj_item.validate() def test_bbox(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:bbox", proj_item.properties) - proj_bbox = proj_item.ext.projection.bbox + proj_bbox = projection_ext(proj_item).bbox self.assertEqual(proj_bbox, proj_item.properties['proj:bbox']) # Set - proj_item.ext.projection.bbox = [1.0, 2.0, 3.0, 4.0] + projection_ext(proj_item).bbox = [1.0, 2.0, 3.0, 4.0] self.assertEqual(proj_item.properties['proj:bbox'], [1.0, 2.0, 3.0, 4.0]) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_bbox(asset_no_prop), - proj_item.ext.projection.get_bbox()) - self.assertEqual(proj_item.ext.projection.get_bbox(asset_prop), [1.0, 2.0, 3.0, 4.0]) + self.assertEqual(projection_ext(asset_no_prop).bbox, + projection_ext(proj_item).bbox) + self.assertEqual(projection_ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) # Set to Asset asset_value = [10.0, 20.0, 30.0, 40.0] - proj_item.ext.projection.set_bbox(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_bbox(asset_no_prop), - proj_item.ext.projection.get_bbox()) - self.assertEqual(proj_item.ext.projection.get_bbox(asset_no_prop), asset_value) + projection_ext(asset_no_prop).bbox = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).bbox, + projection_ext(proj_item).bbox) + self.assertEqual(projection_ext(asset_no_prop).bbox, asset_value) # Validate proj_item.validate() def test_centroid(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:centroid", proj_item.properties) - proj_centroid = proj_item.ext.projection.centroid + proj_centroid = projection_ext(proj_item).centroid self.assertEqual(proj_centroid, proj_item.properties['proj:centroid']) # Set new_val = {'lat': 2.0, 'lon': 3.0} - proj_item.ext.projection.centroid = new_val + projection_ext(proj_item).centroid = new_val self.assertEqual(proj_item.properties['proj:centroid'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_centroid(asset_no_prop), - proj_item.ext.projection.get_centroid()) - self.assertEqual(proj_item.ext.projection.get_centroid(asset_prop), { + self.assertEqual(projection_ext(asset_no_prop).centroid, + projection_ext(proj_item).centroid) + self.assertEqual(projection_ext(asset_prop).centroid, { "lat": 0.5, "lon": 0.3 }) # Set to Asset asset_value = {"lat": 1.5, "lon": 1.3} - proj_item.ext.projection.set_centroid(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_centroid(asset_no_prop), - proj_item.ext.projection.get_centroid()) - self.assertEqual(proj_item.ext.projection.get_centroid(asset_no_prop), asset_value) + projection_ext(asset_no_prop).centroid = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).centroid, + projection_ext(proj_item).centroid) + self.assertEqual(projection_ext(asset_no_prop).centroid, asset_value) # Validate proj_item.validate() # Ensure setting bad centroid fails validation with self.assertRaises(STACValidationError): - proj_item.ext.projection.centroid = {'lat': 2.0, 'lng': 3.0} + projection_ext(proj_item).centroid = {'lat': 2.0, 'lng': 3.0} proj_item.validate() def test_shape(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:shape", proj_item.properties) - proj_shape = proj_item.ext.projection.shape + proj_shape = projection_ext(proj_item).shape self.assertEqual(proj_shape, proj_item.properties['proj:shape']) # Set new_val = [100, 200] - proj_item.ext.projection.shape = new_val + projection_ext(proj_item).shape = new_val self.assertEqual(proj_item.properties['proj:shape'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_shape(asset_no_prop), - proj_item.ext.projection.get_shape()) - self.assertEqual(proj_item.ext.projection.get_shape(asset_prop), [16781, 16621]) + self.assertEqual(projection_ext(asset_no_prop).shape, + projection_ext(proj_item).shape) + self.assertEqual(projection_ext(asset_prop).shape, [16781, 16621]) # Set to Asset asset_value = [1, 2] - proj_item.ext.projection.set_shape(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_shape(asset_no_prop), - proj_item.ext.projection.get_shape()) - self.assertEqual(proj_item.ext.projection.get_shape(asset_no_prop), asset_value) + projection_ext(asset_no_prop).shape = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).shape, + projection_ext(proj_item).shape) + self.assertEqual(projection_ext(asset_no_prop).shape, asset_value) # Validate proj_item.validate() def test_transform(self): - proj_item = pystac.read_file(self.example_uri) + proj_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("proj:transform", proj_item.properties) - proj_transform = proj_item.ext.projection.transform + proj_transform = projection_ext(proj_item).transform self.assertEqual(proj_transform, proj_item.properties['proj:transform']) # Set new_val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - proj_item.ext.projection.transform = new_val + projection_ext(proj_item).transform = new_val self.assertEqual(proj_item.properties['proj:transform'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(proj_item.ext.projection.get_transform(asset_no_prop), - proj_item.ext.projection.get_transform()) - self.assertEqual(proj_item.ext.projection.get_transform(asset_prop), + self.assertEqual(projection_ext(asset_no_prop).transform, + projection_ext(proj_item).transform) + self.assertEqual(projection_ext(asset_prop).transform, [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0]) # Set to Asset asset_value = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0] - proj_item.ext.projection.set_transform(asset_value, asset_no_prop) - self.assertNotEqual(proj_item.ext.projection.get_transform(asset_no_prop), - proj_item.ext.projection.get_transform()) - self.assertEqual(proj_item.ext.projection.get_transform(asset_no_prop), asset_value) + projection_ext(asset_no_prop).transform = asset_value + self.assertNotEqual(projection_ext(asset_no_prop).transform, + projection_ext(proj_item).transform) + self.assertEqual(projection_ext(asset_no_prop).transform, asset_value) # Validate proj_item.validate() diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index 1917f9e54..ebe1c35b1 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -4,16 +4,17 @@ from typing import List import unittest -import pystac +import pystac as ps from pystac.extensions import sar +from pystac.extensions.sar import sar_ext, SarExtension -def make_item() -> pystac.Item: +def make_item() -> ps.Item: asset_id = 'my/items/2011' start = datetime.datetime(2020, 11, 7) - item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) - item.ext.enable(pystac._OldExtensionShortIDs.SAR) + SarExtension.add_to(item) return item @@ -21,27 +22,26 @@ class SarItemExtTest(unittest.TestCase): def setUp(self): super().setUp() self.item = make_item() - self.item.ext.enable(pystac._OldExtensionShortIDs.SAR) def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.SAR], self.item.stac_extensions) + self.assertTrue(SarExtension.has_extension(self.item)) def test_required(self): mode: str = 'Nonesense mode' frequency_band: sar.FrequencyBand = sar.FrequencyBand.P polarizations: List[sar.Polarization] = [sar.Polarization.HV, sar.Polarization.VH] product_type: str = 'Some product' - self.item.ext.sar.apply(mode, frequency_band, polarizations, product_type) - self.assertEqual(mode, self.item.ext.sar.instrument_mode) + sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type) + self.assertEqual(mode, sar_ext(self.item).instrument_mode) self.assertIn(sar.INSTRUMENT_MODE, self.item.properties) - self.assertEqual(frequency_band, self.item.ext.sar.frequency_band) + self.assertEqual(frequency_band, sar_ext(self.item).frequency_band) self.assertIn(sar.FREQUENCY_BAND, self.item.properties) - self.assertEqual(polarizations, self.item.ext.sar.polarizations) + self.assertEqual(polarizations, sar_ext(self.item).polarizations) self.assertIn(sar.POLARIZATIONS, self.item.properties) - self.assertEqual(product_type, self.item.ext.sar.product_type) + self.assertEqual(product_type, sar_ext(self.item).product_type) self.assertIn(sar.PRODUCT_TYPE, self.item.properties) self.item.validate() @@ -61,36 +61,36 @@ def test_all(self): looks_equivalent_number: float = 9.1 observation_direction: sar.ObservationDirection = sar.ObservationDirection.LEFT - self.item.ext.sar.apply(mode, frequency_band, polarizations, product_type, center_frequency, - resolution_range, resolution_azimuth, pixel_spacing_range, - pixel_spacing_azimuth, looks_range, looks_azimuth, - looks_equivalent_number, observation_direction) + sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type, + center_frequency, resolution_range, resolution_azimuth, + pixel_spacing_range, pixel_spacing_azimuth, looks_range, + looks_azimuth, looks_equivalent_number, observation_direction) - self.assertEqual(center_frequency, self.item.ext.sar.center_frequency) + self.assertEqual(center_frequency, sar_ext(self.item).center_frequency) self.assertIn(sar.CENTER_FREQUENCY, self.item.properties) - self.assertEqual(resolution_range, self.item.ext.sar.resolution_range) + self.assertEqual(resolution_range, sar_ext(self.item).resolution_range) self.assertIn(sar.RESOLUTION_RANGE, self.item.properties) - self.assertEqual(resolution_azimuth, self.item.ext.sar.resolution_azimuth) + self.assertEqual(resolution_azimuth, sar_ext(self.item).resolution_azimuth) self.assertIn(sar.RESOLUTION_AZIMUTH, self.item.properties) - self.assertEqual(pixel_spacing_range, self.item.ext.sar.pixel_spacing_range) + self.assertEqual(pixel_spacing_range, sar_ext(self.item).pixel_spacing_range) self.assertIn(sar.PIXEL_SPACING_RANGE, self.item.properties) - self.assertEqual(pixel_spacing_azimuth, self.item.ext.sar.pixel_spacing_azimuth) + self.assertEqual(pixel_spacing_azimuth, sar_ext(self.item).pixel_spacing_azimuth) self.assertIn(sar.PIXEL_SPACING_AZIMUTH, self.item.properties) - self.assertEqual(looks_range, self.item.ext.sar.looks_range) + self.assertEqual(looks_range, sar_ext(self.item).looks_range) self.assertIn(sar.LOOKS_RANGE, self.item.properties) - self.assertEqual(looks_azimuth, self.item.ext.sar.looks_azimuth) + self.assertEqual(looks_azimuth, sar_ext(self.item).looks_azimuth) self.assertIn(sar.LOOKS_AZIMUTH, self.item.properties) - self.assertEqual(looks_equivalent_number, self.item.ext.sar.looks_equivalent_number) + self.assertEqual(looks_equivalent_number, sar_ext(self.item).looks_equivalent_number) self.assertIn(sar.LOOKS_EQUIVALENT_NUMBER, self.item.properties) - self.assertEqual(observation_direction, self.item.ext.sar.observation_direction) + self.assertEqual(observation_direction, sar_ext(self.item).observation_direction) self.assertIn(sar.OBSERVATION_DIRECTION, self.item.properties) self.item.validate() @@ -101,8 +101,8 @@ def test_polarization_must_be_list(self): # Skip type hint as we are passing in an incorrect polarization. polarizations = sar.Polarization.HV product_type: str = 'Some product' - with self.assertRaises(pystac.STACError): - self.item.ext.sar.apply(mode, frequency_band, polarizations, product_type) + with self.assertRaises(ps.STACError): + sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type) # type:ignore if __name__ == '__main__': diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index 99b8c561e..76495d5d3 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -1,19 +1,22 @@ """Tests for pystac.extensions.sat.""" import datetime +from typing import Any, Dict +from pystac.validation import STACValidationError import unittest -import pystac +import pystac as ps from pystac.extensions import sat +from pystac.extensions.sat import sat_ext, SatExtension -def make_item() -> pystac.Item: +def make_item() -> ps.Item: """Create basic test items that are only slightly different.""" asset_id = 'an/asset' start = datetime.datetime(2018, 1, 2) - item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) - item.ext.enable(pystac._OldExtensionShortIDs.SAT) + SatExtension.add_to(item) return item @@ -23,60 +26,58 @@ def setUp(self): self.item = make_item() def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.SAT], self.item.stac_extensions) + self.assertTrue(SatExtension.has_extension(self.item)) def test_no_args_fails(self): - with self.assertRaises(pystac.STACError): - self.item.ext.sat.apply() + sat_ext(self.item).apply() + with self.assertRaises(STACValidationError): + self.item.validate() def test_orbit_state(self): orbit_state = sat.OrbitState.ASCENDING - self.item.ext.sat.apply(orbit_state) - self.assertEqual(orbit_state, self.item.ext.sat.orbit_state) + sat_ext(self.item).apply(orbit_state) + self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) self.assertNotIn(sat.RELATIVE_ORBIT, self.item.properties) - self.assertFalse(self.item.ext.sat.relative_orbit) + self.assertFalse(sat_ext(self.item).relative_orbit) self.item.validate() def test_relative_orbit(self): relative_orbit = 1234 - self.item.ext.sat.apply(None, relative_orbit) - self.assertEqual(relative_orbit, self.item.ext.sat.relative_orbit) + sat_ext(self.item).apply(None, relative_orbit) + self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) self.assertNotIn(sat.ORBIT_STATE, self.item.properties) - self.assertFalse(self.item.ext.sat.orbit_state) + self.assertFalse(sat_ext(self.item).orbit_state) self.item.validate() def test_relative_orbit_no_negative(self): negative_relative_orbit = -2 - with self.assertRaises(pystac.STACError): - self.item.ext.sat.apply(None, negative_relative_orbit) - - self.item.ext.sat.apply(None, 123) - with self.assertRaises(pystac.STACError): - self.item.ext.sat.relative_orbit = negative_relative_orbit + sat_ext(self.item).apply(None, negative_relative_orbit) + with self.assertRaises(STACValidationError): + self.item.validate() def test_both(self): orbit_state = sat.OrbitState.DESCENDING relative_orbit = 4321 - self.item.ext.sat.apply(orbit_state, relative_orbit) - self.assertEqual(orbit_state, self.item.ext.sat.orbit_state) - self.assertEqual(relative_orbit, self.item.ext.sat.relative_orbit) + sat_ext(self.item).apply(orbit_state, relative_orbit) + self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) + self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) self.item.validate() def test_modify(self): - self.item.ext.sat.apply(sat.OrbitState.DESCENDING, 999) + sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) orbit_state = sat.OrbitState.GEOSTATIONARY - self.item.ext.sat.orbit_state = orbit_state + sat_ext(self.item).orbit_state = orbit_state relative_orbit = 1000 - self.item.ext.sat.relative_orbit = relative_orbit - self.assertEqual(orbit_state, self.item.ext.sat.orbit_state) - self.assertEqual(relative_orbit, self.item.ext.sat.relative_orbit) + sat_ext(self.item).relative_orbit = relative_orbit + self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) + self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) self.item.validate() def test_from_dict(self): orbit_state = sat.OrbitState.GEOSTATIONARY relative_orbit = 1001 - d = { + d: Dict[str, Any] = { 'type': 'Feature', 'stac_version': '1.0.0-beta.2', 'id': 'an/asset', @@ -90,42 +91,32 @@ def test_from_dict(self): 'assets': {}, 'stac_extensions': ['sat'] } - item = pystac.Item.from_dict(d) - self.assertEqual(orbit_state, item.ext.sat.orbit_state) - self.assertEqual(relative_orbit, item.ext.sat.relative_orbit) + item = ps.Item.from_dict(d) + self.assertEqual(orbit_state, sat_ext(item).orbit_state) + self.assertEqual(relative_orbit, sat_ext(item).relative_orbit) def test_to_from_dict(self): orbit_state = sat.OrbitState.GEOSTATIONARY relative_orbit = 1002 - self.item.ext.sat.apply(orbit_state, relative_orbit) + sat_ext(self.item).apply(orbit_state, relative_orbit) d = self.item.to_dict() self.assertEqual(orbit_state.value, d['properties'][sat.ORBIT_STATE]) self.assertEqual(relative_orbit, d['properties'][sat.RELATIVE_ORBIT]) - item = pystac.Item.from_dict(d) - self.assertEqual(orbit_state, item.ext.sat.orbit_state) - self.assertEqual(relative_orbit, item.ext.sat.relative_orbit) + item = ps.Item.from_dict(d) + self.assertEqual(orbit_state, sat_ext(item).orbit_state) + self.assertEqual(relative_orbit, sat_ext(item).relative_orbit) def test_clear_orbit_state(self): - self.item.ext.sat.apply(sat.OrbitState.DESCENDING, 999) + sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) - self.item.ext.sat.orbit_state = None - self.assertIsNone(self.item.ext.sat.orbit_state) + sat_ext(self.item).orbit_state = None + self.assertIsNone(sat_ext(self.item).orbit_state) self.item.validate() def test_clear_relative_orbit(self): - self.item.ext.sat.apply(sat.OrbitState.DESCENDING, 999) + sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) - self.item.ext.sat.relative_orbit = None - self.assertIsNone(self.item.ext.sat.relative_orbit) + sat_ext(self.item).relative_orbit = None + self.assertIsNone(sat_ext(self.item).relative_orbit) self.item.validate() - - def test_clear_orbit_state_fail(self): - self.item.ext.sat.apply(sat.OrbitState.DESCENDING) - with self.assertRaises(pystac.STACError): - self.item.ext.sat.orbit_state = None - - def test_clear_orbit_relative_orbit(self): - self.item.ext.sat.apply(None, 1) - with self.assertRaises(pystac.STACError): - self.item.ext.sat.relative_orbit = None diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 07f10eb53..9fee7b6da 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -5,6 +5,7 @@ import pystac from pystac.extensions import scientific +from pystac.extensions.scientific import scientific_ext, ScientificExtension URL_TEMPLATE = 'http://example.com/catalog/%s.json' @@ -32,7 +33,7 @@ def make_item() -> pystac.Item: item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % 2011) - item.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) + ScientificExtension.add_to(item) return item @@ -40,14 +41,13 @@ class ScientificItemExtTest(unittest.TestCase): def setUp(self): super().setUp() self.item = make_item() - self.item.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.SCIENTIFIC], self.item.stac_extensions) + self.assertTrue(ScientificExtension.has_extension(self.item)) def test_doi(self): - self.item.ext.scientific.apply(DOI) - self.assertEqual(DOI, self.item.ext.scientific.doi) + scientific_ext(self.item).apply(DOI) + self.assertEqual(DOI, scientific_ext(self.item).doi) self.assertIn(scientific.DOI, self.item.properties) link = self.item.get_links(scientific.CITE_AS)[0] self.assertEqual(DOI_URL, link.get_href()) @@ -56,29 +56,29 @@ def test_doi(self): # Check that setting the doi does not cause extra links. # Same doi. - self.item.ext.scientific.doi = DOI + scientific_ext(self.item).doi = DOI self.assertEqual(1, len(self.item.get_links(scientific.CITE_AS))) self.item.validate() # Different doi. - self.item.ext.scientific.doi = PUB1_DOI + scientific_ext(self.item).doi = PUB1_DOI self.assertEqual(1, len(self.item.get_links(scientific.CITE_AS))) link = self.item.get_links(scientific.CITE_AS)[0] self.assertEqual(PUB1_DOI_URL, link.get_href()) self.item.validate() def test_citation(self): - self.item.ext.scientific.apply(citation=CITATION) - self.assertEqual(CITATION, self.item.ext.scientific.citation) + scientific_ext(self.item).apply(citation=CITATION) + self.assertEqual(CITATION, scientific_ext(self.item).citation) self.assertIn(scientific.CITATION, self.item.properties) self.assertFalse(self.item.get_links(scientific.CITE_AS)) self.item.validate() def test_publications_one(self): publications = PUBLICATIONS[:1] - self.item.ext.scientific.apply(publications=publications) + scientific_ext(self.item).apply(publications=publications) self.assertEqual([1], [int('1')]) - self.assertEqual(publications, self.item.ext.scientific.publications) + self.assertEqual(publications, scientific_ext(self.item).publications) self.assertIn(scientific.PUBLICATIONS, self.item.properties) links = self.item.get_links(scientific.CITE_AS) @@ -88,8 +88,8 @@ def test_publications_one(self): self.item.validate() def test_publications(self): - self.item.ext.scientific.apply(publications=PUBLICATIONS) - self.assertEqual(PUBLICATIONS, self.item.ext.scientific.publications) + scientific_ext(self.item).apply(publications=PUBLICATIONS) + self.assertEqual(PUBLICATIONS, scientific_ext(self.item).publications) self.assertIn(scientific.PUBLICATIONS, self.item.properties) links = self.item.get_links(scientific.CITE_AS) @@ -100,9 +100,9 @@ def test_publications(self): def test_remove_publication_one(self): publications = PUBLICATIONS[:1] - self.item.ext.scientific.apply(DOI, publications=publications) - self.item.ext.scientific.remove_publication(publications[0]) - self.assertFalse(self.item.ext.scientific.publications) + scientific_ext(self.item).apply(DOI, publications=publications) + scientific_ext(self.item).remove_publication(publications[0]) + self.assertFalse(scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -110,61 +110,61 @@ def test_remove_publication_one(self): def test_remove_all_publications_one(self): publications = PUBLICATIONS[:1] - self.item.ext.scientific.apply(DOI, publications=publications) - self.item.ext.scientific.remove_publication() - self.assertFalse(self.item.ext.scientific.publications) + scientific_ext(self.item).apply(DOI, publications=publications) + scientific_ext(self.item).remove_publication() + self.assertFalse(scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_publication_forward(self): - self.item.ext.scientific.apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) - self.item.ext.scientific.remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], self.item.ext.scientific.publications) + scientific_ext(self.item).remove_publication(PUBLICATIONS[0]) + self.assertEqual([PUBLICATIONS[1]], scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) self.assertEqual(PUB2_DOI_URL, links[1].target) self.item.validate() - self.item.ext.scientific.remove_publication(PUBLICATIONS[1]) - self.assertFalse(self.item.ext.scientific.publications) + scientific_ext(self.item).remove_publication(PUBLICATIONS[1]) + self.assertFalse(scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_publication_reverse(self): - self.item.ext.scientific.apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) - self.item.ext.scientific.remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], self.item.ext.scientific.publications) + scientific_ext(self.item).remove_publication(PUBLICATIONS[1]) + self.assertEqual([PUBLICATIONS[0]], scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) self.item.validate() - self.item.ext.scientific.remove_publication(PUBLICATIONS[0]) + scientific_ext(self.item).remove_publication(PUBLICATIONS[0]) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_all_publications_with_some(self): - self.item.ext.scientific.apply(DOI, publications=PUBLICATIONS) - self.item.ext.scientific.remove_publication() - self.assertFalse(self.item.ext.scientific.publications) + scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.item).remove_publication() + self.assertFalse(scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_all_publications_with_none(self): - self.item.ext.scientific.apply(DOI) - self.item.ext.scientific.remove_publication() - self.assertFalse(self.item.ext.scientific.publications) + scientific_ext(self.item).apply(DOI) + scientific_ext(self.item).remove_publication() + self.assertFalse(scientific_ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -175,14 +175,14 @@ def make_collection() -> pystac.Collection: asset_id = 'my/thing' start = datetime.datetime(2018, 8, 24) end = start + datetime.timedelta(5, 4, 3, 2, 1) - bboxes = [[-180, -90, 180, 90]] + bboxes = [[-180.0, -90.0, 180.0, 90.0]] spatial_extent = pystac.SpatialExtent(bboxes) temporal_extent = pystac.TemporalExtent([[start, end]]) extent = pystac.Extent(spatial_extent, temporal_extent) collection = pystac.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % 2019) - collection.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) + ScientificExtension.add_to(collection) return collection @@ -190,14 +190,13 @@ class ScientificCollectionExtTest(unittest.TestCase): def setUp(self): super().setUp() self.collection = make_collection() - self.collection.ext.enable(pystac._OldExtensionShortIDs.SCIENTIFIC) def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.SCIENTIFIC], self.collection.stac_extensions) + self.assertTrue(ScientificExtension.has_extension(self.collection)) def test_doi(self): - self.collection.ext.scientific.apply(DOI) - self.assertEqual(DOI, self.collection.ext.scientific.doi) + scientific_ext(self.collection).apply(DOI) + self.assertEqual(DOI, scientific_ext(self.collection).doi) self.assertIn(scientific.DOI, self.collection.extra_fields) link = self.collection.get_links(scientific.CITE_AS)[0] self.assertEqual(DOI_URL, link.get_href()) @@ -206,28 +205,28 @@ def test_doi(self): # Check that setting the doi does not cause extra links. # Same doi. - self.collection.ext.scientific.doi = DOI + scientific_ext(self.collection).doi = DOI self.assertEqual(1, len(self.collection.get_links(scientific.CITE_AS))) self.collection.validate() # Different doi. - self.collection.ext.scientific.doi = PUB1_DOI + scientific_ext(self.collection).doi = PUB1_DOI self.assertEqual(1, len(self.collection.get_links(scientific.CITE_AS))) link = self.collection.get_links(scientific.CITE_AS)[0] self.assertEqual(PUB1_DOI_URL, link.get_href()) self.collection.validate() def test_citation(self): - self.collection.ext.scientific.apply(citation=CITATION) - self.assertEqual(CITATION, self.collection.ext.scientific.citation) + scientific_ext(self.collection).apply(citation=CITATION) + self.assertEqual(CITATION, scientific_ext(self.collection).citation) self.assertIn(scientific.CITATION, self.collection.extra_fields) self.assertFalse(self.collection.get_links(scientific.CITE_AS)) self.collection.validate() def test_publications_one(self): publications = PUBLICATIONS[:1] - self.collection.ext.scientific.apply(publications=publications) - self.assertEqual(publications, self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(publications=publications) + self.assertEqual(publications, scientific_ext(self.collection).publications) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -238,8 +237,8 @@ def test_publications_one(self): self.collection.validate() def test_publications(self): - self.collection.ext.scientific.apply(publications=PUBLICATIONS) - self.assertEqual(PUBLICATIONS, self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(publications=PUBLICATIONS) + self.assertEqual(PUBLICATIONS, scientific_ext(self.collection).publications) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -251,9 +250,9 @@ def test_publications(self): def test_remove_publication_one(self): publications = PUBLICATIONS[:1] - self.collection.ext.scientific.apply(DOI, publications=publications) - self.collection.ext.scientific.remove_publication(publications[0]) - self.assertFalse(self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(DOI, publications=publications) + scientific_ext(self.collection).remove_publication(publications[0]) + self.assertFalse(scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -261,61 +260,61 @@ def test_remove_publication_one(self): def test_remove_all_publications_one(self): publications = PUBLICATIONS[:1] - self.collection.ext.scientific.apply(DOI, publications=publications) - self.collection.ext.scientific.remove_publication() - self.assertFalse(self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(DOI, publications=publications) + scientific_ext(self.collection).remove_publication() + self.assertFalse(scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_publication_forward(self): - self.collection.ext.scientific.apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) - self.collection.ext.scientific.remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], self.collection.ext.scientific.publications) + scientific_ext(self.collection).remove_publication(PUBLICATIONS[0]) + self.assertEqual([PUBLICATIONS[1]], scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) self.assertEqual(PUB2_DOI_URL, links[1].target) self.collection.validate() - self.collection.ext.scientific.remove_publication(PUBLICATIONS[1]) - self.assertFalse(self.collection.ext.scientific.publications) + scientific_ext(self.collection).remove_publication(PUBLICATIONS[1]) + self.assertFalse(scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_publication_reverse(self): - self.collection.ext.scientific.apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) - self.collection.ext.scientific.remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], self.collection.ext.scientific.publications) + scientific_ext(self.collection).remove_publication(PUBLICATIONS[1]) + self.assertEqual([PUBLICATIONS[0]], scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) self.collection.validate() - self.collection.ext.scientific.remove_publication(PUBLICATIONS[0]) + scientific_ext(self.collection).remove_publication(PUBLICATIONS[0]) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_all_publications_with_some(self): - self.collection.ext.scientific.apply(DOI, publications=PUBLICATIONS) - self.collection.ext.scientific.remove_publication() - self.assertFalse(self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) + scientific_ext(self.collection).remove_publication() + self.assertFalse(scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_all_publications_with_none(self): - self.collection.ext.scientific.apply(DOI) - self.collection.ext.scientific.remove_publication() - self.assertFalse(self.collection.ext.scientific.publications) + scientific_ext(self.collection).apply(DOI) + scientific_ext(self.collection).remove_publication() + self.assertFalse(scientific_ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) From f70e36447f28e8b08f7d023ed43f662894071c14 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 27 Apr 2021 23:50:56 -0400 Subject: [PATCH 13/51] Remove single-file-stac extensions This extension is in transition - there's upcoming work to either remove this extension or transfer it to be based on ItemCollections. Removing it for now to remove maintenance burden. --- pystac/extensions/single_file_stac.py | 135 ---------------------- tests/extensions/test_single_file_stac.py | 56 --------- 2 files changed, 191 deletions(-) delete mode 100644 pystac/extensions/single_file_stac.py delete mode 100644 tests/extensions/test_single_file_stac.py diff --git a/pystac/extensions/single_file_stac.py b/pystac/extensions/single_file_stac.py deleted file mode 100644 index 32929ac9c..000000000 --- a/pystac/extensions/single_file_stac.py +++ /dev/null @@ -1,135 +0,0 @@ -# from pystac.item import Item -# from typing import Dict, List, Optional, cast - -# import pystac as ps -# from pystac.extensions.base import (CatalogExtension, ExtensionDefinition, ExtendedObject) - -# def create_single_file_stac(catalog: ps.Catalog) -> ps.Catalog: -# """Creates a Single File STAC from a STAC catalog. - -# This method will recursively collect any collections and items in the catalog -# and return a new catalog with the same properties as the old one, with cleared -# links and the 'collections' and 'features' property of the Single File STAC holding -# each of the respective collected STAC objects. - -# Collections will be filtered to only those referenced by items via the collection_id. -# All links in the items and collections will be cleared in the Single File STAC. - -# Args: -# catalog (Catalog): Catalog to walk while constructing the Single File STAC -# """ -# collections: Dict[str, ps.Collection] = {} -# items: List[ps.Item] = [] -# for root, _, cat_items in catalog.walk(): -# if isinstance(root, ps.Collection): -# new_collection = root.clone() -# new_collection.clear_links() -# collections[root.id] = new_collection -# for item in cat_items: -# new_item = item.clone() -# new_item.clear_links() -# items.append(new_item) - -# filtered_collections: List[ps.Collection] = [] -# for item in items: -# if item.collection_id is not None and item.collection_id in collections: -# filtered_collections.append(collections[item.collection_id]) -# collections.pop(item.collection_id) - -# result = catalog.clone() -# result.clear_links() -# result.ext.enable(ps.Extensions.SINGLE_FILE_STAC) -# sfs_ext = cast("SingleFileSTACCatalogExt", result.ext[ps.Extensions.SINGLE_FILE_STAC]) -# sfs_ext.apply(features=items, collections=filtered_collections) - -# return result - -# class SingleFileSTACCatalogExt(CatalogExtension): -# """An extension of Catalog that provides a set of Collections and Items -# as a single file catalog. A SingleFileSTAC -# is a self contained catalog that contains everything that would normally be in a -# linked set of STAC files. - -# Args: -# catalog (Catalog): The catalog to be extended. - -# Attributes: -# catalog (Catalog): The catalog that is being extended. - -# Note: -# Using SingleFileSTACCatalogExt to directly wrap a Catalog will -# add the 'proj' extension ID to the catalog's stac_extensions. -# """ -# def __init__(self, catalog: ps.Catalog) -> None: -# if catalog.stac_extensions is None: -# catalog.stac_extensions = [str(ps.Extensions.SINGLE_FILE_STAC)] -# elif str(ps.Extensions.SINGLE_FILE_STAC) not in catalog.stac_extensions: -# catalog.stac_extensions.append(str(ps.Extensions.SINGLE_FILE_STAC)) - -# self.catalog = catalog - -# def apply(self, -# features: List[Item], -# collections: Optional[List[ps.Collection]] = None) -> None: -# """ -# Args: -# features (List[Item]): List of items contained by -# this SingleFileSTAC. -# collections (List[Collection]): Optional list of collections that are -# used by any of the Items in the catalog. -# """ -# self.features = features -# self.collections = collections - -# @classmethod -# def enable_extension(cls, stac_object: ps.STACObject) -> None: -# # Ensure the 'type' property is correct so that the Catalog is valid GeoJSON. -# if isinstance(stac_object, ps.Catalog): -# stac_object.extra_fields['type'] = 'FeatureCollection' - -# @property -# def features(self) -> List[Item]: -# """Get or sets a list of :class:`~pystac.Item` contained in this Single File STAC. - -# Returns: -# List[Item] -# """ -# features = self.catalog.extra_fields.get('features') -# if features is None: -# raise ps.STACError('Invalid Single File STAC: does not have "features" property.') - -# return [Item.from_dict(feature) for feature in features] - -# @features.setter -# def features(self, v: List[Item]) -> None: -# self.catalog.extra_fields['features'] = [item.to_dict() for item in v] - -# @property -# def collections(self) -> Optional[List[ps.Collection]]: -# """Get or sets a list of :class:`~pystac.Collection` objects contained -# in this Single File STAC. -# """ -# collections = self.catalog.extra_fields.get('collections') - -# if collections is None: -# return None -# else: -# return [ps.Collection.from_dict(col) for col in collections] - -# @collections.setter -# def collections(self, v: Optional[List[ps.Collection]]) -> None: -# if v is not None: -# self.catalog.extra_fields['collections'] = [col.to_dict() for col in v] -# else: -# self.catalog.extra_fields.pop('collections', None) - -# @classmethod -# def _object_links(cls) -> List[str]: -# return [] - -# @classmethod -# def from_catalog(cls, catalog: ps.Catalog) -> "SingleFileSTACCatalogExt": -# return SingleFileSTACCatalogExt(catalog) - -# SFS_EXTENSION_DEFINITION: ExtensionDefinition = ExtensionDefinition( -# ps.Extensions.SINGLE_FILE_STAC, [ExtendedObject(ps.Catalog, SingleFileSTACCatalogExt)]) diff --git a/tests/extensions/test_single_file_stac.py b/tests/extensions/test_single_file_stac.py deleted file mode 100644 index 7ffcea05f..000000000 --- a/tests/extensions/test_single_file_stac.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -import unittest -from tempfile import TemporaryDirectory -import json - -import pystac -from pystac.extensions.single_file_stac import create_single_file_stac -from tests.utils import TestCases - - -class SingleFileSTACTest(unittest.TestCase): - def setUp(self): - self.EXAMPLE_SINGLE_FILE = TestCases.get_path( - 'data-files/examples/1.0.0-beta.2/' - 'extensions/single-file-stac/examples/example-search.json') - with open(TestCases.get_path(self.EXAMPLE_SINGLE_FILE)) as f: - self.EXAMPLE_SF_DICT = json.load(f) - - def test_read_single_file_stac(self): - cat = pystac.read_file(self.EXAMPLE_SINGLE_FILE) - - cat.validate() - - features = cat.ext['single-file-stac'].features - self.assertEqual(len(features), 2) - - self.assertEqual(features[0].ext.view.sun_azimuth, 152.63804142) - - collections = cat.ext['single-file-stac'].collections - - self.assertEqual(len(collections), 1) - self.assertEqual(collections[0].license, "PDDL-1.0") - - def test_create_single_file_stac(self): - cat = TestCases.test_case_1() - sfs = create_single_file_stac(TestCases.test_case_1()) - - with TemporaryDirectory() as tmp_dir: - path = os.path.join(tmp_dir, 'single_file_stac.json') - pystac.write_file(sfs, include_self_link=False, dest_href=path) - - sfs_read = pystac.read_file(path) - - sfs_read.validate() - - self.assertTrue(sfs_read.ext.implements('single-file-stac')) - - read_fids = set([f.id for f in sfs_read.ext['single-file-stac'].features]) - expected_fids = set([f.id for f in cat.get_all_items()]) - - self.assertEqual(read_fids, expected_fids) - - read_col_ids = set([col.id for col in sfs_read.ext['single-file-stac'].collections]) - expected_col_ids = set(['area-1-1', 'area-1-2', 'area-2-1', 'area-2-2']) - - self.assertEqual(read_col_ids, expected_col_ids) From aa57ae5b92ec6895cb01e24d7041e740bb0732d1 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Wed, 28 Apr 2021 00:17:05 -0400 Subject: [PATCH 14/51] More extension refactors --- pystac/__init__.py | 5 - pystac/extensions/scientific.py | 14 +- pystac/extensions/timestamps.py | 171 ++++++----------- tests/data-files/get_examples.py | 6 +- .../timestamps/example-landsat8.json | 173 +++++++++--------- tests/extensions/test_extensions.py | 12 +- tests/extensions/test_scientific.py | 16 +- tests/extensions/test_timestamps.py | 102 +++++------ tests/extensions/test_version.py | 36 ++-- tests/extensions/test_view.py | 12 +- tests/test_cache.py | 2 +- tests/test_link.py | 24 +-- tests/test_version.py | 10 +- tests/validation/test_schema_uri_map.py | 2 +- tests/validation/test_validate.py | 26 +-- 15 files changed, 276 insertions(+), 335 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index 43dde5ffe..1c6f93c37 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -30,13 +30,8 @@ import pystac.extensions.eo import pystac.extensions.label 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 import pystac.extensions.view -import pystac.extensions.file EXTENSION_HOOKS: pystac.extensions.hooks.RegisteredExtensionHooks = pystac.extensions.hooks.RegisteredExtensionHooks( [ diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 132c26890..1d4933951 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -89,12 +89,9 @@ def apply(self, publications (List[Publication]): Optional list of relevant publications referencing and describing the data. """ - if doi: - self.doi = doi - if citation: - self.citation = citation - if publications: - self.publications = publications + self.doi = doi + self.citation = citation + self.publications = publications @property def doi(self) -> Optional[str]: @@ -142,7 +139,10 @@ def publications(self) -> Optional[List[Publication]]: @publications.setter def publications(self, v: Optional[List[Publication]]) -> None: - self._set_property(PUBLICATIONS, map_opt(lambda pubs: [pub.to_dict() for pub in v], v)) + self._set_property(PUBLICATIONS, map_opt(lambda pubs: [pub.to_dict() for pub in pubs], v)) + if v is not None: + for pub in v: + self.obj.add_link(pub.get_link()) # None for publication will clear all. def remove_publication(self, publication: Optional[Publication] = None) -> None: diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 73d0e2b88..96daa4f1b 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -1,36 +1,23 @@ from datetime import datetime as Datetime -from typing import List, Optional +from typing import Generic, Optional, TypeVar import pystac as ps -from pystac.item import Asset, Item -from pystac.utils import datetime_to_str, str_to_datetime +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.utils import datetime_to_str, map_opt, str_to_datetime +T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) -class TimestampsItemExt(): - """TimestampsItemExt is the extension of an Item in that - allows to specify additional timestamps for assets and metadata. +SCHEMA_URI = "https://stac-extensions.github.io/timestamps/v1.0.0/schema.json" - Args: - item (Item): The item to be extended. +PUBLISHED_PROP = "published" +EXPIRES_PROP = "expires" +UNPUBLISHED_PROP = "unpublished" - Attributes: - item (Item): The Item that is being extended. - Note: - Using TimestampsItemExt to directly wrap an item will add the 'timestamps' - extension ID to the item's stac_extensions. +class TimestampsExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): + """TimestampsItemExt is the extension of an Item in that + allows to specify additional timestamps for assets and metadata. """ - def __init__(self, item: Item) -> None: - self.item = item - - @classmethod - def from_item(cls, item: Item) -> "TimestampsItemExt": - return cls(item) - - @classmethod - def _object_links(cls) -> List[str]: - return [] - def apply(self, published: Optional[Datetime] = None, expires: Optional[Datetime] = None, @@ -45,55 +32,17 @@ def apply(self, unpublished (datetime or None): Date and time the corresponding data was unpublished. """ - if published is None and expires is None and unpublished is None: - raise ps.STACError("timestamps extension needs at least one property value.") - self.published = published self.expires = expires self.unpublished = unpublished - def _timestamp_getter(self, key: str, asset: Optional[Asset] = None) -> Optional[Datetime]: - if asset is not None and key in asset.properties: - timestamp_str = asset.properties.get(key) - else: - timestamp_str = self.item.properties.get(key) - - timestamp = None - if timestamp_str is not None: - timestamp = str_to_datetime(timestamp_str) - - return timestamp - - def _timestamp_setter(self, - timestamp: Optional[Datetime], - key: str, - asset: Optional[Asset] = None) -> None: - if timestamp is not None: - value: Optional[str] = datetime_to_str(timestamp) - else: - value = None - self._set_property(key, value, asset) - @property def published(self) -> Optional[Datetime]: """Get or sets a datetime objects that represent the date and time that the corresponding data was published the first time. - Returns: - datetime - """ - return self.get_published() - - @published.setter - def published(self, v: Optional[Datetime]) -> None: - self.set_published(v) - - def get_published(self, asset: Optional[Asset] = None) -> Optional[Datetime]: - """Get an Item or Asset published datetime - - If an Asset is supplied and the published property exists on the Asset, - return the Asset's value. Otherwise return the Item's value. 'Published' + 'Published' has a different meaning depending on where it is used. If available in the asset properties, it refers to the timestamps valid for the actual data linked to the Asset Object. If it comes from the Item properties, it's referencing to @@ -102,15 +51,11 @@ def get_published(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime """ - return self._timestamp_getter('published', asset) - - def set_published(self, published: Optional[Datetime], asset: Optional[Asset] = None) -> None: - """Set an Item or asset published datetime + return map_opt(str_to_datetime, self._get_property(PUBLISHED_PROP, str)) - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._timestamp_setter(published, 'published', asset) + @published.setter + def published(self, v: Optional[Datetime]) -> None: + self._set_property(PUBLISHED_PROP, map_opt(datetime_to_str, v)) @property def expires(self) -> Optional[Datetime]: @@ -118,20 +63,7 @@ def expires(self) -> Optional[Datetime]: the date and time the corresponding data expires (is not valid any longer). - Returns: - datetime - """ - return self.get_expires() - - @expires.setter - def expires(self, v: Optional[Datetime]) -> None: - self.set_expires(v) - - def get_expires(self, asset: Optional[Asset] = None) -> Optional[Datetime]: - """Get an Item or Asset expires datetime - - If an Asset is supplied and the expires property exists on the Asset, - return the Asset's value. Otherwise return the Item's value. 'Unpublished' + 'Unpublished' has a different meaning depending on where it is used. If available in the asset properties, it refers to the timestamps valid for the actual data linked to the Asset Object. If it comes from the Item properties, it's referencing to @@ -140,51 +72,60 @@ def get_expires(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime """ - return self._timestamp_getter('expires', asset) - - def set_expires(self, expires: Optional[Datetime], asset: Optional[Asset] = None) -> None: - """Set an Item or asset expires datetime + return map_opt(str_to_datetime, self._get_property(EXPIRES_PROP, str)) - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._timestamp_setter(expires, 'expires', asset) + @expires.setter + def expires(self, v: Optional[Datetime]) -> None: + self._set_property(EXPIRES_PROP, map_opt(datetime_to_str, v)) @property def unpublished(self) -> Optional[Datetime]: """Get or sets a datetime objects that represent the Date and time the corresponding data was unpublished. + 'Unpublished' + has a different meaning depending on where it is used. If available in + the asset properties, it refers to the timestamps valid for the actual data linked + to the Asset Object. If it comes from the Item properties, it's referencing to + the timestamp valid for the metadata. + Returns: datetime """ - return self.get_unpublished() + return map_opt(str_to_datetime, self._get_property(UNPUBLISHED_PROP, str)) @unpublished.setter def unpublished(self, v: Optional[Datetime]) -> None: - self.set_unpublished(v) + self._set_property(UNPUBLISHED_PROP, map_opt(datetime_to_str, v)) - def get_unpublished(self, asset: Optional[Asset] = None) -> Optional[Datetime]: - """Get an Item or Asset unpublished datetime + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI - If an Asset is supplied and the unpublished property exists on the Asset, - return the Asset's value. Otherwise return the Item's value. 'Unpublished' - has a different meaning depending on where it is used. If available in - the asset properties, it refers to the timestamps valid for the actual data linked - to the Asset Object. If it comes from the Item properties, it's referencing to - the timestamp valid for the metadata. +class ItemTimestampsExtension(TimestampsExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties - Returns: - datetime - """ - return self._timestamp_getter('unpublished', asset) + def __repr__(self) -> str: + return ''.format(self.item.id) - def set_unpublished(self, - unpublished: Optional[Datetime], - asset: Optional[Asset] = None) -> None: - """Set an Item or asset unpublished datetime - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._timestamp_setter(unpublished, 'unpublished', asset) +class AssetTimestampsExtension(TimestampsExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + + +def timestamps_ext(obj: T) -> TimestampsExtension[T]: + if isinstance(obj, ps.Item): + return ItemTimestampsExtension(obj) + elif isinstance(obj, ps.Asset): + return AssetTimestampsExtension(obj) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index 6cf96810c..389914470 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -22,7 +22,7 @@ def remove_bad_collection(js): if rel is not None and rel == 'collection': href = link['href'] try: - json.loads(pystac.STAC_IO.read_text(href)) + json.loads(ps.STAC_IO.read_text(href)) filtered_links.append(link) except (HTTPError, FileNotFoundError, json.decoder.JSONDecodeError): print('===REMOVING UNREADABLE COLLECTION AT {}'.format(href)) @@ -42,7 +42,7 @@ def remove_bad_collection(js): args = parser.parse_args() stac_repo = 'https://github.com/radiantearth/stac-spec' - stac_spec_tag = 'v{}'.format(pystac.get_stac_version()) + stac_spec_tag = 'v{}'.format(ps.get_stac_version()) examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'examples')) @@ -69,7 +69,7 @@ def remove_bad_collection(js): example_version = js.get('stac_version') if example_version is not None and \ example_version > args.previous_version: - relpath = '{}/{}'.format(pystac.get_stac_version(), + relpath = '{}/{}'.format(ps.get_stac_version(), path.replace('{}/'.format(tmp_dir), '')) target_path = os.path.join(examples_dir, relpath) diff --git a/tests/data-files/timestamps/example-landsat8.json b/tests/data-files/timestamps/example-landsat8.json index 55c5e6697..bb19b5265 100644 --- a/tests/data-files/timestamps/example-landsat8.json +++ b/tests/data-files/timestamps/example-landsat8.json @@ -1,91 +1,96 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [ - "timestamps" + "type": "Feature", + "stac_version": "1.0.0-rc.2", + "id": "LC08_L1TP_107018_20181001", + "properties": { + "datetime": "2018-10-01T01:08:32Z", + "created": "2018-10-02T00:00:03Z", + "updated": "2020-05-20T12:13:02Z", + "published": "2018-10-03T06:45:55Z", + "expires": "2025-01-01T00:00:00Z", + "platform": "landsat-8", + "instruments": [ + "oli", + "tirs" ], - "id": "LC08_L1TP_107018_20181001", - "type": "Feature", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 152.52758, - 60.63437 - ], - [ - 149.1755, - 61.19016 - ], - [ - 148.13933, - 59.51584 - ], - [ - 151.33786, - 58.97792 - ], - [ - 152.52758, - 60.63437 - ] - ] + "constellation": "landsat" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 ] + ] + ] + }, + "links": [ + { + "rel": "self", + "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" + } + ], + "assets": { + "blue": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 2 (blue)", + "created": "2018-10-02T00:00:00Z", + "published": "2018-11-02T00:00:00Z", + "expires": "2018-12-02T00:00:00Z", + "unpublished": "2019-01-02T00:00:00Z" + }, + "green": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 3 (green)", + "created": "2018-10-02T00:00:00Z" }, - "properties": { - "datetime": "2018-10-01T01:08:32Z", - "created": "2018-10-02T00:00:03Z", - "updated": "2020-05-20T12:13:02Z", - "published": "2018-10-03T06:45:55Z", - "expires": "2025-01-01T00:00:00Z", - "platform": "landsat-8", - "instruments": ["oli", "tirs"], - "constellation": "landsat" + "red": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 4 (red)", + "created": "2018-10-02T00:00:00Z" }, - "assets": { - "blue": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", - "type": "image/tiff; application=geotiff", - "title": "Band 2 (blue)", - "created": "2018-10-02T00:00:00Z", - "published": "2018-11-02T00:00:00Z", - "expires": "2018-12-02T00:00:00Z", - "unpublished": "2019-01-02T00:00:00Z" - }, - "green": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", - "type": "image/tiff; application=geotiff", - "title": "Band 3 (green)", - "created": "2018-10-02T00:00:00Z" - }, - "red": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", - "type": "image/tiff; application=geotiff", - "title": "Band 4 (red)", - "created": "2018-10-02T00:00:00Z" - }, - "thumbnail": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", - "title": "Thumbnail image", - "type": "image/jpeg", - "created": "2018-10-02T00:00:01Z" - }, - "index": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", - "type": "text/html", - "title": "HTML index page", - "created": "2018-10-02T00:00:02Z", - "updated": "2020-05-20T12:13:01Z" - } + "thumbnail": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", + "type": "image/jpeg", + "title": "Thumbnail image", + "created": "2018-10-02T00:00:01Z" }, - "links": [{ - "rel": "self", - "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" - }] + "index": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", + "type": "text/html", + "title": "HTML index page", + "created": "2018-10-02T00:00:02Z", + "updated": "2020-05-20T12:13:01Z" + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/timestamps/v1.0.0/schema.json" + ] } \ No newline at end of file diff --git a/tests/extensions/test_extensions.py b/tests/extensions/test_extensions.py index f2e3cdcf0..5aa8ffe65 100644 --- a/tests/extensions/test_extensions.py +++ b/tests/extensions/test_extensions.py @@ -62,9 +62,9 @@ def _object_links(cls): class ExtensionsTest(unittest.TestCase): def test_can_add_custom_extension(self): - prev_extensions = pystac.EXTENSION_HOOKS.get_registered_extensions() + prev_extensions = ps.EXTENSION_HOOKS.get_registered_extensions() - pystac.EXTENSION_HOOKS.add_extension_hooks( + ps.EXTENSION_HOOKS.add_extension_hooks( ExtensionDefinition("test", [ ExtendedObject(Catalog, TestCatalogExt), ExtendedObject(Collection, TestCollectionExt), @@ -85,10 +85,10 @@ def test_can_add_custom_extension(self): self.assertEqual(item.ext.test.asset_keys, set(item.assets)) finally: - pystac.EXTENSION_HOOKS.remove_extension("test") + ps.EXTENSION_HOOKS.remove_extension("test") - self.assertFalse(pystac.EXTENSION_HOOKS.is_registered_extension("test")) - self.assertEqual(pystac.EXTENSION_HOOKS.get_registered_extensions(), prev_extensions) + self.assertFalse(ps.EXTENSION_HOOKS.is_registered_extension("test")) + self.assertEqual(ps.EXTENSION_HOOKS.get_registered_extensions(), prev_extensions) def test_getattribute_overload(self): catalog = Catalog(id='test', description='test') @@ -97,4 +97,4 @@ def test_getattribute_overload(self): self.assertRaises(ExtensionError, catalog.ext.__getattr__, 'eo') catalog.ext.enable('single-file-stac') self.assertTrue(catalog.ext.__getattr__('single-file-stac'), - pystac.extensions.single_file_stac.SingleFileSTACCatalogExt) + ps.extensions.single_file_stac.SingleFileSTACCatalogExt) diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 9fee7b6da..06c8fd4c2 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -3,7 +3,7 @@ import datetime import unittest -import pystac +import pystac as ps from pystac.extensions import scientific from pystac.extensions.scientific import scientific_ext, ScientificExtension @@ -27,10 +27,10 @@ ] -def make_item() -> pystac.Item: +def make_item() -> ps.Item: asset_id = 'USGS/GAP/CONUS/2011' start = datetime.datetime(2011, 1, 2) - item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % 2011) ScientificExtension.add_to(item) @@ -171,15 +171,15 @@ def test_remove_all_publications_with_none(self): self.item.validate() -def make_collection() -> pystac.Collection: +def make_collection() -> ps.Collection: asset_id = 'my/thing' start = datetime.datetime(2018, 8, 24) end = start + datetime.timedelta(5, 4, 3, 2, 1) bboxes = [[-180.0, -90.0, 180.0, 90.0]] - spatial_extent = pystac.SpatialExtent(bboxes) - temporal_extent = pystac.TemporalExtent([[start, end]]) - extent = pystac.Extent(spatial_extent, temporal_extent) - collection = pystac.Collection(asset_id, 'desc', extent) + spatial_extent = ps.SpatialExtent(bboxes) + temporal_extent = ps.TemporalExtent([[start, end]]) + extent = ps.Extent(spatial_extent, temporal_extent) + collection = ps.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % 2019) ScientificExtension.add_to(collection) diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index 1d9573ade..cb078325c 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -1,11 +1,10 @@ import json -import pystac import unittest from datetime import datetime -from pystac import (_OldExtensionShortIDs, Item) -from pystac.extensions import ExtensionError -from pystac.utils import (str_to_datetime, datetime_to_str) +import pystac as ps +from pystac.extensions.timestamps import timestamps_ext, TimestampsExtension +from pystac.utils import (get_opt, str_to_datetime, datetime_to_str) from tests.utils import (TestCases, test_to_from_dict) @@ -19,22 +18,22 @@ def setUp(self): self.sample_datetime = str_to_datetime(self.sample_datetime_str) def test_to_from_dict(self): - test_to_from_dict(self, Item, self.item_dict) + test_to_from_dict(self, ps.Item, self.item_dict) def test_apply(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.timestamps + item = next(iter(TestCases.test_case_2().get_all_items())) + self.assertFalse(TimestampsExtension.has_extension(item)) - item.ext.enable(_OldExtensionShortIDs.TIMESTAMPS) - self.assertIn(_OldExtensionShortIDs.TIMESTAMPS, item.stac_extensions) - item.ext.timestamps.apply(published=str_to_datetime("2020-01-03T06:45:55Z"), - expires=str_to_datetime("2020-02-03T06:45:55Z"), - unpublished=str_to_datetime("2020-03-03T06:45:55Z")) + TimestampsExtension.add_to(item) + self.assertTrue(TimestampsExtension.has_extension(item)) + timestamps_ext(item).apply(published=str_to_datetime("2020-01-03T06:45:55Z"), + expires=str_to_datetime("2020-02-03T06:45:55Z"), + unpublished=str_to_datetime("2020-03-03T06:45:55Z")) for d in [ - item.ext.timestamps.published, item.ext.timestamps.expires, - item.ext.timestamps.unpublished + timestamps_ext(item).published, + timestamps_ext(item).expires, + timestamps_ext(item).unpublished ]: self.assertIsInstance(d, datetime) @@ -42,109 +41,110 @@ def test_apply(self): self.assertIsInstance(item.properties[p], str) published_str = "2020-04-03T06:45:55Z" - item.ext.timestamps.apply(published=str_to_datetime(published_str)) - self.assertIsInstance(item.ext.timestamps.published, datetime) + timestamps_ext(item).apply(published=str_to_datetime(published_str)) + self.assertIsInstance(timestamps_ext(item).published, datetime) self.assertEqual(item.properties['published'], published_str) - for d in [item.ext.timestamps.expires, item.ext.timestamps.unpublished]: + for d in [timestamps_ext(item).expires, timestamps_ext(item).unpublished]: self.assertIsNone(d) for p in ('expires', 'unpublished'): self.assertNotIn(p, item.properties) def test_validate_timestamps(self): - item = pystac.read_file(self.example_uri) + item = ps.read_file(self.example_uri) item.validate() def test_expires(self): - timestamps_item = pystac.read_file(self.example_uri) + timestamps_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("expires", timestamps_item.properties) - timestamps_expires = timestamps_item.ext.timestamps.expires + timestamps_expires = timestamps_ext(timestamps_item).expires self.assertIsInstance(timestamps_expires, datetime) - self.assertEqual(datetime_to_str(timestamps_expires), timestamps_item.properties['expires']) + self.assertEqual(datetime_to_str(get_opt(timestamps_expires)), + timestamps_item.properties['expires']) # Set - timestamps_item.ext.timestamps.expires = self.sample_datetime + timestamps_ext(timestamps_item).expires = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['expires']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_item.ext.timestamps.get_expires(asset_no_prop), - timestamps_item.ext.timestamps.get_expires()) - self.assertEqual(timestamps_item.ext.timestamps.get_expires(asset_prop), + self.assertEqual(timestamps_ext(asset_no_prop).expires, + timestamps_ext(timestamps_item).expires) + self.assertEqual(timestamps_ext(asset_prop).expires, str_to_datetime("2018-12-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_item.ext.timestamps.set_expires(asset_value, asset_no_prop) - self.assertNotEqual(timestamps_item.ext.timestamps.get_expires(asset_no_prop), - timestamps_item.ext.timestamps.get_expires()) - self.assertEqual(timestamps_item.ext.timestamps.get_expires(asset_no_prop), asset_value) + timestamps_ext(asset_no_prop).expires =asset_value + self.assertNotEqual(timestamps_ext(asset_no_prop).expires, + timestamps_ext(timestamps_item).expires) + self.assertEqual(timestamps_ext(asset_no_prop).expires, asset_value) # Validate timestamps_item.validate() def test_published(self): - timestamps_item = pystac.read_file(self.example_uri) + timestamps_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("published", timestamps_item.properties) - timestamps_published = timestamps_item.ext.timestamps.published + timestamps_published = timestamps_ext(timestamps_item).published self.assertIsInstance(timestamps_published, datetime) - self.assertEqual(datetime_to_str(timestamps_published), + self.assertEqual(datetime_to_str(get_opt(timestamps_published)), timestamps_item.properties['published']) # Set - timestamps_item.ext.timestamps.published = self.sample_datetime + timestamps_ext(timestamps_item).published = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['published']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_item.ext.timestamps.get_published(asset_no_prop), - timestamps_item.ext.timestamps.get_published()) - self.assertEqual(timestamps_item.ext.timestamps.get_published(asset_prop), + self.assertEqual(timestamps_ext(asset_no_prop).published, + timestamps_ext(timestamps_item).published) + self.assertEqual(timestamps_ext(asset_prop).published, str_to_datetime("2018-11-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_item.ext.timestamps.set_published(asset_value, asset_no_prop) - self.assertNotEqual(timestamps_item.ext.timestamps.get_published(asset_no_prop), - timestamps_item.ext.timestamps.get_published()) - self.assertEqual(timestamps_item.ext.timestamps.get_published(asset_no_prop), asset_value) + timestamps_ext(asset_no_prop).published = asset_value + self.assertNotEqual(timestamps_ext(asset_no_prop).published, + timestamps_ext(timestamps_item).published) + self.assertEqual(timestamps_ext(asset_no_prop).published, asset_value) # Validate timestamps_item.validate() def test_unpublished(self): - timestamps_item = pystac.read_file(self.example_uri) + timestamps_item = ps.Item.from_file(self.example_uri) # Get self.assertNotIn("unpublished", timestamps_item.properties) - timestamps_unpublished = timestamps_item.ext.timestamps.unpublished + timestamps_unpublished = timestamps_ext(timestamps_item).unpublished self.assertIsNone(timestamps_unpublished, datetime) # Set - timestamps_item.ext.timestamps.unpublished = self.sample_datetime + timestamps_ext(timestamps_item).unpublished = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['unpublished']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_item.ext.timestamps.get_unpublished(asset_no_prop), - timestamps_item.ext.timestamps.get_unpublished()) - self.assertEqual(timestamps_item.ext.timestamps.get_unpublished(asset_prop), + self.assertEqual(timestamps_ext(asset_no_prop).unpublished, + timestamps_ext(timestamps_item).unpublished) + self.assertEqual(timestamps_ext(asset_prop).unpublished, str_to_datetime("2019-01-02T00:00:00Z")) # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_item.ext.timestamps.set_unpublished(asset_value, asset_no_prop) - self.assertNotEqual(timestamps_item.ext.timestamps.get_unpublished(asset_no_prop), - timestamps_item.ext.timestamps.get_unpublished()) - self.assertEqual(timestamps_item.ext.timestamps.get_unpublished(asset_no_prop), asset_value) + timestamps_ext(asset_no_prop).unpublished = asset_value + self.assertNotEqual(timestamps_ext(asset_no_prop).unpublished, + timestamps_ext(timestamps_item).unpublished) + self.assertEqual(timestamps_ext(asset_no_prop).unpublished, asset_value) # Validate timestamps_item.validate() diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 0a93c0823..8f31e1cd2 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -10,15 +10,15 @@ URL_TEMPLATE: str = 'http://example.com/catalog/%s.json' -def make_item(year: int) -> pystac.Item: +def make_item(year: int) -> ps.Item: """Create basic test items that are only slightly different.""" asset_id = f'USGS/GAP/CONUS/{year}' start = datetime.datetime(year, 1, 2) - item = pystac.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % year) - item.ext.enable(pystac._OldExtensionShortIDs.VERSION) + item.ext.enable(ps._OldExtensionShortIDs.VERSION) return item @@ -30,10 +30,10 @@ def setUp(self): super().setUp() self.item = make_item(2011) - self.item.ext.enable(pystac._OldExtensionShortIDs.VERSION) + self.item.ext.enable(ps._OldExtensionShortIDs.VERSION) def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.VERSION], self.item.stac_extensions) + self.assertEqual([ps._OldExtensionShortIDs.VERSION], self.item.stac_extensions) def test_add_version(self): self.item.ext.version.apply(self.version) @@ -97,7 +97,7 @@ def test_successor(self): self.item.validate() def test_fail_validate(self): - with self.assertRaises(pystac.validation.STACValidationError): + with self.assertRaises(ps.validation.STACValidationError): self.item.validate() def test_all_links(self): @@ -117,8 +117,8 @@ def test_full_copy(self): # Enable the version extension on each, and link them # as if they are different versions of the same Item - item1.ext.enable(pystac._OldExtensionShortIDs.VERSION) - item2.ext.enable(pystac._OldExtensionShortIDs.VERSION) + item1.ext.enable(ps._OldExtensionShortIDs.VERSION) + item2.ext.enable(ps._OldExtensionShortIDs.VERSION) item1.ext.version.apply(version='2.0', predecessor=item2) item2.ext.version.apply(version='1.0', successor=item1, latest=item1) @@ -194,19 +194,19 @@ def test_multiple_link_setting(self): self.assertEqual(expected_href, links[0].get_href()) -def make_collection(year: int) -> pystac.Collection: +def make_collection(year: int) -> ps.Collection: asset_id = f'my/collection/of/things/{year}' start = datetime.datetime(2014, 8, 10) end = datetime.datetime(year, 1, 3, 4, 5) bboxes = [[-180, -90, 180, 90]] - spatial_extent = pystac.SpatialExtent(bboxes) - temporal_extent = pystac.TemporalExtent([[start, end]]) - extent = pystac.Extent(spatial_extent, temporal_extent) + spatial_extent = ps.SpatialExtent(bboxes) + temporal_extent = ps.TemporalExtent([[start, end]]) + extent = ps.Extent(spatial_extent, temporal_extent) - collection = pystac.Collection(asset_id, 'desc', extent) + collection = ps.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % year) - collection.ext.enable(pystac._OldExtensionShortIDs.VERSION) + collection.ext.enable(ps._OldExtensionShortIDs.VERSION) return collection @@ -219,7 +219,7 @@ def setUp(self): self.collection = make_collection(2011) def test_stac_extensions(self): - self.assertEqual([pystac._OldExtensionShortIDs.VERSION], self.collection.stac_extensions) + self.assertEqual([ps._OldExtensionShortIDs.VERSION], self.collection.stac_extensions) def test_add_version(self): self.collection.ext.version.apply(self.version) @@ -283,7 +283,7 @@ def test_successor(self): self.collection.validate() def test_fail_validate(self): - with self.assertRaises(pystac.validation.STACValidationError): + with self.assertRaises(ps.validation.STACValidationError): self.collection.validate() def test_validate_all(self): @@ -303,8 +303,8 @@ def test_full_copy(self): # Enable the version extension on each, and link them # as if they are different versions of the same Collection - col1.ext.enable(pystac._OldExtensionShortIDs.VERSION) - col2.ext.enable(pystac._OldExtensionShortIDs.VERSION) + col1.ext.enable(ps._OldExtensionShortIDs.VERSION) + col2.ext.enable(ps._OldExtensionShortIDs.VERSION) col1.ext.version.apply(version='2.0', predecessor=col2) col2.ext.version.apply(version='1.0', successor=col1, latest=col1) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index d8aff3529..7f982d0b3 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -53,11 +53,11 @@ def test_apply_none(self): ) def test_validate_view(self): - item = pystac.read_file(self.example_uri) + item = ps.read_file(self.example_uri) item.validate() def test_off_nadir(self): - view_item = pystac.read_file(self.example_uri) + view_item = ps.read_file(self.example_uri) # Get self.assertIn("view:off_nadir", view_item.properties) @@ -86,7 +86,7 @@ def test_off_nadir(self): view_item.validate() def test_incidence_angle(self): - view_item = pystac.read_file(self.example_uri) + view_item = ps.read_file(self.example_uri) # Get self.assertIn("view:incidence_angle", view_item.properties) @@ -115,7 +115,7 @@ def test_incidence_angle(self): view_item.validate() def test_azimuth(self): - view_item = pystac.read_file(self.example_uri) + view_item = ps.read_file(self.example_uri) # Get self.assertIn("view:azimuth", view_item.properties) @@ -144,7 +144,7 @@ def test_azimuth(self): view_item.validate() def test_sun_azimuth(self): - view_item = pystac.read_file(self.example_uri) + view_item = ps.read_file(self.example_uri) # Get self.assertIn("view:sun_azimuth", view_item.properties) @@ -173,7 +173,7 @@ def test_sun_azimuth(self): view_item.validate() def test_sun_elevation(self): - view_item = pystac.read_file(self.example_uri) + view_item = ps.read_file(self.example_uri) # Get self.assertIn("view:sun_elevation", view_item.properties) diff --git a/tests/test_cache.py b/tests/test_cache.py index 798ad4692..0ff09be5a 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -6,7 +6,7 @@ def create_catalog(suffix, include_href=True): - return pystac.Catalog( + return ps.Catalog( id='test {}'.format(suffix), description='test desc {}'.format(suffix), href=('http://example.com/catalog_{}.json'.format(suffix) if include_href else None)) diff --git a/tests/test_link.py b/tests/test_link.py index e9db74802..385662069 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -7,10 +7,10 @@ class LinkTest(unittest.TestCase): - item: pystac.Item + item: ps.Item def setUp(self): - self.item = pystac.Item(id='test-item', + self.item = ps.Item(id='test-item', geometry=None, bbox=None, datetime=TEST_DATETIME, @@ -19,7 +19,7 @@ def setUp(self): def test_minimal(self): rel = 'my rel' target = 'https://example.com/a/b' - link = pystac.Link(rel, target) + link = ps.Link(rel, target) self.assertEqual(target, link.get_href()) self.assertEqual(target, link.get_absolute_href()) @@ -56,7 +56,7 @@ def test_relative(self): rel = 'my rel' target = '../elsewhere' mime_type = 'example/stac_thing' - link = pystac.Link(rel, target, mime_type, 'a title', properties={'a': 'b'}) + link = ps.Link(rel, target, mime_type, 'a title', properties={'a': 'b'}) expected_dict = { 'rel': rel, 'href': target, @@ -68,7 +68,7 @@ def test_relative(self): 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') + catalog = ps.Catalog(id='test', description='test desc') catalog.add_item(self.item) catalog.set_self_href('/some/href') @@ -76,7 +76,7 @@ def test_link_does_not_fail_if_href_is_none(self): self.assertIsNone(link.get_href()) def test_resolve_stac_object_no_root_and_target_is_item(self): - link = pystac.Link('my rel', target=self.item) + link = ps.Link('my rel', target=self.item) link.resolve_stac_object() @@ -110,22 +110,22 @@ def test_from_dict_round_trip(self): }, ] for d in test_cases: - d2 = pystac.Link.from_dict(d).to_dict() + d2 = ps.Link.from_dict(d).to_dict() self.assertEqual(d, d2) def test_from_dict_failures(self): for d in [{}, {'href': 't'}, {'rel': 'r'}]: with self.assertRaises(KeyError): - pystac.Link.from_dict(d) + ps.Link.from_dict(d) def test_collection(self): - c = pystac.Collection('collection id', 'desc', extent=None) - link = pystac.Link.collection(c) + c = ps.Collection('collection id', 'desc', extent=None) + link = ps.Link.collection(c) expected = {'rel': 'collection', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) def test_child(self): - c = pystac.Collection('collection id', 'desc', extent=None) - link = pystac.Link.child(c) + c = ps.Collection('collection id', 'desc', extent=None) + link = ps.Link.child(c) expected = {'rel': 'child', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) diff --git a/tests/test_version.py b/tests/test_version.py index 89d26f088..1a9dc49e1 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -22,15 +22,15 @@ def test_override_stac_version_with_environ(self): os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = version def test_override_stac_version_with_call(self): - version = pystac.get_stac_version() + version = ps.get_stac_version() try: override_version = '1.0.0-delta.2' - pystac.set_stac_version(override_version) + ps.set_stac_version(override_version) cat = TestCases.test_case_1() d = cat.to_dict() self.assertEqual(d['stac_version'], override_version) finally: - if version == pystac.version.STACVersion.DEFAULT_STAC_VERSION: - pystac.set_stac_version(None) + if version == ps.version.STACVersion.DEFAULT_STAC_VERSION: + ps.set_stac_version(None) else: - pystac.set_stac_version(version) + ps.set_stac_version(version) diff --git a/tests/validation/test_schema_uri_map.py b/tests/validation/test_schema_uri_map.py index 285942da7..fac1fc95a 100644 --- a/tests/validation/test_schema_uri_map.py +++ b/tests/validation/test_schema_uri_map.py @@ -7,7 +7,7 @@ class SchemaUriMapTest(unittest.TestCase): def test_gets_extension_for_old_version(self): d = DefaultSchemaUriMap() - uri = d.get_extension_schema_uri('asset', pystac.STACObjectType.COLLECTION, '0.8.0') + uri = d.get_extension_schema_uri('asset', ps.STACObjectType.COLLECTION, '0.8.0') self.assertEqual( uri, 'https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.0/' diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 48a70bfaf..2372e8edc 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -17,18 +17,18 @@ class ValidateTest(unittest.TestCase): def test_validate_current_version(self): - catalog = pystac.read_file( + catalog = ps.read_file( TestCases.get_path('data-files/catalogs/test-case-1/' 'catalog.json')) catalog.validate() - collection = pystac.read_file( + collection = ps.read_file( TestCases.get_path('data-files/catalogs/test-case-1/' '/country-1/area-1-1/' 'collection.json')) collection.validate() - item = pystac.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) item.validate() def test_validate_examples(self): @@ -42,7 +42,7 @@ def test_validate_examples(self): with open(path) as f: stac_json = json.load(f) - self.assertEqual(len(pystac.validation.validate_dict(stac_json)), 0) + self.assertEqual(len(ps.validation.validate_dict(stac_json)), 0) else: with self.subTest(path): with open(path) as f: @@ -50,16 +50,16 @@ def test_validate_examples(self): # Check if common properties need to be merged if stac_version < '1.0': - if example['object_type'] == pystac.STACObjectType.ITEM: + if example['object_type'] == ps.STACObjectType.ITEM: collection_cache = CollectionCache() merge_common_properties(stac_json, collection_cache, path) if valid: - pystac.validation.validate_dict(stac_json) + ps.validation.validate_dict(stac_json) else: with self.assertRaises(STACValidationError): try: - pystac.validation.validate_dict(stac_json) + ps.validation.validate_dict(stac_json) except STACValidationError as e: self.assertIsInstance(e.source, jsonschema.ValidationError) raise e @@ -82,9 +82,9 @@ def test_validate_error_contains_href(self): def test_validate_all(self): for test_case in TestCases.all_test_catalogs(): catalog_href = TestCases.test_case_7().get_self_href() - stac_dict = pystac.STAC_IO.read_json(catalog_href) + stac_dict = ps.STAC_IO.read_json(catalog_href) - pystac.validation.validate_all(stac_dict, catalog_href) + ps.validation.validate_all(stac_dict, catalog_href) # Modify a 0.8.1 collection in a catalog to be invalid with a since-renamed extension # and make sure it catches the validation error. @@ -98,7 +98,7 @@ def test_validate_all(self): new_cat_href = os.path.join(dst_dir, 'catalog.json') # Make sure it's valid before modification - pystac.validation.validate_all(pystac.STAC_IO.read_json(new_cat_href), new_cat_href) + ps.validation.validate_all(ps.STAC_IO.read_json(new_cat_href), new_cat_href) # Modify a contained collection to add an extension for which the # collection is invalid. @@ -108,10 +108,10 @@ def test_validate_all(self): with open(os.path.join(dst_dir, 'acc/collection.json'), 'w') as f: json.dump(col, f) - stac_dict = pystac.STAC_IO.read_json(new_cat_href) + stac_dict = ps.STAC_IO.read_json(new_cat_href) with self.assertRaises(STACValidationError): - pystac.validation.validate_all(stac_dict, new_cat_href) + ps.validation.validate_all(stac_dict, new_cat_href) def test_validates_geojson_with_tuple_coordinates(self): """This unit tests guards against a bug where if a geometry @@ -127,7 +127,7 @@ def test_validates_geojson_with_tuple_coordinates(self): (-115.307, 36.126), (-115.305, 36.126)), ) } - item = pystac.Item(id='test-item', + item = ps.Item(id='test-item', geometry=geom, bbox=[-115.308, 36.126, -115.305, 36.129], datetime=datetime.utcnow(), From a1f5096646a5bc8852ecac1fcbbba0e8eb11dd03 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 00:28:11 -0400 Subject: [PATCH 15/51] All extensions refactored to new architecture --- pystac/__init__.py | 23 +- pystac/collection.py | 10 +- pystac/errors.py | 21 +- pystac/extensions/eo.py | 189 +++++++------ pystac/extensions/file.py | 19 +- pystac/extensions/hooks.py | 57 +++- pystac/extensions/label.py | 70 ++--- pystac/extensions/pointcloud.py | 34 ++- pystac/extensions/projection.py | 20 +- pystac/extensions/sar.py | 91 +++---- pystac/extensions/sat.py | 19 +- pystac/extensions/scientific.py | 20 +- pystac/extensions/timestamps.py | 21 +- pystac/extensions/version.py | 286 ++++++-------------- pystac/extensions/view.py | 209 ++++---------- pystac/item.py | 2 +- pystac/link.py | 4 +- pystac/serialization/identify.py | 6 +- pystac/serialization/migrate.py | 20 -- pystac/stac_object.py | 2 +- pystac/utils.py | 10 + tests/data-files/view/example-landsat8.json | 74 ++--- tests/extensions/test_custom.py | 120 ++++++++ tests/extensions/test_extensions.py | 100 ------- tests/extensions/test_scientific.py | 4 +- tests/extensions/test_version.py | 149 +++++----- tests/extensions/test_view.py | 158 +++++------ tests/serialization/test_identify.py | 4 +- tests/serialization/test_migrate.py | 13 +- tests/test_cache.py | 24 +- tests/test_catalog.py | 29 +- tests/test_collection.py | 3 +- tests/test_item.py | 52 ++-- tests/test_link.py | 9 +- tests/test_version.py | 7 +- tests/test_writing.py | 2 +- tests/utils/test_cases.py | 23 +- tests/validation/test_schema_uri_map.py | 8 +- tests/validation/test_validate.py | 24 +- 39 files changed, 922 insertions(+), 1014 deletions(-) create mode 100644 tests/extensions/test_custom.py delete mode 100644 tests/extensions/test_extensions.py diff --git a/pystac/__init__.py b/pystac/__init__.py index 1c6f93c37..c68b494f3 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -4,7 +4,7 @@ # flake8: noqa -from pystac.errors import (STACError, STACTypeError, RequiredValueMissing) # type:ignore +from pystac.errors import (STACError, STACTypeError, RequiredPropertyMissing) # type:ignore from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore @@ -28,15 +28,30 @@ import pystac.extensions.hooks import pystac.extensions.eo +import pystac.extensions.file import pystac.extensions.label +import pystac.extensions.pointcloud +import pystac.extensions.projection import pystac.extensions.sar +import pystac.extensions.sat +import pystac.extensions.scientific +import pystac.extensions.timestamps import pystac.extensions.version import pystac.extensions.view -EXTENSION_HOOKS: pystac.extensions.hooks.RegisteredExtensionHooks = pystac.extensions.hooks.RegisteredExtensionHooks( +EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks( [ - pystac.extensions.eo.EO_EXTENSION_HOOKS, pystac.extensions.label.LABEL_EXTENSION_HOOKS, - pystac.extensions.sar.SAR_EXTENSION_HOOKS + pystac.extensions.eo.EO_EXTENSION_HOOKS, + pystac.extensions.file.FILE_EXTENSION_HOOKS, + pystac.extensions.label.LABEL_EXTENSION_HOOKS, + pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, + pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, + pystac.extensions.sar.SAR_EXTENSION_HOOKS, + pystac.extensions.sat.SAT_EXTENSION_HOOKS, + pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, + pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, + pystac.extensions.version.VERSION_EXTENSION_HOOKS, + pystac.extensions.view.VIEW_EXTENSION_HOOKS ]) diff --git a/pystac/collection.py b/pystac/collection.py index fb5b49fdd..170d04af6 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -11,7 +11,7 @@ from pystac.catalog import Catalog from pystac.layout import HrefLayoutStrategy from pystac.link import Link -from pystac.utils import datetime_to_str +from pystac.utils import datetime_to_str, get_required if TYPE_CHECKING: from pystac.item import Item as Item_Type @@ -384,12 +384,8 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls, d: Dict[str, Any], typ: Type[T] = Any) -> "RangeSummary[T]": - minimum: Optional[T] = d.get('minimum') - if minimum is None: - raise ps.RequiredValueMissing("Range summary does not have 'minimum' property") - maximum: Optional[T] = d.get("maximum") - if maximum is None: - raise ps.RequiredValueMissing("Range summary does not have 'maximum' property") + minimum: Optional[T] = get_required(d.get('minimum'), 'RangeSummary', 'minimum') + maximum: Optional[T] = get_required(d.get("maximum"), 'RangeSummary', 'maximum') return cls(minimum=minimum, maximum=maximum) diff --git a/pystac/errors.py b/pystac/errors.py index fcc1d6c52..9d9af0e93 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -1,3 +1,6 @@ +from typing import Any, Optional, Union + + class STACError(Exception): """A STACError is raised for errors relating to STAC, e.g. for invalid formats or trying to operate on a STAC that does not have @@ -13,10 +16,24 @@ class STACTypeError(Exception): """ pass -class RequiredValueMissing(Exception): + +class RequiredPropertyMissing(Exception): """ This error is raised when a required value was expected to be there but was missing or None. This will happen, for example, in an extension that has required properties, where the required property is missing from the extended object + + Args: + obj: Description of the object that will have a property missing. + Should include a __repr__ that identifies the object for the + error message, or be a string that describes the object. + prop: The property that is missing """ - pass \ No newline at end of file + def __init__(self, + obj: Union[str, Any], + prop: str, + msg: Optional[str] = None, + *args: Any, + **kwargs: Any) -> None: + msg = msg or f"{repr(obj)} does not have required property {prop}" + super().__init__(msg, *args, **kwargs) \ No newline at end of file diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 066914888..b65d057ab 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -1,15 +1,15 @@ from pystac.collection import RangeSummary import re -from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar, cast +from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension from pystac.extensions.hooks import ExtensionHooks from pystac.extensions import view from pystac.serialization.identify import STACJSONDescription, STACVersionID -from pystac.utils import map_opt +from pystac.utils import get_required, map_opt -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -17,97 +17,6 @@ CLOUD_COVER_PROP = "eo:cloud_cover" -class EOExtensionHooks(ExtensionHooks): - schema_uri = SCHEMA_URI - - def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - if not 'properties' in d: - return - - if version < '0.5': - if 'eo:crs' in d['properties']: - # Try to pull out the EPSG code. - # Otherwise, just leave it alone. - wkt = d['properties']['eo:crs'] - matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) - if len(matches) > 0: - epsg_code = matches[-1].group(1) - d['properties'].pop('eo:crs') - d['properties']['eo:epsg'] = int(epsg_code) - - if version < '0.6': - # Change eo:bands from a dict to a list. eo:bands on an asset - # is an index instead of a dict key. eo:bands is in properties. - bands_dict = d['eo:bands'] - keys_to_indices: Dict[str, int] = {} - bands: List[Dict[str, Any]] = [] - for i, (k, band) in enumerate(bands_dict.items()): - keys_to_indices[k] = i - bands.append(band) - - d.pop('eo:bands') - d['properties']['eo:bands'] = bands - for k, asset in d['assets'].items(): - if 'eo:bands' in asset: - asset_band_indices: List[int] = [] - for bk in asset['eo:bands']: - asset_band_indices.append(keys_to_indices[bk]) - asset['eo:bands'] = sorted(asset_band_indices) - - if version < '0.9': - # Some eo fields became common_metadata - if 'eo:platform' in d['properties'] and 'platform' not in d['properties']: - d['properties']['platform'] = d['properties']['eo:platform'] - del d['properties']['eo:platform'] - - if 'eo:instrument' in d['properties'] and 'instruments' not in d['properties']: - d['properties']['instruments'] = [d['properties']['eo:instrument']] - del d['properties']['eo:instrument'] - - if 'eo:constellation' in d['properties'] and 'constellation' not in d['properties']: - d['properties']['constellation'] = d['properties']['eo:constellation'] - del d['properties']['eo:constellation'] - - # Some eo fields became view extension fields - eo_to_view_fields = [ - 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' - ] - - for field in eo_to_view_fields: - if 'eo:{}'.format(field) in d['properties']: - if 'stac_extensions' not in d: - d['stac_extensions'] = [] - if not view.SCHEMA_URI in d['stac_extensions']: - d['stac_extensions'].append(view.SCHEMA_URI) - if not 'view:{}'.format(field) in d['properties']: - d['properties']['view:{}'.format(field)] = \ - d['properties']['eo:{}'.format(field)] - del d['properties']['eo:{}'.format(field)] - - if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: - # gsd moved from eo to common metadata - if 'eo:gsd' in d['properties']: - d['properties']['gsd'] = d['properties']['eo:gsd'] - del d['properties']['eo:gsd'] - - # The way bands were declared in assets changed. - # In 1.0.0-beta.1 they are inlined into assets as - # opposed to having indices back into a property-level array. - if 'eo:bands' in d['properties']: - bands = d['properties']['eo:bands'] - for asset in d['assets'].values(): - if 'eo:bands' in asset: - new_bands: List[Dict[str, Any]] = [] - for band_index in asset['eo:bands']: - new_bands.append(bands[band_index]) - asset['eo:bands'] = new_bands - - # Update stac_extension entry - if 'stac_extensions' in d and 'eo' in d['stac_extensions']: - d['stac_extensions'].remove('eo') - d['stac_extensions'].append(SCHEMA_URI) - - class Band: """Represents Band information attached to an Item that implements the eo extension. @@ -176,7 +85,7 @@ def name(self) -> str: Returns: str """ - return self.properties['name'] + return get_required(self.properties['name'], self, 'name') @name.setter def name(self, v: str) -> None: @@ -431,12 +340,98 @@ def cloud_cover(self) -> Optional[RangeSummary[float]]: def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: self._set_summary(CLOUD_COVER_PROP, v) +class EOExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['eo']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if version < '0.5': + if 'eo:crs' in obj['properties']: + # Try to pull out the EPSG code. + # Otherwise, just leave it alone. + wkt = obj['properties']['eo:crs'] + matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) + if len(matches) > 0: + epsg_code = matches[-1].group(1) + obj['properties'].pop('eo:crs') + obj['properties']['eo:epsg'] = int(epsg_code) + + if version < '0.6': + # Change eo:bands from a dict to a list. eo:bands on an asset + # is an index instead of a dict key. eo:bands is in properties. + bands_dict = obj['eo:bands'] + keys_to_indices: Dict[str, int] = {} + bands: List[Dict[str, Any]] = [] + for i, (k, band) in enumerate(bands_dict.items()): + keys_to_indices[k] = i + bands.append(band) + + obj.pop('eo:bands') + obj['properties']['eo:bands'] = bands + for k, asset in obj['assets'].items(): + if 'eo:bands' in asset: + asset_band_indices: List[int] = [] + for bk in asset['eo:bands']: + asset_band_indices.append(keys_to_indices[bk]) + asset['eo:bands'] = sorted(asset_band_indices) + + if version < '0.9': + # Some eo fields became common_metadata + if 'eo:platform' in obj['properties'] and 'platform' not in obj['properties']: + obj['properties']['platform'] = obj['properties']['eo:platform'] + del obj['properties']['eo:platform'] + + if 'eo:instrument' in obj['properties'] and 'instruments' not in obj['properties']: + obj['properties']['instruments'] = [obj['properties']['eo:instrument']] + del obj['properties']['eo:instrument'] + + if 'eo:constellation' in obj['properties'] and 'constellation' not in obj['properties']: + obj['properties']['constellation'] = obj['properties']['eo:constellation'] + del obj['properties']['eo:constellation'] + + # Some eo fields became view extension fields + eo_to_view_fields = [ + 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' + ] + + for field in eo_to_view_fields: + if 'eo:{}'.format(field) in obj['properties']: + if 'stac_extensions' not in obj: + obj['stac_extensions'] = [] + if not view.SCHEMA_URI in obj['stac_extensions']: + obj['stac_extensions'].append(view.SCHEMA_URI) + if not 'view:{}'.format(field) in obj['properties']: + obj['properties']['view:{}'.format(field)] = \ + obj['properties']['eo:{}'.format(field)] + del obj['properties']['eo:{}'.format(field)] + + if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: + # gsd moved from eo to common metadata + if 'eo:gsd' in obj['properties']: + obj['properties']['gsd'] = obj['properties']['eo:gsd'] + del obj['properties']['eo:gsd'] + + # The way bands were declared in assets changed. + # In 1.0.0-beta.1 they are inlined into assets as + # opposed to having indices back into a property-level array. + if 'eo:bands' in obj['properties']: + bands = obj['properties']['eo:bands'] + for asset in obj['assets'].values(): + if 'eo:bands' in asset: + new_bands: List[Dict[str, Any]] = [] + for band_index in asset['eo:bands']: + new_bands.append(bands[band_index]) + asset['eo:bands'] = new_bands + + super().migrate(obj, version, info) + def eo_ext(obj: T) -> EOExtension[T]: if isinstance(obj, ps.Item): - return ItemEOExtension(obj) + return cast(EOExtension[T], ItemEOExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetEOExtension(obj) + return cast(EOExtension[T], AssetEOExtension(obj)) else: raise ExtensionException(f"EO extension does not apply to type {type(obj)}") diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 4c629c6e6..56d775bce 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,11 +1,12 @@ import enum -from typing import Any, Generic, List, Optional, TypeVar +from typing import Any, Generic, List, Optional, Set, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension +from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -181,14 +182,22 @@ def nodata(self, v: Optional[List[Any]]) -> None: self._set_summary(NODATA_PROP, v) +class FileExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['file']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def file_ext(obj: T) -> FileExtension[T]: if isinstance(obj, ps.Item): - return ItemFileExtension(obj) + return cast(FileExtension[T], ItemFileExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetFileExtension(obj) + return cast(FileExtension[T], AssetFileExtension(obj)) else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") def file_summaries(obj: ps.Collection) -> SummariesFileExtension: - return SummariesFileExtension(obj) \ No newline at end of file + return SummariesFileExtension(obj) + +FILE_EXTENSION_HOOKS = FileExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index 81367927a..ea9960abf 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -1,31 +1,66 @@ from abc import ABC, abstractmethod -from pystac.serialization.identify import STACJSONDescription, STACVersionID -from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING +from typing import Any, Dict, Iterable, List, Optional, Set, TYPE_CHECKING +import pystac as ps from pystac.extensions import ExtensionError +from pystac.serialization.identify import STACJSONDescription, STACVersionID if TYPE_CHECKING: from pystac.stac_object import STACObject as STACObject_Type class ExtensionHooks(ABC): - """ - - Args: - extension_schema_uri: The Schema URI that is used to validate the extension, - but also act as the ID for the extension. - """ @property @abstractmethod def schema_uri(self) -> str: + """The schema_uri for the current version of this extension""" + pass + + @property + @abstractmethod + def prev_extension_ids(self) -> List[str]: + """A list of previous extension IDs (schema URIs or old short ids) + that should be migrated to the latest schema URI in the 'stac_extensions' + property. Override with a class attribute so that the list of previous + IDs is only created once. + """ pass + @property + @abstractmethod + def stac_object_types(self) -> Set[ps.STACObjectType]: + """A set of STACObjectType for which migration logic will be applied.""" + pass + + _stac_object_type_str: Optional[Set[str]] = None + """Translation of stac_object_types to strings, cached as a class attribute""" + def get_object_links(self, obj: "STACObject_Type") -> Optional[List[str]]: return None def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - pass + """Migrate a STAC Object in dict format from a previous version. + The base implementation will update the stac_extensions to the latest + schema ID. This method will only be called for STAC objects that have been + identified as a previous version of STAC. Implementations should directly + manipulate the obj dict. Remember to call super() in order to change out + the old 'stac_extension' entry with the latest schema URI. + """ + if self._stac_object_type_str is None: + self._stac_object_type_str = set([x.value for x in self.stac_object_types]) + if not info.object_type in self._stac_object_type_str: + return + + # Migrate schema versions + for prev_id in self.prev_extension_ids: + if prev_id in info.extensions: + try: + i = obj['stac_extensions'].index(prev_id) + obj['stac_extension'][i] = self.schema_uri + except ValueError: + obj['stac_extensions'].append(self.schema_uri) + break class RegisteredExtensionHooks: def __init__(self, hooks: Iterable[ExtensionHooks]): @@ -38,6 +73,10 @@ def add_extension_hooks(self, hooks: ExtensionHooks) -> None: self.hooks[e_id] = hooks + def remove_extension_hooks(self, extension_id: str) -> None: + if extension_id in self.hooks: + del self.hooks[extension_id] + def get_extended_object_links(self, obj: "STACObject_Type") -> List[str]: result: Optional[List[str]] = None for ext in obj.stac_extensions: diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index fed104e86..1bc3a1430 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -2,7 +2,7 @@ """ from enum import Enum from pystac.extensions.base import ExtensionManagementMixin -from typing import Any, Dict, Iterable, List, Optional, Union, cast +from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast import pystac as ps from pystac.serialization.identify import STACJSONDescription, STACVersionID @@ -11,36 +11,6 @@ SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json" -class LabelExtensionHooks(ExtensionHooks): - schema_uri: str = SCHEMA_URI - - def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: - if isinstance(so, ps.Item): - return ['source'] - return None - - def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': - props = d['properties'] - # Migrate 0.8.0-rc1 non-pluralized forms - # As it's a common mistake, convert for any pre-1.0.0 version. - if 'label:property' in props and 'label:properties' not in props: - props['label:properties'] = props['label:property'] - del props['label:property'] - - if 'label:task' in props and 'label:tasks' not in props: - props['label:tasks'] = props['label:task'] - del props['label:task'] - - if 'label:overview' in props and 'label:overviews' not in props: - props['label:overviews'] = props['label:overview'] - del props['label:overview'] - - if 'label:method' in props and 'label:methods' not in props: - props['label:methods'] = props['label:method'] - del props['label:method'] - - class LabelType(str, Enum): """Enumerates valid label types (RASTER or VECTOR).""" def __str__(self) -> str: @@ -718,9 +688,45 @@ def add_geojson_labels(self, def get_schema_uri(cls) -> str: return SCHEMA_URI + @classmethod + def ext(cls, obj: ps.Item) -> "LabelExtension": + return cls(obj) + + +class LabelExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['label']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: + if isinstance(so, ps.Item): + return ['source'] + return None + + def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': + props = d['properties'] + # Migrate 0.8.0-rc1 non-pluralized forms + # As it's a common mistake, convert for any pre-1.0.0 version. + if 'label:property' in props and 'label:properties' not in props: + props['label:properties'] = props['label:property'] + del props['label:property'] + + if 'label:task' in props and 'label:tasks' not in props: + props['label:tasks'] = props['label:task'] + del props['label:task'] + + if 'label:overview' in props and 'label:overviews' not in props: + props['label:overviews'] = props['label:overview'] + del props['label:overview'] + + if 'label:method' in props and 'label:methods' not in props: + props['label:methods'] = props['label:method'] + del props['label:method'] + def label_ext(item: ps.Item) -> LabelExtension: - return LabelExtension(item) + return LabelExtension.ext(item) LABEL_EXTENSION_HOOKS: ExtensionHooks = LabelExtensionHooks() diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index f0c131481..5bb8f4d09 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -1,10 +1,11 @@ -from typing import Any, Dict, Generic, List, Optional, TypeVar +from pystac.extensions.hooks import ExtensionHooks +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" @@ -384,7 +385,7 @@ def count(self) -> int: """ result = self._get_property(COUNT_PROP, int) if result is None: - raise ps.RequiredValueMissing(f'No {COUNT_PROP} found') + raise ps.RequiredPropertyMissing(self, COUNT_PROP) return result @count.setter @@ -400,7 +401,7 @@ def type(self) -> str: """ result = self._get_property(TYPE_PROP, str) if result is None: - raise ps.RequiredValueMissing(f'No {TYPE_PROP} found') + raise ps.RequiredPropertyMissing(self, TYPE_PROP) return result @type.setter @@ -419,7 +420,7 @@ def encoding(self) -> str: """ result = self._get_property(ENCODING_PROP, str) if result is None: - raise ps.RequiredValueMissing(f'No {ENCODING_PROP} found') + raise ps.RequiredPropertyMissing(self, ENCODING_PROP) return result @encoding.setter @@ -439,7 +440,7 @@ def schemas(self) -> List[PointcloudSchema]: """ result = self._get_property(SCHEMAS_PROP, List[Dict[str, Any]]) if result is None: - raise ps.RequiredValueMissing(f'No {SCHEMAS_PROP} found') + raise ps.RequiredPropertyMissing(self, SCHEMAS_PROP) return [PointcloudSchema(s) for s in result] @schemas.setter @@ -483,13 +484,14 @@ def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + class ItemPointcloudExtension(PointcloudExtension[ps.Item]): def __init__(self, item: ps.Item): self.item = item self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.item.id) class AssetPointcloudExtension(PointcloudExtension[ps.Asset]): @@ -498,14 +500,26 @@ def __init__(self, asset: ps.Asset): self.properties = asset.properties if asset.owner and isinstance(asset.owner, ps.Item): self.additional_read_properties = [asset.owner.properties] + self.repr_id = f"href={asset.href} item.id={asset.owner.id}" + else: + self.repr_id = f"href={asset.href}" def __repr__(self) -> str: - return ''.format(self.asset_href) + return f'' + + +class PointcloudExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['pointcloud']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + def pointcloud_ext(obj: T) -> PointcloudExtension[T]: if isinstance(obj, ps.Item): - return ItemPointcloudExtension(obj) + return cast(PointcloudExtension[T], ItemPointcloudExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetPointcloudExtension(obj) + return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") + +POINTCLOUD_EXTENSION_HOOKS = PointcloudExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 40984ffda..c74eb83e4 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,9 +1,10 @@ +from pystac.extensions.hooks import ExtensionHooks from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension -from typing import Any, Dict, Generic, List, Optional, TypeVar +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/projection/v1.0.0/schema.json" @@ -244,7 +245,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.item.id) class AssetProjectionExtension(ProjectionExtension[ps.Asset]): @@ -255,12 +256,19 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return ''.format(self.asset_href) + +class ProjectionExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['projection']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) def projection_ext(obj: T) -> ProjectionExtension[T]: if isinstance(obj, ps.Item): - return ItemProjectionExtension(obj) + return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetProjectionExtension(obj) + return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") + +PROJECTION_EXTENSION_HOOKS = ProjectionExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index a60782b62..ff07acaa4 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -4,16 +4,16 @@ """ import enum -from typing import Any, Dict, Generic, List, Optional, TypeVar +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions.base import ExtensionException, ExtensionManagementMixin from pystac.extensions.projection import ProjectionExtension from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import map_opt +from pystac.utils import get_required, map_opt -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/sar/v1.0.0/schema.json" @@ -34,29 +34,6 @@ LOOKS_EQUIVALENT_NUMBER: str = 'sar:looks_equivalent_number' OBSERVATION_DIRECTION: str = 'sar:observation_direction' -class SarExtensionHooks(ExtensionHooks): - schema_uri = SCHEMA_URI - - def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - if version < '0.9': - # Some sar fields became common_metadata - if 'sar:platform' in d['properties'] and 'platform' not in d['properties']: - d['properties']['platform'] = d['properties']['sar:platform'] - del d['properties']['sar:platform'] - - if 'sar:instrument' in d['properties'] and 'instruments' not in d['properties']: - d['properties']['instruments'] = [d['properties']['sar:instrument']] - del d['properties']['sar:instrument'] - - if 'sar:constellation' in d['properties'] and 'constellation' not in d['properties']: - d['properties']['constellation'] = d['properties']['sar:constellation'] - del d['properties']['sar:constellation'] - - # Update stac_extension entry - if 'stac_extensions' in d and 'sar' in d['stac_extensions']: - d['stac_extensions'].remove('sar') - d['stac_extensions'].append(SCHEMA_URI) - class FrequencyBand(str, enum.Enum): P = 'P' @@ -168,10 +145,7 @@ def instrument_mode(self) -> str: Returns: str """ - result = self._get_property(INSTRUMENT_MODE, str) - if result is None: - raise ps.STACError(f"Property {INSTRUMENT_MODE} does not exist") - return result + return get_required(self._get_property(INSTRUMENT_MODE, str), self, INSTRUMENT_MODE) @instrument_mode.setter def instrument_mode(self, v: str) -> None: @@ -184,10 +158,9 @@ def frequency_band(self) -> FrequencyBand: Returns: FrequencyBand """ - result = self._get_property(FREQUENCY_BAND, str) - if result is None: - raise ps.STACError(f"Property {FREQUENCY_BAND} does not exist") - return FrequencyBand(result) + return get_required( + map_opt(lambda x: FrequencyBand(x), self._get_property(FREQUENCY_BAND, str)), self, + FREQUENCY_BAND) @frequency_band.setter def frequency_band(self, v: FrequencyBand) -> None: @@ -200,10 +173,12 @@ def polarizations(self) -> List[Polarization]: Returns: List[Polarization] """ - result = self._get_property(POLARIZATIONS, List[str]) - if result is None: - raise ps.STACError(f"Property {POLARIZATIONS} does not exist") - return [Polarization(v) for v in result] + return get_required( + map_opt(lambda values: [Polarization(v) for v in values], + self._get_property(POLARIZATIONS, List[str])), + self, + POLARIZATIONS + ) @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: @@ -218,10 +193,11 @@ def product_type(self) -> str: Returns: str """ - result = self._get_property(PRODUCT_TYPE, str) - if result is None: - raise ps.STACError(f"Property {PRODUCT_TYPE} does not exist") - return result + return get_required( + self._get_property(POLARIZATIONS, str), + self, + POLARIZATIONS + ) @product_type.setter def product_type(self, v: str) -> None: @@ -322,7 +298,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return ''.format(self.item.id) class AssetSarExtension(SarExtension[ps.Asset]): @@ -333,15 +309,38 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return ''.format(self.asset_href) + +class SarExtensionHooks(ExtensionHooks): + schema_uri = SCHEMA_URI + prev_extension_ids: Set[str] = set(['sar']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if version < '0.9': + # Some sar fields became common_metadata + if 'sar:platform' in obj['properties'] and 'platform' not in obj['properties']: + obj['properties']['platform'] = obj['properties']['sar:platform'] + del obj['properties']['sar:platform'] + + if 'sar:instrument' in obj['properties'] and 'instruments' not in obj['properties']: + obj['properties']['instruments'] = [obj['properties']['sar:instrument']] + del obj['properties']['sar:instrument'] + + if 'sar:constellation' in obj['properties'] and 'constellation' not in obj['properties']: + obj['properties']['constellation'] = obj['properties']['sar:constellation'] + del obj['properties']['sar:constellation'] + + super().migrate(obj, version, info) def sar_ext(obj: T) -> SarExtension[T]: if isinstance(obj, ps.Item): - return ItemSarExtension(obj) + return cast(SarExtension[T], ItemSarExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetSarExtension(obj) + return cast(SarExtension[T], AssetSarExtension(obj)) else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") + SAR_EXTENSION_HOOKS: ExtensionHooks = SarExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 8da213ba4..9e098b63c 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -4,13 +4,14 @@ """ import enum -from typing import Generic, Optional, TypeVar +from pystac.extensions.hooks import ExtensionHooks +from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/sat/v1.0.0/schema.json" @@ -106,10 +107,18 @@ def __repr__(self) -> str: return ''.format(self.asset_href) +class SatExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['sat']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def sat_ext(obj: T) -> SatExtension[T]: if isinstance(obj, ps.Item): - return ItemSatExtension(obj) + return cast(SatExtension[T], ItemSatExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetSatExtension(obj) + return cast(SatExtension[T], AssetSatExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + +SAT_EXTENSION_HOOKS = SatExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 1d4933951..04bcb403f 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,14 +8,15 @@ """ import copy -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast from urllib import parse import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar('T', ps.Collection, ps.Item, contravariant=True) +T = TypeVar('T', ps.Collection, ps.Item) SCHEMA_URI = "https://stac-extensions.github.io/scientific/v1.0.0/schema.json" @@ -198,10 +199,19 @@ def __repr__(self) -> str: return ''.format(self.item.id) +class ScientificExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['scientific']) + stac_object_types: Set[ps.STACObjectType] = set( + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + + def scientific_ext(obj: T) -> ScientificExtension[T]: if isinstance(obj, ps.Collection): - return CollectionScientificExtension(obj) + return cast(ScientificExtension[T], CollectionScientificExtension(obj)) if isinstance(obj, ps.Item): - return ItemScientificExtension(obj) + return cast(ScientificExtension[T], ItemScientificExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + +SCIENTIFIC_EXTENSION_HOOKS = ScientificExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 96daa4f1b..3717ee28c 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -1,11 +1,12 @@ from datetime import datetime as Datetime -from typing import Generic, Optional, TypeVar +from pystac.extensions.hooks import ExtensionHooks +from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension from pystac.utils import datetime_to_str, map_opt, str_to_datetime -T = TypeVar('T', ps.Item, ps.Asset, contravariant=True) +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/timestamps/v1.0.0/schema.json" @@ -102,6 +103,7 @@ def unpublished(self, v: Optional[Datetime]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + class ItemTimestampsExtension(TimestampsExtension[ps.Item]): def __init__(self, item: ps.Item): self.item = item @@ -122,10 +124,19 @@ def __repr__(self) -> str: return ''.format(self.asset_href) +class TimestampsExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['timestamps']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + + def timestamps_ext(obj: T) -> TimestampsExtension[T]: if isinstance(obj, ps.Item): - return ItemTimestampsExtension(obj) + return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) elif isinstance(obj, ps.Asset): - return AssetTimestampsExtension(obj) + return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") \ No newline at end of file + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + + +TIMESTAMPS_EXTENSION_HOOKS = TimestampsExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 40dcf6ccf..5703027ba 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -5,13 +5,17 @@ Note that the version/schema.json does not know about the links. """ -from pystac.extensions.hooks import ExtensionHooks -from typing import List, Optional, cast +from pystac.utils import get_required, map_opt +from typing import Generic, List, Optional, Set, TypeVar, Union, cast import pystac as ps -from pystac import STACError +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks + +T = TypeVar('T', ps.Collection, ps.Item) -VERSION_EXT_SCHEMA = "https://stac-extensions.github.io/version/v1.0.0/schema.json" +# TODO: Modify this to released version with the fix. +SCHEMA_URI = "https://raw.githubusercontent.com/lossyrob/version/fix/rde/schema-id/json-schema/schema.json" # STAC fields - These are unusual for an extension in that they do not have # a prefix. e.g. nothing like "ver:" @@ -27,7 +31,8 @@ MEDIA_TYPE: str = 'application/json' -class VersionItemExt(): +class VersionExtension(Generic[T], PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): """VersionItemExt extends Item to add version and deprecated properties along with links to the latest, predecessor, and successor Items. @@ -41,17 +46,17 @@ class VersionItemExt(): Using VersionItemExt to directly wrap an item will add the 'version' extension ID to the item's stac_extensions. """ - item: ps.Item + obj: ps.STACObject - def __init__(self, an_item: ps.Item) -> None: - self.item = an_item + def __init__(self, obj: ps.STACObject) -> None: + self.obj = obj def apply(self, version: str, deprecated: Optional[bool] = None, - latest: Optional[ps.Item] = None, - predecessor: Optional[ps.Item] = None, - successor: Optional[ps.Item] = None) -> None: + latest: Optional[T] = None, + predecessor: Optional[T] = None, + successor: Optional[T] = None) -> None: """Applies version extension properties to the extended Item. Args: @@ -80,235 +85,87 @@ def version(self) -> str: Returns: str """ - result = self.item.properties.get(VERSION) - if result is None: - raise STACError(f"Item {self.item.id} has version extension but no version property") - return result + return get_required(self._get_property(VERSION, str), self, VERSION) @version.setter def version(self, v: str) -> None: - self.item.properties[VERSION] = v + self._set_property(VERSION, v, pop_if_none=False) @property - def deprecated(self) -> bool: - """Get or sets is the item is deprecated. - - Returns: - bool - """ - return bool(self.item.properties.get(DEPRECATED)) + def deprecated(self) -> Optional[bool]: + """Get or sets is the item is deprecated.""" + return self._get_property(DEPRECATED, bool) @deprecated.setter - def deprecated(self, v: bool) -> None: - if not isinstance(v, bool): - raise ps.STACError(DEPRECATED + ' must be a bool') - self.item.properties[DEPRECATED] = v + def deprecated(self, v: Optional[bool]) -> None: + self._set_property(DEPRECATED, v) @property - def latest(self) -> Optional[ps.Item]: - """Get or sets the most recent item. - - Returns: - Item or None - """ - result = next(iter(self.item.get_stac_objects(LATEST)), None) - if result is None: - return None - return cast(ps.Item, result) + def latest(self) -> Optional[T]: + """Get or sets the most recent version.""" + return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(LATEST)), None)) @latest.setter - def latest(self, source_item: ps.Item) -> None: - self.item.clear_links(LATEST) - if source_item: - self.item.add_link(ps.Link(LATEST, source_item, MEDIA_TYPE)) + def latest(self, item: Optional[T]) -> None: + self.obj.clear_links(LATEST) + if item is not None: + self.obj.add_link(ps.Link(LATEST, item, MEDIA_TYPE)) @property - def predecessor(self) -> Optional[ps.Item]: - """Get or sets the previous item. - - Returns: - Item or None - """ - result = next(iter(self.item.get_stac_objects(PREDECESSOR)), None) - if result is None: - return None - return cast(ps.Item, result) + def predecessor(self) -> Optional[T]: + """Get or sets the previous item.""" + return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(PREDECESSOR)), + None)) @predecessor.setter - def predecessor(self, source_item: ps.Item) -> None: - self.item.clear_links(PREDECESSOR) - if source_item: - self.item.add_link(ps.Link(PREDECESSOR, source_item, MEDIA_TYPE)) + def predecessor(self, item: Optional[T]) -> None: + self.obj.clear_links(PREDECESSOR) + if item is not None: + self.obj.add_link(ps.Link(PREDECESSOR, item, MEDIA_TYPE)) @property - def successor(self) -> Optional[ps.Item]: - """Get or sets the next item. - - Returns: - Item or None - """ - result = next(iter(self.item.get_stac_objects(SUCCESSOR)), None) - if result is None: - return None - return cast(ps.Item, result) + def successor(self) -> Optional[T]: + """Get or sets the next item.""" + return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(SUCCESSOR)), None)) @successor.setter - def successor(self, source_item: ps.Item) -> None: - self.item.clear_links(SUCCESSOR) - if source_item: - self.item.add_link(ps.Link(SUCCESSOR, source_item, MEDIA_TYPE)) - - @classmethod - def from_item(cls, an_item: ps.Item) -> "VersionItemExt": - return cls(an_item) + def successor(self, item: Optional[T]) -> None: + self.obj.clear_links(SUCCESSOR) + if item is not None: + self.obj.add_link(ps.Link(SUCCESSOR, item, MEDIA_TYPE)) @classmethod - def _object_links(cls) -> List[str]: - return [LATEST, PREDECESSOR, SUCCESSOR] - - -class VersionCollectionExt(): - """VersionCollectionExt extends Collection to add version and deprecated properties - along with links to the latest, predecessor, and successor Collections. - - Args: - a_collection (Collection): The collection to be extended. + def get_schema_uri(cls) -> str: + return SCHEMA_URI - Attributes: - collection (Collection): The collection that is being extended. - Note: - Using VersionCollectionExt to directly wrap a collection will add the - 'version' extension ID to the collections's stac_extensions. - """ - collection: ps.Collection +class CollectionVersionExtension(VersionExtension[ps.Collection]): + def __init__(self, collection: ps.Collection): + self.collection = collection + self.properties = collection.extra_fields + self.links = collection.links + super().__init__(self.collection) - def __init__(self, a_collection: ps.Collection) -> None: - self.collection = a_collection + def __repr__(self) -> str: + return ''.format(self.collection.id) - @property - def version(self) -> str: - """Get or sets a version string of the collection. - Returns: - str - """ - result = self.collection.extra_fields.get(VERSION) - if result is None: - raise STACError(f"Collection {self.collection.id} does not have property {VERSION}") - return result +class ItemVersionExtension(VersionExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties + self.links = item.links + super().__init__(self.item) - @version.setter - def version(self, v: str) -> None: - self.collection.extra_fields[VERSION] = v - - @property - def deprecated(self) -> bool: - """Get or sets is the collection is deprecated. - - Returns: - bool - """ - return bool(self.collection.extra_fields.get(DEPRECATED)) - - @deprecated.setter - def deprecated(self, v: bool) -> None: - if not isinstance(v, bool): - raise ps.STACError(DEPRECATED + ' must be a bool') - self.collection.extra_fields[DEPRECATED] = v - - @property - def latest(self) -> Optional[ps.Collection]: - """Get or sets the most recent collection. - - Returns: - Collection or None - """ - result = next(iter(self.collection.get_stac_objects(LATEST)), None) - if result is None: - return None - return cast(ps.Collection, result) - - @latest.setter - def latest(self, source_collection: ps.Collection) -> None: - self.collection.clear_links(LATEST) - if source_collection: - self.collection.add_link(ps.Link(LATEST, source_collection, MEDIA_TYPE)) - - @property - def predecessor(self) -> Optional[ps.Collection]: - """Get or sets the previous collection. - - Returns: - Collection or None - """ - result = next(iter(self.collection.get_stac_objects(PREDECESSOR)), None) - if result is None: - return None - return cast(ps.Collection, result) - - @predecessor.setter - def predecessor(self, source_collection: ps.Collection) -> None: - self.collection.clear_links(PREDECESSOR) - if source_collection: - self.collection.add_link(ps.Link(PREDECESSOR, source_collection, MEDIA_TYPE)) - - @property - def successor(self) -> Optional[ps.Collection]: - """Get or sets the next collection. - - Returns: - Collection or None - """ - result = next(iter(self.collection.get_stac_objects(SUCCESSOR)), None) - if result is None: - return None - return cast(ps.Collection, result) - - @successor.setter - def successor(self, source_collection: ps.Collection) -> None: - self.collection.clear_links(SUCCESSOR) - if source_collection: - self.collection.add_link(ps.Link(SUCCESSOR, source_collection, MEDIA_TYPE)) - - @classmethod - def from_collection(cls, a_collection: ps.Collection) -> "VersionCollectionExt": - return cls(a_collection) - - @classmethod - def _object_links(cls) -> List[str]: - return [LATEST, PREDECESSOR, SUCCESSOR] - - def apply(self, - version: str, - deprecated: Optional[bool] = None, - latest: Optional[ps.Collection] = None, - predecessor: Optional[ps.Collection] = None, - successor: Optional[ps.Collection] = None) -> None: - """Applies version extension properties to the extended Collection. - - Args: - version (str): The version string for the collection. - deprecated (bool): Optional flag set to True if an Collection is - deprecated with the potential to be removed. Defaults to false - if not present. - latest (Collection): Collection with the latest (e.g., current) version. - predecessor (Collection): Collection with the previous version. - successor (Collection): Collection with the next most recent version. - """ - self.version = version - if deprecated is not None: - self.deprecated = deprecated - if latest: - self.latest = latest - if predecessor: - self.predecessor = predecessor - if successor: - self.successor = successor + def __repr__(self) -> str: + return ''.format(self.item.id) class VersionExtensionHooks(ExtensionHooks): - schema_uri = VERSION_EXT_SCHEMA + schema_uri = SCHEMA_URI + prev_extension_ids: Set[str] = set(['version']) + stac_object_types: Set[ps.STACObjectType] = set( + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: if isinstance(so, ps.Collection) or isinstance(so, ps.Item): @@ -316,4 +173,13 @@ def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: return None +def version_ext(obj: T) -> VersionExtension[T]: + if isinstance(obj, ps.Collection): + return cast(VersionExtension[T], CollectionVersionExtension(obj)) + if isinstance(obj, ps.Item): + return cast(VersionExtension[T], ItemVersionExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + + VERSION_EXTENSION_HOOKS: ExtensionHooks = VersionExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 5c227bbe5..a4ec6a8de 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -1,11 +1,20 @@ -from typing import List, Optional +from pystac.extensions.hooks import ExtensionHooks +from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension + +T = TypeVar('T', ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/view/v1.0.0/schema.json" +OFF_NADIR_PROP = 'view:off_nadir' +INCIDENCE_ANGLE_PROP = 'view:incidence_angle' +AZIMUTH_PROP = 'view:azimuth' +SUN_AZIMUTH_PROP = 'view:sun_azimuth' +SUN_ELEVATION_PROP = 'view:sun_elevation' -class ViewItemExt(): +class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """ViewItemExt is the extension of the Item in the View Geometry Extension. View Geometry adds metadata related to angles of sensors and other radiance angles that affect the view of resulting data. It will often be combined with other extensions that @@ -21,9 +30,6 @@ class ViewItemExt(): Using ViewItemExt to directly wrap an item will add the 'view' extension ID to the item's stac_extensions. """ - def __init__(self, item: ps.Item) -> None: - self.item = item - def apply(self, off_nadir: Optional[float] = None, incidence_angle: Optional[float] = None, @@ -46,21 +52,11 @@ def apply(self, sun_elevation (float): Sun elevation angle. The angle from the tangent of the scene center point to the sun. Measured from the horizon in degrees (0-90). """ - if (off_nadir is None and incidence_angle is None and azimuth is None - and sun_azimuth is None and sun_elevation is None): - raise ps.STACError( - 'Must provide at least one of: off_nadir, incidence_angle, azimuth, sun_azimuth, sun_elevation' # noqa: E501 - ) - if off_nadir: - self.off_nadir = off_nadir - if incidence_angle: - self.incidence_angle = incidence_angle - if azimuth: - self.azimuth = azimuth - if sun_azimuth: - self.sun_azimuth = sun_azimuth - if sun_elevation: - self.sun_elevation = sun_elevation + self.off_nadir = off_nadir + self.incidence_angle = incidence_angle + self.azimuth = azimuth + self.sun_azimuth = sun_azimuth + self.sun_elevation = sun_elevation @property def off_nadir(self) -> Optional[float]: @@ -70,33 +66,11 @@ def off_nadir(self) -> Optional[float]: Returns: float """ - return self.get_off_nadir() + return self._get_property(OFF_NADIR_PROP, float) @off_nadir.setter def off_nadir(self, v: Optional[float]) -> None: - self.set_off_nadir(v) - - def get_off_nadir(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset off_nadir. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - float - """ - if asset is None or 'view:off_nadir' not in asset.properties: - return self.item.properties.get('view:off_nadir') - else: - return asset.properties.get('view:off_nadir') - - def set_off_nadir(self, off_nadir: Optional[float], asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset off_nadir. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('view:off_nadir', off_nadir, asset) + self._set_property(OFF_NADIR_PROP, v) @property def incidence_angle(self) -> Optional[float]: @@ -107,35 +81,11 @@ def incidence_angle(self) -> Optional[float]: Returns: float """ - return self.get_incidence_angle() + return self._get_property(INCIDENCE_ANGLE_PROP, float) @incidence_angle.setter def incidence_angle(self, v: Optional[float]) -> None: - self.set_incidence_angle(v) - - def get_incidence_angle(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset incidence_angle. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - float - """ - if asset is None or 'view:incidence_angle' not in asset.properties: - return self.item.properties.get('view:incidence_angle') - else: - return asset.properties.get('view:incidence_angle') - - def set_incidence_angle(self, - incidence_angle: Optional[float], - asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset incidence_angle. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('view:incidence_angle', incidence_angle, asset) + self._set_property(INCIDENCE_ANGLE_PROP, v) @property def azimuth(self) -> Optional[float]: @@ -146,33 +96,11 @@ def azimuth(self) -> Optional[float]: Returns: float """ - return self.get_azimuth() + return self._get_property(AZIMUTH_PROP, float) @azimuth.setter def azimuth(self, v: Optional[float]) -> None: - self.set_azimuth(v) - - def get_azimuth(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset azimuth. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - float - """ - if asset is None or 'view:azimuth' not in asset.properties: - return self.item.properties.get('view:azimuth') - else: - return asset.properties.get('view:azimuth') - - def set_azimuth(self, azimuth: Optional[float], asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset azimuth. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('view:azimuth', azimuth, asset) + self._set_property(AZIMUTH_PROP, v) @property def sun_azimuth(self) -> Optional[float]: @@ -182,35 +110,11 @@ def sun_azimuth(self) -> Optional[float]: Returns: float """ - return self.get_sun_azimuth() + return self._get_property(SUN_AZIMUTH_PROP, float) @sun_azimuth.setter def sun_azimuth(self, v: Optional[float]) -> None: - self.set_sun_azimuth(v) - - def get_sun_azimuth(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset sun_azimuth. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value - - Returns: - float - """ - if asset is None or 'view:sun_azimuth' not in asset.properties: - return self.item.properties.get('view:sun_azimuth') - else: - return asset.properties.get('view:sun_azimuth') - - def set_sun_azimuth(self, - sun_azimuth: Optional[float], - asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset sun_azimuth. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('view:sun_azimuth', sun_azimuth, asset) + self._set_property(SUN_AZIMUTH_PROP, v) @property def sun_elevation(self) -> Optional[float]: @@ -220,40 +124,47 @@ def sun_elevation(self) -> Optional[float]: Returns: float """ - return self.get_sun_elevation() + return self._get_property(SUN_ELEVATION_PROP, float) @sun_elevation.setter def sun_elevation(self, v: Optional[float]) -> None: - self.set_sun_elevation(v) + self._set_property(SUN_ELEVATION_PROP, v) + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI - def get_sun_elevation(self, asset: Optional[ps.Asset] = None) -> Optional[float]: - """Gets an Item or an Asset sun_elevation. - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value +class ItemViewExtension(ViewExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties - Returns: - float - """ - if asset is None or 'view:sun_elevation' not in asset.properties: - return self.item.properties.get('view:sun_elevation') - else: - return asset.properties.get('view:sun_elevation') - - def set_sun_elevation(self, - sun_elevation: Optional[float], - asset: Optional[ps.Asset] = None) -> None: - """Set an Item or an Asset sun_elevation. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - self._set_property('view:sun_elevation', sun_elevation, asset) + def __repr__(self) -> str: + return ''.format(self.item.id) - @classmethod - def _object_links(cls) -> List[str]: - return [] - @classmethod - def from_item(cls, item: ps.Item) -> "ViewItemExt": - return cls(item) +class AssetViewExtension(ViewExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + +class ViewExtensionHooks(ExtensionHooks): + schema_uri = SCHEMA_URI + prev_extension_ids: Set[str] = set(['view']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + +def view_ext(obj: T) -> ViewExtension[T]: + if isinstance(obj, ps.Item): + return cast(ViewExtension[T], ItemViewExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(ViewExtension[T], AssetViewExtension(obj)) + else: + raise ExtensionException(f"View extension does not apply to type {type(obj)}") + +VIEW_EXTENSION_HOOKS = ViewExtensionHooks() \ No newline at end of file diff --git a/pystac/item.py b/pystac/item.py index a6ac91d32..5ea7d1039 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -654,7 +654,7 @@ def __init__(self, def __repr__(self) -> str: return ''.format(self.id) - def set_self_href(self, href: str) -> None: + def set_self_href(self, href: Optional[str]) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. diff --git a/pystac/link.py b/pystac/link.py index bc36085b3..ce1195e6f 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -69,11 +69,11 @@ def __init__(self, self.properties = properties self.owner: Optional["STACObject_Type"] = None - def set_owner(self, owner: "STACObject_Type") -> "Link": + def set_owner(self, owner: Optional["STACObject_Type"]) -> "Link": """Sets the owner of this link. Args: - owner (STACObject): The owner of this link. + owner: The owner of this link. Pass None to clear. """ self.owner = owner return self diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index c00e66b2b..231e1bf4b 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -1,6 +1,6 @@ from enum import Enum from functools import total_ordering -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union, cast +from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING, Union, cast import pystac as ps from pystac.version import STACVersion @@ -145,7 +145,7 @@ class STACJSONDescription: object implements """ def __init__(self, object_type: "STACObjectType_Type", version_range: STACVersionRange, - extensions: List[str]) -> None: + extensions: Set[str]) -> None: self.object_type = object_type self.version_range = version_range self.extensions = extensions @@ -374,4 +374,4 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: json_dict['links'])): version_range.set_min(STACVersionID('0.7.0')) - return STACJSONDescription(object_type, version_range, stac_extensions) + return STACJSONDescription(object_type, version_range, set(stac_extensions)) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 0025c7cee..12b714f12 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -226,16 +226,6 @@ def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, return None -def _migrate_checksum(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - return None - - -def _migrate_datacube(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - return None - - def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: if version < '0.9': @@ -251,16 +241,6 @@ def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, return None -def _migrate_scientific(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - return None - - -def _migrate_single_file_stac(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - return None - - def _get_object_migrations( ) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]]: return { diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 75e26bb52..ef36d27ae 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -164,7 +164,7 @@ def get_self_href(self) -> Optional[str]: else: return None - def set_self_href(self, href: str) -> None: + def set_self_href(self, href: Optional[str]) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. diff --git a/pystac/utils.py b/pystac/utils.py index 27f1f9c9e..0daefeaae 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -1,5 +1,6 @@ import os import posixpath +from pystac.errors import RequiredPropertyMissing from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from urllib.parse import (urlparse, ParseResult as URLParseResult) from datetime import datetime, timezone @@ -230,3 +231,12 @@ def get_opt(option: Optional[T]) -> T: if option is None: raise ValueError("Cannot get value from None") return option + +def get_required(option: Optional[T], obj: Union[str, Any], prop: str) -> T: + """ Retrieves an optional value that comes from a required property. + If the option is None, throws an RequiredPropertyError with + the given obj and property + """ + if option is None: + raise RequiredPropertyMissing(obj, prop) + return option \ No newline at end of file diff --git a/tests/data-files/view/example-landsat8.json b/tests/data-files/view/example-landsat8.json index 9e2d20d44..371bb523e 100644 --- a/tests/data-files/view/example-landsat8.json +++ b/tests/data-files/view/example-landsat8.json @@ -1,18 +1,22 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [ - "sat", - "view" - ], - "id": "LC08_L1TP_107018_20181001", - "collection": "landsat-8-l1", "type": "Feature", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], + "stac_version": "1.0.0-rc.2", + "id": "LC08_L1TP_107018_20181001", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "view:sun_azimuth": 168.8989761, + "view:sun_elevation": 26.32596431, + "view:off_nadir": 0, + "view:incidence_angle": 0, + "view:azimuth": 23.4, + "platform": "landsat-8", + "instruments": [ + "oli", + "tirs" + ], + "constellation": "landsat", + "sat:orbit_state": "ascending" + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -40,21 +44,12 @@ ] ] }, - "properties": { - "datetime": "2018-10-01T01:08:32.033Z", - "view:sun_azimuth": 168.8989761, - "view:sun_elevation": 26.32596431, - "view:off_nadir": 0, - "view:incidence_angle": 0, - "view:azimuth": 23.4, - "platform": "landsat-8", - "instruments": [ - "oli", - "tirs" - ], - "constellation": "landsat", - "sat:orbit_state": "ascending" - }, + "links": [ + { + "rel": "collection", + "href": "http://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" + } + ], "assets": { "blue": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", @@ -78,8 +73,8 @@ }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", - "title": "Thumbnail image", - "type": "image/jpeg" + "type": "image/jpeg", + "title": "Thumbnail image" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", @@ -87,10 +82,15 @@ "title": "HTML index page" } }, - "links": [ - { - "rel": "collection", - "href": "http://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" - } - ] -} + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "view", + "https://schemas.stacspec.org/v1.0.0-beta.2/extensions/sat/json-schema/schema.json" + ], + "collection": "landsat-8-l1" +} \ No newline at end of file diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py new file mode 100644 index 000000000..e4672c4c8 --- /dev/null +++ b/tests/extensions/test_custom.py @@ -0,0 +1,120 @@ +"""Tests creating a custom extension""" + +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast +import unittest + +import pystac as ps +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from pystac.extensions import ExtensionError +from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks + + +T = TypeVar('T', ps.Catalog, ps.Collection, ps.Item, ps.Asset) + +SCHEMA_URI = "https://example.com/v2.0/custom-schema.json" + +TEST_PROP = "test:prop" +TEST_LINK_REL = "test-link" + + +class CustomExtension(Generic[T], PropertiesExtension, + ExtensionManagementMixin[Union[ps.Catalog, ps.Collection, ps.Item]]): + def __init__(self, obj: Optional[ps.STACObject]) -> None: + self.obj = obj + + def apply(self, test_prop: Optional[str]) -> None: + self.test_prop = test_prop + + @property + def test_prop(self) -> Optional[str]: + self._get_property(TEST_PROP, str) + + @test_prop.setter + def test_prop(self, v: Optional[str]) -> None: + self._set_property(TEST_PROP, v) + + def add_link(self, target: ps.STACObject) -> None: + if self.obj is not None: + self.obj.add_link(ps.Link(TEST_LINK_REL, target)) + else: + raise ExtensionError(f'{self} does not support links') + + @classmethod + def get_schema_uri(cls) -> str: + return super().get_schema_uri() + + +class CatalogCustomExtension(CustomExtension[ps.Catalog]): + def __init__(self, catalog: ps.Catalog) -> None: + self.catalog = catalog + self.properties = catalog.extra_fields + super().__init__(catalog) + + +class CollectionCustomExtension(CustomExtension[ps.Collection]): + def __init__(self, collection: ps.Collection) -> None: + self.catalog = collection + self.properties = collection.extra_fields + super().__init__(collection) + + +class ItemCustomExtension(CustomExtension[ps.Item]): + def __init__(self, item: ps.Item) -> None: + self.catalog = item + self.properties = item.properties + super().__init__(item) + + +class AssetCustomExtension(CustomExtension[ps.Asset]): + def __init__(self, asset: ps.Asset) -> None: + self.catalog = asset + self.properties = asset.properties + if asset.owner: + if isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + elif isinstance(asset.owner, ps.Collection): + self.additional_read_properties = [asset.owner.extra_fields] + super().__init__(None) + + +class CustomExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['custom', 'https://example.com/v1.0/custom-schema.json']) + stac_object_types: Set[ps.STACObjectType] = set( + [ps.STACObjectType.CATALOG, ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + + def get_object_links(self, obj: ps.STACObject) -> Optional[List[str]]: + return [TEST_LINK_REL] + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + if version < "1.0.0-rc2" and info.object_type == ps.STACObjectType.ITEM: + if 'test:old-prop-name' in obj['properties']: + obj['properties'][TEST_PROP] = obj['properties']['test:old-prop-name'] + super().migrate(obj, version, info) + + +def custom_ext(obj: T) -> CustomExtension[T]: + if isinstance(obj, ps.Asset): + return cast(CustomExtension[T], AssetCustomExtension(obj)) + if isinstance(obj, ps.Item): + return cast(CustomExtension[T], ItemCustomExtension(obj)) + if isinstance(obj, ps.Collection): + return cast(CustomExtension[T], CollectionCustomExtension(obj)) + if isinstance(obj, ps.Catalog): + return cast(CustomExtension[T], CatalogCustomExtension(obj)) + + raise ExtensionError(f'Custom extension does not apply to {type(obj)}') + + +class CustomExtensionTest(unittest.TestCase): + def setUp(self): + ps.EXTENSION_HOOKS.add_extension_hooks(CustomExtensionHooks()) + + def tearDown(self) -> None: + ps.EXTENSION_HOOKS.remove_extension_hooks(SCHEMA_URI) + + # TODO: Test custom extensions and extension hooks + + def test_migrates(self): + pass \ No newline at end of file diff --git a/tests/extensions/test_extensions.py b/tests/extensions/test_extensions.py deleted file mode 100644 index 5aa8ffe65..000000000 --- a/tests/extensions/test_extensions.py +++ /dev/null @@ -1,100 +0,0 @@ -import unittest - -import pystac -from pystac import (Catalog, Collection, Item) -from pystac.extensions.base import (CatalogExtension, CollectionExtension, ItemExtension, - ExtensionDefinition, ExtendedObject, ExtensionError) -from pystac.stac_object import ExtensionIndex - -from tests.utils import TestCases - - -class TestCatalogExt(CatalogExtension): - def __init__(self, cat): - self.cat = cat - - @property - def test_id(self): - return self.cat.id - - @classmethod - def from_catalog(cls, cat): - return TestCatalogExt(cat) - - @classmethod - def _object_links(cls): - return [] - - -class TestCollectionExt(CollectionExtension): - def __init__(self, col): - self.col = col - - @property - def xmin(self): - return self.col.extent.spatial.bboxes[0][0] - - @classmethod - def from_collection(cls, col): - return TestCollectionExt(col) - - @classmethod - def _object_links(cls): - return [] - - -class TestItemExt(ItemExtension): - def __init__(self, item): - self.item = item - - @property - def asset_keys(self): - return set(self.item.assets) - - @classmethod - def from_item(cls, item): - return TestItemExt(item) - - @classmethod - def _object_links(cls): - return [] - - -class ExtensionsTest(unittest.TestCase): - def test_can_add_custom_extension(self): - prev_extensions = ps.EXTENSION_HOOKS.get_registered_extensions() - - ps.EXTENSION_HOOKS.add_extension_hooks( - ExtensionDefinition("test", [ - ExtendedObject(Catalog, TestCatalogExt), - ExtendedObject(Collection, TestCollectionExt), - ExtendedObject(Item, TestItemExt) - ])) - - try: - cat = TestCases.test_case_2() - col = cat.get_child('1a8c1632-fa91-4a62-b33e-3a87c2ebdf16') - item = next(cat.get_all_items()) - - cat.ext.enable("test") - col.ext.enable("test") - item.ext.enable("test") - - self.assertEqual(cat.ext.test.test_id, cat.id) - self.assertEqual(col.ext.test.xmin, col.extent.spatial.bboxes[0][0]) - self.assertEqual(item.ext.test.asset_keys, set(item.assets)) - - finally: - ps.EXTENSION_HOOKS.remove_extension("test") - - self.assertFalse(ps.EXTENSION_HOOKS.is_registered_extension("test")) - self.assertEqual(ps.EXTENSION_HOOKS.get_registered_extensions(), prev_extensions) - - def test_getattribute_overload(self): - catalog = Catalog(id='test', description='test') - self.assertEqual(ExtensionIndex.__name__, 'ExtensionIndex') - self.assertRaises(ExtensionError, catalog.ext.__getattr__, 'foo') - self.assertRaises(ExtensionError, catalog.ext.__getattr__, 'eo') - catalog.ext.enable('single-file-stac') - self.assertTrue(catalog.ext.__getattr__('single-file-stac'), - ps.extensions.single_file_stac.SingleFileSTACCatalogExt) diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 06c8fd4c2..adc917e02 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -37,7 +37,7 @@ def make_item() -> ps.Item: return item -class ScientificItemExtTest(unittest.TestCase): +class ItemScientificExtensionTest(unittest.TestCase): def setUp(self): super().setUp() self.item = make_item() @@ -186,7 +186,7 @@ def make_collection() -> ps.Collection: return collection -class ScientificCollectionExtTest(unittest.TestCase): +class CollectionScientificExtensionTest(unittest.TestCase): def setUp(self): super().setUp() self.collection = make_collection() diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 8f31e1cd2..02923ff84 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -1,10 +1,12 @@ """Tests for pystac.extensions.version.""" import datetime +from pystac.validation import STACValidationError import unittest -import pystac +import pystac as ps from pystac.extensions import version +from pystac.extensions.version import version_ext, VersionExtension from tests.utils import TestCases URL_TEMPLATE: str = 'http://example.com/catalog/%s.json' @@ -18,53 +20,51 @@ def make_item(year: int) -> ps.Item: item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % year) - item.ext.enable(ps._OldExtensionShortIDs.VERSION) + VersionExtension.add_to(item) return item -class VersionItemExtTest(unittest.TestCase): +class ItemVersionExtensionTest(unittest.TestCase): version: str = '1.2.3' def setUp(self): super().setUp() self.item = make_item(2011) - self.item.ext.enable(ps._OldExtensionShortIDs.VERSION) - def test_stac_extensions(self): - self.assertEqual([ps._OldExtensionShortIDs.VERSION], self.item.stac_extensions) + self.assertTrue(VersionExtension.has_extension(self.item)) def test_add_version(self): - self.item.ext.version.apply(self.version) - self.assertEqual(self.version, self.item.ext.version.version) + version_ext(self.item).apply(self.version) + self.assertEqual(self.version, version_ext(self.item).version) self.assertNotIn(version.DEPRECATED, self.item.properties) - self.assertFalse(self.item.ext.version.deprecated) + self.assertFalse(version_ext(self.item).deprecated) self.item.validate() def test_version_in_properties(self): - self.item.ext.version.apply(self.version, deprecated=True) + version_ext(self.item).apply(self.version, deprecated=True) self.assertIn(version.VERSION, self.item.properties) self.assertIn(version.DEPRECATED, self.item.properties) self.item.validate() def test_add_not_deprecated_version(self): - self.item.ext.version.apply(self.version, deprecated=False) + version_ext(self.item).apply(self.version, deprecated=False) self.assertIn(version.DEPRECATED, self.item.properties) - self.assertFalse(self.item.ext.version.deprecated) + self.assertFalse(version_ext(self.item).deprecated) self.item.validate() def test_add_deprecated_version(self): - self.item.ext.version.apply(self.version, deprecated=True) + version_ext(self.item).apply(self.version, deprecated=True) self.assertIn(version.DEPRECATED, self.item.properties) - self.assertTrue(self.item.ext.version.deprecated) + self.assertTrue(version_ext(self.item).deprecated) self.item.validate() def test_latest(self): year = 2013 latest = make_item(year) - self.item.ext.version.apply(self.version, latest=latest) - latest_result = self.item.ext.version.latest + version_ext(self.item).apply(self.version, latest=latest) + latest_result = version_ext(self.item).latest self.assertIs(latest, latest_result) expected_href = URL_TEMPLATE % year @@ -75,8 +75,8 @@ def test_latest(self): def test_predecessor(self): year = 2010 predecessor = make_item(year) - self.item.ext.version.apply(self.version, predecessor=predecessor) - predecessor_result = self.item.ext.version.predecessor + version_ext(self.item).apply(self.version, predecessor=predecessor) + predecessor_result = version_ext(self.item).predecessor self.assertIs(predecessor, predecessor_result) expected_href = URL_TEMPLATE % year @@ -87,8 +87,8 @@ def test_predecessor(self): def test_successor(self): year = 2012 successor = make_item(year) - self.item.ext.version.apply(self.version, successor=successor) - successor_result = self.item.ext.version.successor + version_ext(self.item).apply(self.version, successor=successor) + successor_result = version_ext(self.item).successor self.assertIs(successor, successor_result) expected_href = URL_TEMPLATE % year @@ -97,7 +97,7 @@ def test_successor(self): self.item.validate() def test_fail_validate(self): - with self.assertRaises(ps.validation.STACValidationError): + with self.assertRaises(STACValidationError): self.item.validate() def test_all_links(self): @@ -105,7 +105,7 @@ def test_all_links(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - self.item.ext.version.apply(self.version, deprecated, latest, predecessor, successor) + version_ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) self.item.validate() def test_full_copy(self): @@ -115,13 +115,16 @@ def test_full_copy(self): item1 = cat.get_item('area-1-1-imagery', recursive=True) item2 = cat.get_item('area-2-2-imagery', recursive=True) + assert item1 is not None + assert item2 is not None + # Enable the version extension on each, and link them # as if they are different versions of the same Item - item1.ext.enable(ps._OldExtensionShortIDs.VERSION) - item2.ext.enable(ps._OldExtensionShortIDs.VERSION) + VersionExtension.add_to(item1) + VersionExtension.add_to(item2) - item1.ext.version.apply(version='2.0', predecessor=item2) - item2.ext.version.apply(version='1.0', successor=item1, latest=item1) + version_ext(item1).apply(version='2.0', predecessor=item2) + version_ext(item2).apply(version='1.0', successor=item1, latest=item1) # Make a full copy of the catalog cat_copy = cat.full_copy() @@ -145,34 +148,34 @@ def test_setting_none_clears_link(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - self.item.ext.version.apply(self.version, deprecated, latest, predecessor, successor) + version_ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) - self.item.ext.version.latest = None + version_ext(self.item).latest = None links = self.item.get_links(version.LATEST) self.assertEqual(0, len(links)) - self.assertIsNone(self.item.ext.version.latest) + self.assertIsNone(version_ext(self.item).latest) - self.item.ext.version.predecessor = None + version_ext(self.item).predecessor = None links = self.item.get_links(version.PREDECESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(self.item.ext.version.predecessor) + self.assertIsNone(version_ext(self.item).predecessor) - self.item.ext.version.successor = None + version_ext(self.item).successor = None links = self.item.get_links(version.SUCCESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(self.item.ext.version.successor) + self.assertIsNone(version_ext(self.item).successor) def test_multiple_link_setting(self): deprecated = False latest1 = make_item(2013) predecessor1 = make_item(2010) successor1 = make_item(2012) - self.item.ext.version.apply(self.version, deprecated, latest1, predecessor1, successor1) + version_ext(self.item).apply(self.version, deprecated, latest1, predecessor1, successor1) year = 2015 latest2 = make_item(year) expected_href = URL_TEMPLATE % year - self.item.ext.version.latest = latest2 + version_ext(self.item).latest = latest2 links = self.item.get_links(version.LATEST) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -180,7 +183,7 @@ def test_multiple_link_setting(self): year = 2009 predecessor2 = make_item(year) expected_href = URL_TEMPLATE % year - self.item.ext.version.predecessor = predecessor2 + version_ext(self.item).predecessor = predecessor2 links = self.item.get_links(version.PREDECESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -188,7 +191,7 @@ def test_multiple_link_setting(self): year = 2014 successor2 = make_item(year) expected_href = URL_TEMPLATE % year - self.item.ext.version.successor = successor2 + version_ext(self.item).successor = successor2 links = self.item.get_links(version.SUCCESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -198,7 +201,7 @@ def make_collection(year: int) -> ps.Collection: asset_id = f'my/collection/of/things/{year}' start = datetime.datetime(2014, 8, 10) end = datetime.datetime(year, 1, 3, 4, 5) - bboxes = [[-180, -90, 180, 90]] + bboxes = [[-180.0, -90.0, 180.0, 90.0]] spatial_extent = ps.SpatialExtent(bboxes) temporal_extent = ps.TemporalExtent([[start, end]]) extent = ps.Extent(spatial_extent, temporal_extent) @@ -206,12 +209,12 @@ def make_collection(year: int) -> ps.Collection: collection = ps.Collection(asset_id, 'desc', extent) collection.set_self_href(URL_TEMPLATE % year) - collection.ext.enable(ps._OldExtensionShortIDs.VERSION) + VersionExtension.add_to(collection) return collection -class VersionCollectionExtTest(unittest.TestCase): +class CollectionVersionExtensionTest(unittest.TestCase): version: str = '1.2.3' def setUp(self): @@ -219,38 +222,38 @@ def setUp(self): self.collection = make_collection(2011) def test_stac_extensions(self): - self.assertEqual([ps._OldExtensionShortIDs.VERSION], self.collection.stac_extensions) + self.assertTrue(VersionExtension.has_extension(self.collection)) def test_add_version(self): - self.collection.ext.version.apply(self.version) - self.assertEqual(self.version, self.collection.ext.version.version) + version_ext(self.collection).apply(self.version) + self.assertEqual(self.version, version_ext(self.collection).version) self.assertNotIn(version.DEPRECATED, self.collection.extra_fields) - self.assertFalse(self.collection.ext.version.deprecated) + self.assertFalse(version_ext(self.collection).deprecated) self.collection.validate() def test_version_deprecated(self): - self.collection.ext.version.apply(self.version, deprecated=True) + version_ext(self.collection).apply(self.version, deprecated=True) self.assertIn(version.VERSION, self.collection.extra_fields) self.assertIn(version.DEPRECATED, self.collection.extra_fields) self.collection.validate() def test_add_not_deprecated_version(self): - self.collection.ext.version.apply(self.version, deprecated=False) + version_ext(self.collection).apply(self.version, deprecated=False) self.assertIn(version.DEPRECATED, self.collection.extra_fields) - self.assertFalse(self.collection.ext.version.deprecated) + self.assertFalse(version_ext(self.collection).deprecated) self.collection.validate() def test_add_deprecated_version(self): - self.collection.ext.version.apply(self.version, deprecated=True) + version_ext(self.collection).apply(self.version, deprecated=True) self.assertIn(version.DEPRECATED, self.collection.extra_fields) - self.assertTrue(self.collection.ext.version.deprecated) + self.assertTrue(version_ext(self.collection).deprecated) self.collection.validate() def test_latest(self): year = 2013 latest = make_collection(year) - self.collection.ext.version.apply(self.version, latest=latest) - latest_result = self.collection.ext.version.latest + version_ext(self.collection).apply(self.version, latest=latest) + latest_result = version_ext(self.collection).latest self.assertIs(latest, latest_result) expected_href = URL_TEMPLATE % year @@ -261,8 +264,8 @@ def test_latest(self): def test_predecessor(self): year = 2010 predecessor = make_collection(year) - self.collection.ext.version.apply(self.version, predecessor=predecessor) - predecessor_result = self.collection.ext.version.predecessor + version_ext(self.collection).apply(self.version, predecessor=predecessor) + predecessor_result = version_ext(self.collection).predecessor self.assertIs(predecessor, predecessor_result) expected_href = URL_TEMPLATE % year @@ -273,8 +276,8 @@ def test_predecessor(self): def test_successor(self): year = 2012 successor = make_collection(year) - self.collection.ext.version.apply(self.version, successor=successor) - successor_result = self.collection.ext.version.successor + version_ext(self.collection).apply(self.version, successor=successor) + successor_result = version_ext(self.collection).successor self.assertIs(successor, successor_result) expected_href = URL_TEMPLATE % year @@ -283,7 +286,7 @@ def test_successor(self): self.collection.validate() def test_fail_validate(self): - with self.assertRaises(ps.validation.STACValidationError): + with self.assertRaises(STACValidationError): self.collection.validate() def test_validate_all(self): @@ -291,7 +294,7 @@ def test_validate_all(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - self.collection.ext.version.apply(self.version, deprecated, latest, predecessor, successor) + version_ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) self.collection.validate() def test_full_copy(self): @@ -299,15 +302,17 @@ def test_full_copy(self): # Fetch two collections from the catalog col1 = cat.get_child('area-1-1', recursive=True) + assert isinstance(col1, ps.Collection) col2 = cat.get_child('area-2-2', recursive=True) + assert isinstance(col2, ps.Collection) # Enable the version extension on each, and link them # as if they are different versions of the same Collection - col1.ext.enable(ps._OldExtensionShortIDs.VERSION) - col2.ext.enable(ps._OldExtensionShortIDs.VERSION) + VersionExtension.add_to(col1) + VersionExtension.add_to(col2) - col1.ext.version.apply(version='2.0', predecessor=col2) - col2.ext.version.apply(version='1.0', successor=col1, latest=col1) + version_ext(col1).apply(version='2.0', predecessor=col2) + version_ext(col2).apply(version='1.0', successor=col1, latest=col1) # Make a full copy of the catalog cat_copy = cat.full_copy() @@ -331,35 +336,35 @@ def test_setting_none_clears_link(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - self.collection.ext.version.apply(self.version, deprecated, latest, predecessor, successor) + version_ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) - self.collection.ext.version.latest = None + version_ext(self.collection).latest = None links = self.collection.get_links(version.LATEST) self.assertEqual(0, len(links)) - self.assertIsNone(self.collection.ext.version.latest) + self.assertIsNone(version_ext(self.collection).latest) - self.collection.ext.version.predecessor = None + version_ext(self.collection).predecessor = None links = self.collection.get_links(version.PREDECESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(self.collection.ext.version.predecessor) + self.assertIsNone(version_ext(self.collection).predecessor) - self.collection.ext.version.successor = None + version_ext(self.collection).successor = None links = self.collection.get_links(version.SUCCESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(self.collection.ext.version.successor) + self.assertIsNone(version_ext(self.collection).successor) def test_multiple_link_setting(self): deprecated = False latest1 = make_collection(2013) predecessor1 = make_collection(2010) successor1 = make_collection(2012) - self.collection.ext.version.apply(self.version, deprecated, latest1, predecessor1, + version_ext(self.collection).apply(self.version, deprecated, latest1, predecessor1, successor1) year = 2015 latest2 = make_collection(year) expected_href = URL_TEMPLATE % year - self.collection.ext.version.latest = latest2 + version_ext(self.collection).latest = latest2 links = self.collection.get_links(version.LATEST) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -367,7 +372,7 @@ def test_multiple_link_setting(self): year = 2009 predecessor2 = make_collection(year) expected_href = URL_TEMPLATE % year - self.collection.ext.version.predecessor = predecessor2 + version_ext(self.collection).predecessor = predecessor2 links = self.collection.get_links(version.PREDECESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -375,7 +380,7 @@ def test_multiple_link_setting(self): year = 2014 successor2 = make_collection(year) expected_href = URL_TEMPLATE % year - self.collection.ext.version.successor = successor2 + version_ext(self.collection).successor = successor2 links = self.collection.get_links(version.SUCCESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 7f982d0b3..045bbc773 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -1,9 +1,8 @@ import json import unittest -import pystac -from pystac.extensions import ExtensionError -from pystac import (Item, _OldExtensionShortIDs) +import pystac as ps +from pystac.extensions.view import ViewExtension, view_ext from tests.utils import (TestCases, test_to_from_dict) @@ -15,188 +14,171 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, Item, d) + test_to_from_dict(self, ps.Item, d) def test_apply(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.view - - item.ext.enable(_OldExtensionShortIDs.VIEW) - item.ext.view.apply(off_nadir=1.0, - incidence_angle=2.0, - azimuth=3.0, - sun_azimuth=2.0, - sun_elevation=1.0) - - def test_apply_one(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.view - - item.ext.enable(_OldExtensionShortIDs.VIEW) - item.ext.view.apply(off_nadir=1.0) - - @unittest.expectedFailure - def test_apply_none(self): - item = next(TestCases.test_case_2().get_all_items()) - with self.assertRaises(ExtensionError): - item.ext.view - - item.ext.enable(_OldExtensionShortIDs.VIEW) - item.ext.view.apply( - off_nadir=None, - incidence_angle=None, - azimuth=None, - sun_azimuth=None, - sun_elevation=None, - ) + item = next(iter(TestCases.test_case_2().get_all_items())) + self.assertFalse(ViewExtension.has_extension(item)) + + ViewExtension.add_to(item) + view_ext(item).apply(off_nadir=1.0, + incidence_angle=2.0, + azimuth=3.0, + sun_azimuth=4.0, + sun_elevation=5.0) + + self.assertEqual(view_ext(item).off_nadir, 1.0) + self.assertEqual(view_ext(item).incidence_angle, 2.0) + self.assertEqual(view_ext(item).azimuth, 3.0) + self.assertEqual(view_ext(item).sun_azimuth, 4.0) + self.assertEqual(view_ext(item).sun_elevation, 5.0) def test_validate_view(self): - item = ps.read_file(self.example_uri) + item = ps.Item.from_file(self.example_uri) + self.assertTrue(ViewExtension.has_extension(item)) item.validate() def test_off_nadir(self): - view_item = ps.read_file(self.example_uri) + view_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("view:off_nadir", view_item.properties) - view_off_nadir = view_item.ext.view.off_nadir + view_off_nadir = view_ext(view_item).off_nadir self.assertEqual(view_off_nadir, view_item.properties['view:off_nadir']) # Set - view_item.ext.view.off_nadir = view_off_nadir + 10 + view_ext(view_item).off_nadir = view_off_nadir + 10 self.assertEqual(view_off_nadir + 10, view_item.properties['view:off_nadir']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_item.ext.view.get_off_nadir(asset_no_prop), - view_item.ext.view.get_off_nadir()) - self.assertEqual(view_item.ext.view.get_off_nadir(asset_prop), 3.0) + self.assertEqual(view_ext(asset_no_prop).off_nadir, + view_ext(view_item).off_nadir) + self.assertEqual(view_ext(asset_prop).off_nadir, 3.0) # Set to Asset asset_value = 13.0 - view_item.ext.view.set_off_nadir(asset_value, asset_no_prop) - self.assertNotEqual(view_item.ext.view.get_off_nadir(asset_no_prop), - view_item.ext.view.get_off_nadir()) - self.assertEqual(view_item.ext.view.get_off_nadir(asset_no_prop), asset_value) + view_ext(asset_no_prop).off_nadir = asset_value + self.assertNotEqual(view_ext(asset_no_prop).off_nadir, + view_ext(view_item).off_nadir) + self.assertEqual(view_ext(asset_no_prop).off_nadir, asset_value) # Validate view_item.validate() def test_incidence_angle(self): - view_item = ps.read_file(self.example_uri) + view_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("view:incidence_angle", view_item.properties) - view_incidence_angle = view_item.ext.view.incidence_angle + view_incidence_angle = view_ext(view_item).incidence_angle self.assertEqual(view_incidence_angle, view_item.properties['view:incidence_angle']) # Set - view_item.ext.view.incidence_angle = view_incidence_angle + 10 + view_ext(view_item).incidence_angle = view_incidence_angle + 10 self.assertEqual(view_incidence_angle + 10, view_item.properties['view:incidence_angle']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_item.ext.view.get_incidence_angle(asset_no_prop), - view_item.ext.view.get_incidence_angle()) - self.assertEqual(view_item.ext.view.get_incidence_angle(asset_prop), 4.0) + self.assertEqual(view_ext(asset_no_prop).incidence_angle, + view_ext(view_item).incidence_angle) + self.assertEqual(view_ext(asset_prop).incidence_angle, 4.0) # Set to Asset asset_value = 14.0 - view_item.ext.view.set_incidence_angle(asset_value, asset_no_prop) - self.assertNotEqual(view_item.ext.view.get_incidence_angle(asset_no_prop), - view_item.ext.view.get_incidence_angle()) - self.assertEqual(view_item.ext.view.get_incidence_angle(asset_no_prop), asset_value) + view_ext(asset_no_prop).incidence_angle = asset_value + self.assertNotEqual(view_ext(asset_no_prop).incidence_angle, + view_ext(view_item).incidence_angle) + self.assertEqual(view_ext(asset_no_prop).incidence_angle, asset_value) # Validate view_item.validate() def test_azimuth(self): - view_item = ps.read_file(self.example_uri) + view_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("view:azimuth", view_item.properties) - view_azimuth = view_item.ext.view.azimuth + view_azimuth = view_ext(view_item).azimuth self.assertEqual(view_azimuth, view_item.properties['view:azimuth']) # Set - view_item.ext.view.azimuth = view_azimuth + 100 + view_ext(view_item).azimuth = view_azimuth + 100 self.assertEqual(view_azimuth + 100, view_item.properties['view:azimuth']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_item.ext.view.get_azimuth(asset_no_prop), - view_item.ext.view.get_azimuth()) - self.assertEqual(view_item.ext.view.get_azimuth(asset_prop), 5.0) + self.assertEqual(view_ext(asset_no_prop).azimuth, + view_ext(view_item).azimuth) + self.assertEqual(view_ext(asset_prop).azimuth, 5.0) # Set to Asset asset_value = 15.0 - view_item.ext.view.set_azimuth(asset_value, asset_no_prop) - self.assertNotEqual(view_item.ext.view.get_azimuth(asset_no_prop), - view_item.ext.view.get_azimuth()) - self.assertEqual(view_item.ext.view.get_azimuth(asset_no_prop), asset_value) + view_ext(asset_no_prop).azimuth = asset_value + self.assertNotEqual(view_ext(asset_no_prop).azimuth, + view_ext(view_item).azimuth) + self.assertEqual(view_ext(asset_no_prop).azimuth, asset_value) # Validate view_item.validate() def test_sun_azimuth(self): - view_item = ps.read_file(self.example_uri) + view_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("view:sun_azimuth", view_item.properties) - view_sun_azimuth = view_item.ext.view.sun_azimuth + view_sun_azimuth = view_ext(view_item).sun_azimuth self.assertEqual(view_sun_azimuth, view_item.properties['view:sun_azimuth']) # Set - view_item.ext.view.sun_azimuth = view_sun_azimuth + 100 + view_ext(view_item).sun_azimuth = view_sun_azimuth + 100 self.assertEqual(view_sun_azimuth + 100, view_item.properties['view:sun_azimuth']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_item.ext.view.get_sun_azimuth(asset_no_prop), - view_item.ext.view.get_sun_azimuth()) - self.assertEqual(view_item.ext.view.get_sun_azimuth(asset_prop), 1.0) + self.assertEqual(view_ext(asset_no_prop).sun_azimuth, + view_ext(view_item).sun_azimuth) + self.assertEqual(view_ext(asset_prop).sun_azimuth, 1.0) # Set to Asset asset_value = 11.0 - view_item.ext.view.set_sun_azimuth(asset_value, asset_no_prop) - self.assertNotEqual(view_item.ext.view.get_sun_azimuth(asset_no_prop), - view_item.ext.view.get_sun_azimuth()) - self.assertEqual(view_item.ext.view.get_sun_azimuth(asset_no_prop), asset_value) + view_ext(asset_no_prop).sun_azimuth = asset_value + self.assertNotEqual(view_ext(asset_no_prop).sun_azimuth, + view_ext(view_item).sun_azimuth) + self.assertEqual(view_ext(asset_no_prop).sun_azimuth, asset_value) # Validate view_item.validate() def test_sun_elevation(self): - view_item = ps.read_file(self.example_uri) + view_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("view:sun_elevation", view_item.properties) - view_sun_elevation = view_item.ext.view.sun_elevation + view_sun_elevation = view_ext(view_item).sun_elevation self.assertEqual(view_sun_elevation, view_item.properties['view:sun_elevation']) # Set - view_item.ext.view.sun_elevation = view_sun_elevation + 10 + view_ext(view_item).sun_elevation = view_sun_elevation + 10 self.assertEqual(view_sun_elevation + 10, view_item.properties['view:sun_elevation']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_item.ext.view.get_sun_elevation(asset_no_prop), - view_item.ext.view.get_sun_elevation()) - self.assertEqual(view_item.ext.view.get_sun_elevation(asset_prop), 2.0) + self.assertEqual(view_ext(asset_no_prop).sun_elevation, + view_ext(view_item).sun_elevation) + self.assertEqual(view_ext(asset_prop).sun_elevation, 2.0) # Set to Asset asset_value = 12.0 - view_item.ext.view.set_sun_elevation(asset_value, asset_no_prop) - self.assertNotEqual(view_item.ext.view.get_sun_elevation(asset_no_prop), - view_item.ext.view.get_sun_elevation()) - self.assertEqual(view_item.ext.view.get_sun_elevation(asset_no_prop), asset_value) + view_ext(asset_no_prop).sun_elevation = asset_value + self.assertNotEqual(view_ext(asset_no_prop).sun_elevation, + view_ext(view_item).sun_elevation) + self.assertEqual(view_ext(asset_no_prop).sun_elevation, asset_value) # Validate view_item.validate() diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index d1a61279d..71b4c09ff 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -39,10 +39,10 @@ def test_identify(self): self.assertEqual(actual.object_type, example['object_type'], msg=msg) version_contained_in_range = actual.version_range.contains(example['stac_version']) self.assertTrue(version_contained_in_range, msg=msg) - self.assertEqual(set(actual.common_extensions), + self.assertEqual(set(actual.extensions), set(example['common_extensions']), msg=msg) - self.assertEqual(set(actual.custom_extensions), + self.assertEqual(set(actual.extensions), set(example['custom_extensions']), msg=msg) diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index faffc2d17..571bab31b 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -1,3 +1,4 @@ +from pystac.extensions.view import ViewExtension, view_ext import unittest import pystac as ps @@ -26,15 +27,13 @@ def test_migrate(self): info = identify_stac_object(d) - migrated_d, info = migrate_to_latest(d, info) + migrated_d = migrate_to_latest(d, info) migrated_info = identify_stac_object(migrated_d) self.assertEqual(migrated_info.object_type, info.object_type) self.assertEqual(migrated_info.version_range.latest_valid_version(), ps.get_stac_version()) - self.assertEqual(set(migrated_info.common_extensions), set(info.common_extensions)) - self.assertEqual(set(migrated_info.custom_extensions), set(info.custom_extensions)) # Test that PySTAC can read it without errors. if info.object_type != ps.STACObjectType.ITEMCOLLECTION: @@ -52,10 +51,10 @@ def test_migrates_added_extension(self): item = ps.Item.from_file( TestCases.get_path('data-files/examples/0.8.1/item-spec/' 'examples/planet-sample.json')) - self.assertTrue('view' in item.stac_extensions) - self.assertEqual(item.ext.view.sun_azimuth, 101.8) - self.assertEqual(item.ext.view.sun_elevation, 58.8) - self.assertEqual(item.ext.view.off_nadir, 1) + self.assertTrue(ViewExtension.has_extension(item)) + self.assertEqual(view_ext(item).sun_azimuth, 101.8) + self.assertEqual(view_ext(item).sun_elevation, 58.8) + self.assertEqual(view_ext(item).off_nadir, 1) def test_migrates_renamed_extension(self): collection = ps.Collection.from_file( diff --git a/tests/test_cache.py b/tests/test_cache.py index 0ff09be5a..d4c7abb31 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,11 +1,13 @@ +from typing import Any, Dict +from pystac.utils import get_opt import unittest -import pystac +import pystac as ps from pystac.cache import (ResolvedObjectCache, ResolvedObjectCollectionCache) from tests.utils import TestCases -def create_catalog(suffix, include_href=True): +def create_catalog(suffix: Any, include_href: bool = True) -> ps.Catalog: return ps.Catalog( id='test {}'.format(suffix), description='test desc {}'.format(suffix), @@ -44,10 +46,13 @@ def test_merge(self): identical_cat1 = create_catalog(1, include_href=False) identical_cat2 = create_catalog(2) - cached_ids_1 = {cat1.id: cat1} - cached_hrefs_1 = {cat2.get_self_href(): cat2} - cached_ids_2 = {cat3.id: cat3, cat1.id: identical_cat1} - cached_hrefs_2 = {cat4.get_self_href(): cat4, cat2.get_self_href(): identical_cat2} + cached_ids_1: Dict[str, Any] = {cat1.id: cat1} + cached_hrefs_1: Dict[str, Any] = {get_opt(cat2.get_self_href()): cat2} + cached_ids_2: Dict[str, Any] = {cat3.id: cat3, cat1.id: identical_cat1} + cached_hrefs_2: Dict[str, Any] = { + get_opt(cat4.get_self_href()): cat4, + get_opt(cat2.get_self_href()): identical_cat2 + } cache1 = ResolvedObjectCollectionCache(ResolvedObjectCache(), cached_ids=cached_ids_1, cached_hrefs=cached_hrefs_1) @@ -61,12 +66,13 @@ def test_merge(self): self.assertIs(merged.get_by_id(cat1.id), cat1) self.assertEqual(set(merged.cached_hrefs.keys()), set([cat.get_self_href() for cat in [cat2, cat4]])) - self.assertIs(merged.get_by_href(cat2.get_self_href()), cat2) + self.assertIs(merged.get_by_href(get_opt(cat2.get_self_href())), cat2) def test_cache(self): cache = ResolvedObjectCache().as_collection_cache() collection = TestCases.test_case_8() collection_json = collection.to_dict() cache.cache(collection_json, collection.get_self_href()) - - self.assertEqual(cache.get_by_id(collection.id)['id'], collection.id) + cached = cache.get_by_id(collection.id) + assert isinstance(cached, dict) + self.assertEqual(cached['id'], collection.id) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index fcd1c670f..8cf3cbe16 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,9 +7,8 @@ from collections import defaultdict import pystac as ps -from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, _OldExtensionShortIDs, - HIERARCHICAL_LINKS) -from pystac.extensions.label import LabelClasses +from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, HIERARCHICAL_LINKS) +from pystac.extensions.label import LabelClasses, LabelExtension, LabelType, label_ext from pystac.validation import STACValidationError from pystac.utils import is_absolute_href from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX, MockStacIO) @@ -291,7 +290,8 @@ def test_normalize_href_works_with_label_source_links(self): catalog = TestCases.test_case_1() catalog.normalize_hrefs('http://example.com') item = catalog.get_item('area-1-1-labels', recursive=True) - source = next(item.ext.label.get_sources()) + assert item is not None + source = next(iter(label_ext(item).get_sources())) self.assertEqual( source.get_self_href(), "http://example.com/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json") @@ -487,7 +487,7 @@ def test_map_items_multiple_2(self): kitten.add_item(item2) def modify_item_title(item: ps.Item) -> ps.Item: - item.title = 'Some new title' + item.properties['title'] = 'Some title' return item def create_label_item(item: ps.Item) -> List[ps.Item]: @@ -500,11 +500,10 @@ def create_label_item(item: ps.Item) -> List[ps.Item]: bbox=item.bbox, datetime=datetime.utcnow(), properties={}) - label_item.ext.enable(_OldExtensionShortIDs.LABEL) - label_ext = label_item.ext.label - label_ext.apply( + LabelExtension(label_item).add_to(label_item) + label_ext(label_item).apply( label_description='labels', - label_type='vector', + label_type=LabelType.VECTOR, label_properties=['label'], label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], label_tasks=['classification']) @@ -788,7 +787,7 @@ def test_collections_cache_correctly(self): # Iterate over items to make sure they are read self.assertNotEqual(list(items), None) - call_uris = [ + call_uris: List[Any] = [ call[0][0] for call in mock_io.read_text_method.call_args_list if call[0][0] in expected_collection_reads ] @@ -909,12 +908,11 @@ def test_full_copy_2(self): geometry=RANDOM_GEOM, bbox=RANDOM_BBOX, datetime=datetime.utcnow(), - properties={}, - stac_extensions=[_OldExtensionShortIDs.LABEL]) - label_ext = label_item.ext.label - label_ext.apply( + properties={}) + LabelExtension.add_to(label_item) + label_ext(label_item).apply( label_description='labels', - label_type='vector', + label_type=LabelType.VECTOR, label_properties=['label'], label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], label_tasks=['classification']) @@ -959,4 +957,5 @@ def test_full_copy_4(self): item = cat2.get_item('cf73ec1a-d790-4b59-b077-e101738571ed', recursive=True) href = item.assets['cf73ec1a-d790-4b59-b077-e101738571ed'].get_absolute_href() + assert href is not None self.assertTrue(os.path.exists(href)) diff --git a/tests/test_collection.py b/tests/test_collection.py index b9588fd72..46371d0c2 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -6,6 +6,7 @@ from dateutil import tz import pystac as ps +from pystac.extensions.eo import EOExtension from pystac.validation import validate_dict from pystac import (Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType) from pystac.utils import datetime_to_str @@ -28,7 +29,7 @@ def test_read_eo_items_are_heritable(self): cat = TestCases.test_case_5() item = next(iter(cat.get_all_items())) - self.assertTrue(item.ext.implements('eo')) + self.assertTrue(EOExtension.has_extension(item)) def test_save_uses_previous_catalog_type(self): collection = TestCases.test_case_8() diff --git a/tests/test_item.py b/tests/test_item.py index da85a8d90..e5f49edae 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -7,8 +7,9 @@ import pystac as ps from pystac import Asset, Item, Provider from pystac.validation import validate_dict +import pystac.serialization.common_properties from pystac.item import CommonMetadata -from pystac.utils import (str_to_datetime, is_absolute_href) +from pystac.utils import (datetime_to_str, get_opt, str_to_datetime, is_absolute_href) from tests.utils import (TestCases, test_to_from_dict) @@ -65,7 +66,7 @@ def test_asset_absolute_href(self): self.assertEqual(expected_href, actual_href) def test_extra_fields(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item.json')) item.extra_fields['test'] = 'extra' @@ -77,13 +78,14 @@ def test_extra_fields(self): self.assertTrue('test' in item_json) self.assertEqual(item_json['test'], 'extra') - read_item = ps.read_file(p) + read_item = ps.Item.from_file(p) self.assertTrue('test' in read_item.extra_fields) self.assertEqual(read_item.extra_fields['test'], 'extra') def test_clearing_collection(self): collection = TestCases.test_case_4().get_child('acc') - item = next(collection.get_all_items()) + assert isinstance(collection, ps.Collection) + item = next(iter(collection.get_all_items())) self.assertEqual(item.collection_id, collection.id) item.set_collection(None) self.assertIsNone(item.collection_id) @@ -102,7 +104,7 @@ def test_datetime_ISO8601_format(self): self.assertEqual('2016-05-03T13:22:30.040000Z', formatted_time) def test_null_datetime(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item.json')) with self.assertRaises(ps.STACError): Item('test', geometry=item.geometry, bbox=item.bbox, datetime=None, properties={}) @@ -112,14 +114,14 @@ def test_null_datetime(self): bbox=item.bbox, datetime=None, properties={ - 'start_datetime': ps.utils.datetime_to_str(item.datetime), - 'end_datetime': ps.utils.datetime_to_str(item.datetime) + 'start_datetime': datetime_to_str(get_opt(item.datetime)), + 'end_datetime': datetime_to_str(get_opt(item.datetime)) }) null_dt_item.validate() def test_get_set_asset_datetime(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) item_datetime = item.datetime # No property on asset @@ -171,12 +173,12 @@ def test_0_9_item_with_no_extensions_does_not_read_collection_data(self): assert item_json.get('stac_extensions') is None assert item_json.get('stac_version') == '0.9.0' - did_merge = ps.serialization.common_properties.merge_common_properties(item_json) + did_merge = pystac.serialization.common_properties.merge_common_properties(item_json) self.assertFalse(did_merge) def test_clone_sets_asset_owner(self): cat = TestCases.test_case_2() - item = next(cat.get_all_items()) + item = next(iter(cat.get_all_items())) original_asset = list(item.assets.values())[0] assert original_asset.owner is item @@ -186,7 +188,7 @@ def test_clone_sets_asset_owner(self): def test_make_asset_href_relative_is_noop_on_relative_hrefs(self): cat = TestCases.test_case_2() - item = next(cat.get_all_items()) + item = next(iter(cat.get_all_items())) asset = list(item.assets.values())[0] assert not is_absolute_href(asset.href) original_href = asset.get_absolute_href() @@ -311,8 +313,8 @@ def test_common_metadata_providers(self): }] example_providers_object_list = [Provider.from_dict(d) for d in example_providers_dict_list] - for i in range(len(x.common_metadata.providers)): - p1 = x.common_metadata.providers[i] + for i in range(len(get_opt(x.common_metadata.providers))): + p1 = get_opt(x.common_metadata.providers)[i] p2 = providers_object_list[i] self.assertIsInstance(p1, Provider) self.assertIsInstance(p2, Provider) @@ -376,7 +378,7 @@ def test_common_metadata_basics(self): # Instruments instruments = ["cool_sensor_v1"] example_instruments = ["example instrument 1", "example instrument 2"] - self.assertListEqual(x.common_metadata.instruments, instruments) + self.assertListEqual(x.common_metadata.instruments or [], instruments) x.common_metadata.instruments = example_instruments self.assertListEqual(x.common_metadata.instruments, example_instruments) self.assertListEqual(x.properties['instruments'], example_instruments) @@ -404,7 +406,7 @@ def test_common_metadata_basics(self): self.assertEqual(x.properties['gsd'], example_gsd) def test_asset_start_datetime(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.start_datetime @@ -425,7 +427,7 @@ def test_asset_start_datetime(self): self.assertEqual(cm.start_datetime, item_value) def test_asset_end_datetime(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.end_datetime @@ -446,7 +448,7 @@ def test_asset_end_datetime(self): self.assertEqual(cm.end_datetime, item_value) def test_asset_license(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.license @@ -467,7 +469,7 @@ def test_asset_license(self): self.assertEqual(cm.license, item_value) def test_asset_providers(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.providers @@ -492,7 +494,7 @@ def test_asset_providers(self): self.assertEqual(cm.providers[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.platform @@ -513,7 +515,7 @@ def test_asset_platform(self): self.assertEqual(cm.platform, item_value) def test_asset_instruments(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.instruments @@ -534,7 +536,7 @@ def test_asset_instruments(self): self.assertEqual(cm.instruments, item_value) def test_asset_constellation(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.constellation @@ -555,7 +557,7 @@ def test_asset_constellation(self): self.assertEqual(cm.constellation, item_value) def test_asset_mission(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.mission @@ -576,7 +578,7 @@ def test_asset_mission(self): self.assertEqual(cm.mission, item_value) def test_asset_gsd(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.gsd @@ -597,7 +599,7 @@ def test_asset_gsd(self): self.assertEqual(cm.gsd, item_value) def test_asset_created(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.created @@ -618,7 +620,7 @@ def test_asset_created(self): self.assertEqual(cm.created, item_value) def test_asset_updated(self): - item = ps.read_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.updated diff --git a/tests/test_link.py b/tests/test_link.py index 385662069..411341932 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -1,7 +1,8 @@ import datetime import unittest -import pystac +import pystac as ps +from tests.utils.test_cases import RANDOM_EXTENT TEST_DATETIME: datetime.datetime = datetime.datetime(2020, 3, 14, 16, 32) @@ -44,8 +45,6 @@ def test_minimal(self): # Try the modification methods. self.assertIsNone(link.owner) - link.set_owner(1) # A junk value. - self.assertEqual(1, link.owner) link.set_owner(None) self.assertIsNone(link.owner) @@ -119,13 +118,13 @@ def test_from_dict_failures(self): ps.Link.from_dict(d) def test_collection(self): - c = ps.Collection('collection id', 'desc', extent=None) + c = ps.Collection('collection id', 'desc', extent=RANDOM_EXTENT) link = ps.Link.collection(c) expected = {'rel': 'collection', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) def test_child(self): - c = ps.Collection('collection id', 'desc', extent=None) + c = ps.Collection('collection id', 'desc', extent=RANDOM_EXTENT) link = ps.Link.child(c) expected = {'rel': 'child', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) diff --git a/tests/test_version.py b/tests/test_version.py index 1a9dc49e1..56fb7ac31 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,7 +1,7 @@ import os import unittest -import pystac +import pystac as ps from tests.utils import TestCases @@ -30,7 +30,4 @@ def test_override_stac_version_with_call(self): d = cat.to_dict() self.assertEqual(d['stac_version'], override_version) finally: - if version == ps.version.STACVersion.DEFAULT_STAC_VERSION: - ps.set_stac_version(None) - else: - ps.set_stac_version(version) + ps.set_stac_version(version) diff --git a/tests/test_writing.py b/tests/test_writing.py index 258e96388..ad6d26177 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -28,7 +28,7 @@ def validate_catalog(self, catalog: ps.Catalog): def validate_file(self, path: str, object_type: str): d = STAC_IO.read_json(path) - return validate_dict(d, object_type) + return validate_dict(d, ps.STACObjectType(object_type)) def validate_link_types(self, root_href: str, catalog_type: ps.CatalogType): def validate_asset_href_type(item: ps.Item, item_href: str): diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index c14049253..b176f0925 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -5,7 +5,8 @@ from pystac import (Catalog, Collection, Item, Asset, Extent, TemporalExtent, SpatialExtent, MediaType) -from pystac.extensions.label import (LabelOverview, LabelClasses, LabelCount) +from pystac.extensions.label import (LabelExtension, LabelOverview, LabelClasses, LabelCount, + LabelType) TEST_LABEL_CATALOG = { 'country-1': { @@ -133,16 +134,16 @@ def test_case_3() -> Catalog: datetime=datetime.utcnow(), properties={}) - label_item.ext.enable("label") - label_item.ext.label.apply( - label_description='ML Labels', - label_type='vector', - label_properties=['label'], - label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], - label_tasks=['classification'], - label_methods=['manual'], - label_overviews=overviews) - label_item.ext.label.add_source(image_item, assets=['ortho']) + LabelExtension.add_to(label_item) + label_ext = LabelExtension.ext(label_item) + label_ext.apply(label_description='ML Labels', + label_type=LabelType.VECTOR, + label_properties=['label'], + label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], + label_tasks=['classification'], + label_methods=['manual'], + label_overviews=overviews) + label_ext.add_source(image_item, assets=['ortho']) root_cat.add_item(image_item) root_cat.add_item(label_item) diff --git a/tests/validation/test_schema_uri_map.py b/tests/validation/test_schema_uri_map.py index fac1fc95a..6a702c2e7 100644 --- a/tests/validation/test_schema_uri_map.py +++ b/tests/validation/test_schema_uri_map.py @@ -1,14 +1,14 @@ import unittest -import pystac +import pystac as ps from pystac.validation.schema_uri_map import DefaultSchemaUriMap class SchemaUriMapTest(unittest.TestCase): - def test_gets_extension_for_old_version(self): + def test_gets_schema_uri_for_old_version(self): d = DefaultSchemaUriMap() - uri = d.get_extension_schema_uri('asset', ps.STACObjectType.COLLECTION, '0.8.0') + uri = d.get_object_schema_uri(ps.STACObjectType.ITEM, '0.8.0') self.assertEqual( uri, 'https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.0/' - 'extensions/asset/json-schema/schema.json') + 'item/json-schema/schema.json') diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 2372e8edc..8f0a452ce 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -1,13 +1,15 @@ from datetime import datetime import json import os +from typing import Any, Dict +from pystac.utils import get_opt import shutil import unittest from tempfile import TemporaryDirectory import jsonschema -import pystac +import pystac as ps import pystac.validation from pystac.cache import CollectionCache from pystac.serialization.common_properties import merge_common_properties @@ -42,7 +44,7 @@ def test_validate_examples(self): with open(path) as f: stac_json = json.load(f) - self.assertEqual(len(ps.validation.validate_dict(stac_json)), 0) + self.assertEqual(len(pystac.validation.validate_dict(stac_json)), 0) else: with self.subTest(path): with open(path) as f: @@ -55,11 +57,11 @@ def test_validate_examples(self): merge_common_properties(stac_json, collection_cache, path) if valid: - ps.validation.validate_dict(stac_json) + pystac.validation.validate_dict(stac_json) else: with self.assertRaises(STACValidationError): try: - ps.validation.validate_dict(stac_json) + pystac.validation.validate_dict(stac_json) except STACValidationError as e: self.assertIsInstance(e.source, jsonschema.ValidationError) raise e @@ -76,15 +78,15 @@ def test_validate_error_contains_href(self): try: item.validate() except STACValidationError as e: - self.assertTrue(item.get_self_href() in str(e)) + self.assertTrue(get_opt(item.get_self_href()) in str(e)) raise e def test_validate_all(self): for test_case in TestCases.all_test_catalogs(): - catalog_href = TestCases.test_case_7().get_self_href() + catalog_href = get_opt(TestCases.test_case_7().get_self_href()) stac_dict = ps.STAC_IO.read_json(catalog_href) - ps.validation.validate_all(stac_dict, catalog_href) + pystac.validation.validate_all(stac_dict, catalog_href) # Modify a 0.8.1 collection in a catalog to be invalid with a since-renamed extension # and make sure it catches the validation error. @@ -92,13 +94,13 @@ def test_validate_all(self): with TemporaryDirectory() as tmp_dir: dst_dir = os.path.join(tmp_dir, 'catalog') # Copy test case 7 to the temporary directory - catalog_href = TestCases.test_case_7().get_self_href() + catalog_href = get_opt(TestCases.test_case_7().get_self_href()) shutil.copytree(os.path.dirname(catalog_href), dst_dir) new_cat_href = os.path.join(dst_dir, 'catalog.json') # Make sure it's valid before modification - ps.validation.validate_all(ps.STAC_IO.read_json(new_cat_href), new_cat_href) + pystac.validation.validate_all(ps.STAC_IO.read_json(new_cat_href), new_cat_href) # Modify a contained collection to add an extension for which the # collection is invalid. @@ -111,7 +113,7 @@ def test_validate_all(self): stac_dict = ps.STAC_IO.read_json(new_cat_href) with self.assertRaises(STACValidationError): - ps.validation.validate_all(stac_dict, new_cat_href) + pystac.validation.validate_all(stac_dict, new_cat_href) def test_validates_geojson_with_tuple_coordinates(self): """This unit tests guards against a bug where if a geometry @@ -119,7 +121,7 @@ def test_validates_geojson_with_tuple_coordinates(self): which can be produced by shapely, then the geometry still passses validation. """ - geom = { + geom: Dict[str, Any] = { 'type': 'Polygon', # Last , is required to ensure tuple creation. From 245a6db04e8e952fbd10047bba7fea83024768ed Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 01:39:01 -0400 Subject: [PATCH 16/51] Fixed identify tests; fixing migration teses --- pystac/collection.py | 2 +- pystac/extensions/file.py | 2 +- pystac/extensions/hooks.py | 17 +- pystac/extensions/label.py | 6 +- pystac/extensions/projection.py | 2 +- pystac/serialization/identify.py | 37 ++- pystac/serialization/migrate.py | 55 ++-- tests/data-files/examples/example-info.csv | 276 ++++++++++----------- tests/data-files/get_examples.py | 23 +- tests/serialization/test_identify.py | 13 +- tests/serialization/test_migrate.py | 8 +- tests/test_catalog.py | 50 ++-- tests/test_collection.py | 18 +- tests/test_layout.py | 16 +- tests/test_link.py | 6 +- tests/utils/__init__.py | 2 +- tests/utils/test_cases.py | 63 ++--- tests/validation/test_validate.py | 10 +- 18 files changed, 303 insertions(+), 303 deletions(-) diff --git a/pystac/collection.py b/pystac/collection.py index 170d04af6..c73572462 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -633,7 +633,7 @@ def from_dict(cls, collection.add_link(Link.from_dict(link)) if assets is not None: - for asset_key, asset_dict in assets: + for asset_key, asset_dict in assets.items(): collection.add_asset(asset_key, Asset(asset_dict)) return collection diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 56d775bce..36824bd7d 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -8,7 +8,7 @@ T = TypeVar('T', ps.Item, ps.Asset) -SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" +SCHEMA_URI = "https://stac-extensions.github.io/file/v1.0.0/schema.json" DATA_TYPE_PROP = 'file:data_type' SIZE_PROP = "file:size" diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index ea9960abf..8e4aa5117 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from functools import lru_cache from typing import Any, Dict, Iterable, List, Optional, Set, TYPE_CHECKING import pystac as ps @@ -32,8 +33,10 @@ def stac_object_types(self) -> Set[ps.STACObjectType]: """A set of STACObjectType for which migration logic will be applied.""" pass - _stac_object_type_str: Optional[Set[str]] = None - """Translation of stac_object_types to strings, cached as a class attribute""" + @lru_cache() + def _get_stac_object_types(self) -> Set[str]: + """Translation of stac_object_types to strings, cached""" + return set([x.value for x in self.stac_object_types]) def get_object_links(self, obj: "STACObject_Type") -> Optional[List[str]]: return None @@ -47,17 +50,12 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, manipulate the obj dict. Remember to call super() in order to change out the old 'stac_extension' entry with the latest schema URI. """ - if self._stac_object_type_str is None: - self._stac_object_type_str = set([x.value for x in self.stac_object_types]) - if not info.object_type in self._stac_object_type_str: - return - # Migrate schema versions for prev_id in self.prev_extension_ids: if prev_id in info.extensions: try: i = obj['stac_extensions'].index(prev_id) - obj['stac_extension'][i] = self.schema_uri + obj['stac_extensions'][i] = self.schema_uri except ValueError: obj['stac_extensions'].append(self.schema_uri) break @@ -92,4 +90,5 @@ def get_extended_object_links(self, obj: "STACObject_Type") -> List[str]: def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: for hooks in self.hooks.values(): - hooks.migrate(obj, version, info) + if info.object_type in hooks._get_stac_object_types(): + hooks.migrate(obj, version, info) diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 1bc3a1430..3d24dbbde 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -703,9 +703,9 @@ def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: return ['source'] return None - def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': - props = d['properties'] + props = obj['properties'] # Migrate 0.8.0-rc1 non-pluralized forms # As it's a common mistake, convert for any pre-1.0.0 version. if 'label:property' in props and 'label:properties' not in props: @@ -724,6 +724,8 @@ def migrate(self, d: Dict[str, Any], version: STACVersionID, info: STACJSONDescr props['label:methods'] = props['label:method'] del props['label:method'] + super().migrate(obj, version, info) + def label_ext(item: ps.Item) -> LabelExtension: return LabelExtension.ext(item) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index c74eb83e4..5629929d0 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -260,7 +260,7 @@ def __repr__(self) -> str: class ProjectionExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['projection']) + prev_extension_ids: Set[str] = set(['proj', 'projection']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) def projection_ext(obj: T) -> ProjectionExtension[T]: diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 231e1bf4b..30c32cb09 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -9,24 +9,21 @@ from pystac.stac_object import STACObjectType as STACObjectType_Type -class OldExtensionShortIDs(str, Enum): +class OldExtensionShortIDs(Enum): """Enumerates the IDs of common extensions.""" - def __str__(self) -> str: - return str(self.value) - - CHECKSUM = 'checksum' - COLLECTION_ASSETS = 'collection-assets' - DATACUBE = 'datacube' + CHECKSUM = 'checksum' # REMOVED + COLLECTION_ASSETS = 'collection-assets' # REMOVED + DATACUBE = 'datacube' # TODO EO = 'eo' - ITEM_ASSETS = 'item-assets' + ITEM_ASSETS = 'item-assets' # TODO LABEL = 'label' POINTCLOUD = 'pointcloud' PROJECTION = 'projection' SAR = 'sar' SAT = 'sat' SCIENTIFIC = 'scientific' - SINGLE_FILE_STAC = 'single-file-stac' - TILED_ASSETS = 'tiled-assets' + SINGLE_FILE_STAC = 'single-file-stac' # TODO + TILED_ASSETS = 'tiled-assets' # Removed (Unpublished) TIMESTAMPS = 'timestamps' VERSION = 'version' VIEW = 'view' @@ -163,7 +160,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], Returns a list of stac_extensions. May mutate the version_range to update min or max version. """ - stac_extensions = set([]) + stac_extensions: Set[str] = set([]) # assets (collection assets) @@ -184,21 +181,21 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if any(prop.startswith('checksum:') for prop in link_props): found_checksum = True - stac_extensions.add(OldExtensionShortIDs.CHECKSUM) + stac_extensions.add(OldExtensionShortIDs.CHECKSUM.value) if not found_checksum: if 'assets' in d: for asset in d['assets'].values(): asset_props = cast(Dict[str, Any], asset).keys() if any(prop.startswith('checksum:') for prop in asset_props): found_checksum = True - stac_extensions.add(OldExtensionShortIDs.CHECKSUM) + stac_extensions.add(OldExtensionShortIDs.CHECKSUM.value) if found_checksum: version_range.set_min(STACVersionID('0.6.2')) # datacube if object_type == ps.STACObjectType.ITEM: if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(OldExtensionShortIDs.DATACUBE) + stac_extensions.add(OldExtensionShortIDs.DATACUBE.value) version_range.set_min(STACVersionID('0.6.1')) # datetime-range (old extension) @@ -210,7 +207,7 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], # eo if object_type == ps.STACObjectType.ITEM: if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(OldExtensionShortIDs.EO) + stac_extensions.add(OldExtensionShortIDs.EO.value) if 'eo:epsg' in d['properties']: if d['properties']['eo:epsg'] is None: version_range.set_min(STACVersionID('0.6.1')) @@ -219,19 +216,19 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if 'eo:constellation' in d['properties']: version_range.set_min(STACVersionID('0.6.0')) if 'eo:bands' in d: - stac_extensions.add(OldExtensionShortIDs.EO) + stac_extensions.add(OldExtensionShortIDs.EO.value) version_range.set_max(STACVersionID('0.5.2')) # pointcloud if object_type == ps.STACObjectType.ITEM: if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(OldExtensionShortIDs.POINTCLOUD) + stac_extensions.add(OldExtensionShortIDs.POINTCLOUD.value) version_range.set_min(STACVersionID('0.6.2')) # sar if object_type == ps.STACObjectType.ITEM: if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): - stac_extensions.add(OldExtensionShortIDs.SAR) + stac_extensions.add(OldExtensionShortIDs.SAR.value) version_range.set_min(STACVersionID('0.6.2')) if version_range.contains('0.6.2'): for prop in [ @@ -260,13 +257,13 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], if 'properties' in d: prop_keys = cast(Dict[str, Any], d['properties']).keys() if any(k.startswith('sci:') for k in prop_keys): - stac_extensions.add(OldExtensionShortIDs.SCIENTIFIC) + stac_extensions.add(OldExtensionShortIDs.SCIENTIFIC.value) version_range.set_min(STACVersionID('0.6.0')) # Single File STAC if object_type == ps.STACObjectType.ITEMCOLLECTION: if 'collections' in d: - stac_extensions.add(OldExtensionShortIDs.SINGLE_FILE_STAC) + stac_extensions.add(OldExtensionShortIDs.SINGLE_FILE_STAC.value) version_range.set_min(STACVersionID('0.8.0')) if 'stac_extensions' not in d: version_range.set_max(STACVersionID('0.8.1')) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 12b714f12..677f1344e 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -22,7 +22,7 @@ def _migrate_catalog(d: Dict[str, Any], version: STACVersionID, info: STACJSONDe _migrate_links(d, version) if version < '0.8': - d['stac_extensions'] = info.extensions + d['stac_extensions'] = list(info.extensions) def _migrate_collection(d: Dict[str, Any], version: STACVersionID, @@ -34,13 +34,13 @@ def _migrate_item(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescr _migrate_links(d, version) if version < '0.8': - d['stac_extensions'] = info.extensions + d['stac_extensions'] = list(info.extensions) def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: if version < '0.9.0': - d['stac_extensions'] = info.extensions + d['stac_extensions'] = list(info.extensions) # Extensions @@ -77,7 +77,7 @@ def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], @lru_cache() def get_schema_map(cls) -> Dict[str, Any]: return { - OldExtensionShortIDs.CHECKSUM: ({ + OldExtensionShortIDs.CHECKSUM.value: ({ ps.STACObjectType.CATALOG: 'extensions/checksum/json-schema/schema.json', ps.STACObjectType.COLLECTION: @@ -85,11 +85,11 @@ def get_schema_map(cls) -> Dict[str, Any]: ps.STACObjectType.ITEM: 'extensions/checksum/json-schema/schema.json' }, None), - OldExtensionShortIDs.COLLECTION_ASSETS: ({ + OldExtensionShortIDs.COLLECTION_ASSETS.value: ({ ps.STACObjectType.COLLECTION: 'extensions/collection-assets/json-schema/schema.json' }, None), - OldExtensionShortIDs.DATACUBE: ({ + OldExtensionShortIDs.DATACUBE.value: ({ ps.STACObjectType.COLLECTION: 'extensions/datacube/json-schema/schema.json', ps.STACObjectType.ITEM: @@ -98,21 +98,21 @@ def get_schema_map(cls) -> Dict[str, Any]: ps.STACObjectType.COLLECTION: None, ps.STACObjectType.ITEM: None })]), - OldExtensionShortIDs.EO: ({ + OldExtensionShortIDs.EO.value: ({ ps.STACObjectType.ITEM: 'extensions/eo/json-schema/schema.json' }, None), - OldExtensionShortIDs.ITEM_ASSETS: ({ + OldExtensionShortIDs.ITEM_ASSETS.value: ({ ps.STACObjectType.COLLECTION: 'extensions/item-assets/json-schema/schema.json' }, None), - OldExtensionShortIDs.LABEL: ({ + OldExtensionShortIDs.LABEL.value: ({ ps.STACObjectType.ITEM: 'extensions/label/json-schema/schema.json' }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { ps.STACObjectType.ITEM: 'extensions/label/schema.json' })]), - OldExtensionShortIDs.POINTCLOUD: ( + OldExtensionShortIDs.POINTCLOUD.value: ( { # Poincloud schema was broken in 1.0.0-beta.2 and prior; # Use this schema version (corresponding to 1.0.0-rc.1) @@ -121,29 +121,29 @@ def get_schema_map(cls) -> Dict[str, Any]: 'https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json' }, None), - OldExtensionShortIDs.PROJECTION: ({ + OldExtensionShortIDs.PROJECTION.value: ({ ps.STACObjectType.ITEM: 'extensions/projection/json-schema/schema.json' }, None), - OldExtensionShortIDs.SAR: ({ + OldExtensionShortIDs.SAR.value: ({ ps.STACObjectType.ITEM: 'extensions/sar/json-schema/schema.json' }, None), - OldExtensionShortIDs.SAT: ({ + OldExtensionShortIDs.SAT.value: ({ ps.STACObjectType.ITEM: 'extensions/sat/json-schema/schema.json' }, None), - OldExtensionShortIDs.SCIENTIFIC: ({ + OldExtensionShortIDs.SCIENTIFIC.value: ({ ps.STACObjectType.ITEM: 'extensions/scientific/json-schema/schema.json', ps.STACObjectType.COLLECTION: 'extensions/scientific/json-schema/schema.json' }, None), - OldExtensionShortIDs.SINGLE_FILE_STAC: ({ + OldExtensionShortIDs.SINGLE_FILE_STAC.value: ({ ps.STACObjectType.CATALOG: 'extensions/single-file-stac/json-schema/schema.json' }, None), - OldExtensionShortIDs.TILED_ASSETS: ({ + OldExtensionShortIDs.TILED_ASSETS.value: ({ ps.STACObjectType.CATALOG: 'extensions/tiled-assets/json-schema/schema.json', ps.STACObjectType.COLLECTION: @@ -151,17 +151,17 @@ def get_schema_map(cls) -> Dict[str, Any]: ps.STACObjectType.ITEM: 'extensions/tiled-assets/json-schema/schema.json' }, None), - OldExtensionShortIDs.TIMESTAMPS: ({ + OldExtensionShortIDs.TIMESTAMPS.value: ({ ps.STACObjectType.ITEM: 'extensions/timestamps/json-schema/schema.json' }, None), - OldExtensionShortIDs.VERSION: ({ + OldExtensionShortIDs.VERSION.value: ({ ps.STACObjectType.ITEM: 'extensions/version/json-schema/schema.json', ps.STACObjectType.COLLECTION: 'extensions/version/json-schema/schema.json' }, None), - OldExtensionShortIDs.VIEW: ({ + OldExtensionShortIDs.VIEW.value: ({ ps.STACObjectType.ITEM: 'extensions/view/json-schema/schema.json' }, None), @@ -287,23 +287,16 @@ def migrate_to_latest(json_dict: Dict[str, Any], info: STACJSONDescription) -> D if version != STACVersion.DEFAULT_STAC_VERSION: object_migrations[info.object_type](result, version, info) + if 'stac_extensions' not in result: + # Force stac_extensions property, as it makes + # downstream migration less complex + result['stac_extensions'] = [] ps.EXTENSION_HOOKS.migrate(result, version, info) - for ext in (result.get('stac_extensions') or []): + for ext in result['stac_extensions'][:]: if ext in removed_extension_migrations: removed_extension_migrations[ext](result, version, info) result['stac_extensions'].remove(ext) - else: - # Ensure old ID's are moved to schemas - # May need a better way to differentiate - # old ID's from schemas, but going with - # the file extension for now. - if not ext.lower().endswith('.json'): - result['stac_extensions'].remove(ext) - uri = OldExtensionSchemaUriMap.get_extension_schema_uri( - ext, info.object_type, version) - if uri is not None: - result['stac_extensions'].append(uri) result['stac_version'] = STACVersion.DEFAULT_STAC_VERSION diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index 21be594f0..712c14d67 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -1,138 +1,138 @@ -"0.4.1/extensions/examples/landsat8-merged.json","ITEM","0.4.1","eo","" -"0.5.2/extensions/examples/landsat8-merged.json","ITEM","0.5.2","eo","" -"0.7.0/extensions/sar/examples/sentinel1.json","ITEM","0.7.0","sar|datetime-range|checksum","" -"0.8.1/catalog-spec/examples/catalog.json","CATALOG","0.8.1","","" -"0.8.1/catalog-spec/examples/summaries-s2.json","CATALOG","0.8.1","","" -"0.8.1/collection-spec/examples/landsat-collection.json","COLLECTION","0.8.1","","" -"0.8.1/collection-spec/examples/landsat-item.json","ITEM","0.8.1","eo","" -"0.8.1/collection-spec/examples/sentinel2.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/asset/examples/example-landsat8.json","COLLECTION","0.8.1","asset","" -"0.8.1/extensions/checksum/examples/example-sentinel1.json","ITEM","0.8.1","checksum","" -"0.8.1/extensions/datacube/examples/example.json","ITEM","0.8.1","datacube","" -"0.8.1/extensions/datetime-range/examples/example-video.json","ITEM","0.8.1","datetime-range","" -"0.8.1/extensions/eo/examples/example-landsat8.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.8.1/extensions/label/examples/multidataset/catalog.json","CATALOG","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.8.1","","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.8.1","label","" -"0.8.1/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.8.1","","" -"0.8.1/extensions/pointcloud/examples/example-autzen.json","ITEM","0.8.1","pointcloud","" -"0.8.1/extensions/sar/examples/envisat.json","ITEM","0.8.1","sar|datetime-range","" -"0.8.1/extensions/sar/examples/sentinel1.json","ITEM","0.8.1","checksum|sar|datetime-range","" -"0.8.1/extensions/scientific/examples/collection.json","COLLECTION","0.8.1","scientific","" -"0.8.1/extensions/scientific/examples/item.json","ITEM","0.8.1","datetime-range|checksum|scientific","" -"0.8.1/item-spec/examples/digitalglobe-sample.json","ITEM","0.8.1","eo","https://example.digitalglobe.com/stac/1.0/schema.json" -"0.8.1/item-spec/examples/landsat8-sample.json","ITEM","0.8.1","eo","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.8.1/item-spec/examples/planet-sample.json","ITEM","0.8.1","eo","https://example.planet.com/stac/1.0/schema.json" -"0.8.1/item-spec/examples/sample-full.json","ITEM","0.8.1","eo","https://example.com/cs-extension/1.0/schema.json" -"0.8.1/item-spec/examples/sample.json","ITEM","0.8.1","","" -"0.8.1/item-spec/examples/sentinel2-sample.json","ITEM","0.8.1","eo","" -"0.9.0/catalog-spec/examples/catalog.json","CATALOG","0.9.0","","" -"0.9.0/collection-spec/examples/landsat-collection.json","COLLECTION","0.9.0","commons|view|eo","" -"0.9.0/collection-spec/examples/landsat-item.json","ITEM","0.9.0","commons|eo|view","https://example.com/stac/landsat-extension/1.0/schema.json","INVALID" -"0.9.0/collection-spec/examples/sentinel2.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/asset/examples/example-landsat8.json","COLLECTION","0.9.0","asset|commons","" -"0.9.0/extensions/checksum/examples/sentinel1.json","ITEM","0.9.0","checksum","" -"0.9.0/extensions/commons/examples/landsat-collection.json","COLLECTION","0.9.0","commons","" -"0.9.0/extensions/commons/examples/landsat-item.json","ITEM","0.9.0","commons|eo|sat","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/extensions/datacube/examples/example-collection.json","COLLECTION","0.9.0","datacube","" -"0.9.0/extensions/datacube/examples/example-item.json","ITEM","0.9.0","datacube","" -"0.9.0/extensions/eo/examples/example-landsat8.json","ITEM","0.9.0","eo|view|commons","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/extensions/label/examples/multidataset/catalog.json","CATALOG","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.9.0","label|version","","INVALID" -"0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.9.0","","" -"0.9.0/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.9.0","label|version","" -"0.9.0/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.9.0","","" -"0.9.0/extensions/pointcloud/examples/example-autzen.json","ITEM","0.9.0","pointcloud","" -"0.9.0/extensions/projection/examples/example-landsat8.json","ITEM","0.9.0","proj|commons","" -"0.9.0/extensions/sar/examples/envisat.json","ITEM","0.9.0","sat|sar","" -"0.9.0/extensions/sar/examples/sentinel1.json","ITEM","0.9.0","checksum|sar|sat","" -"0.9.0/extensions/sat/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" -"0.9.0/extensions/scientific/examples/collection.json","COLLECTION","0.9.0","scientific","" -"0.9.0/extensions/scientific/examples/item.json","ITEM","0.9.0","scientific|checksum","" -"0.9.0/extensions/version/examples/collection.json","COLLECTION","0.9.0","version","" -"0.9.0/extensions/version/examples/item.json","ITEM","0.9.0","version","","INVALID" -"0.9.0/extensions/view/examples/example-landsat8.json","ITEM","0.9.0","sat|view","" -"0.9.0/item-spec/examples/datetimerange.json","ITEM","0.9.0","","" -"0.9.0/item-spec/examples/digitalglobe-sample.json","ITEM","0.9.0","eo|proj|view","https://example.digitalglobe.com/stac/1.0/schema.json" -"0.9.0/item-spec/examples/landsat8-sample.json","ITEM","0.9.0","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"0.9.0/item-spec/examples/planet-sample.json","ITEM","0.9.0","eo|view","https://example.planet.com/stac/1.0/schema.json" -"0.9.0/item-spec/examples/sample-full.json","ITEM","0.9.0","eo|view","https://example.com/cs-extension/1.0/schema.json" -"0.9.0/item-spec/examples/sample.json","ITEM","0.9.0","","" -"0.9.0/item-spec/examples/sentinel2-sample.json","ITEM","0.9.0","eo|view|proj|commons","" -"1.0.0-beta.2/catalog-spec/examples/catalog-items.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/catalog-spec/examples/catalog.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/collection-spec/examples/landsat-collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/collection-spec/examples/sentinel2.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/checksum/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum","" -"1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json","COLLECTION","1.0.0-beta.2","collection-assets","https://github.com/NCAR/esm-collection-spec/tree/v0.2.0/schema.json" -"1.0.0-beta.2/extensions/datacube/examples/example-collection.json","COLLECTION","1.0.0-beta.2","datacube","" -"1.0.0-beta.2/extensions/datacube/examples/example-item.json","ITEM","1.0.0-beta.2","datacube","" -"1.0.0-beta.2/extensions/eo/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json","COLLECTION","1.0.0-beta.2","item-assets","" -"1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json","CATALOG","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","1.0.0-beta.2","label|version","" -"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json","ITEM","1.0.0-beta.2","pointcloud","" -"1.0.0-beta.2/extensions/projection/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|projection","" -"1.0.0-beta.2/extensions/sar/examples/envisat.json","ITEM","1.0.0-beta.2","sat|sar","" -"1.0.0-beta.2/extensions/sar/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum|sar|sat","" -"1.0.0-beta.2/extensions/sat/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" -"1.0.0-beta.2/extensions/scientific/examples/collection.json","COLLECTION","1.0.0-beta.2","scientific","" -"1.0.0-beta.2/extensions/scientific/examples/item.json","ITEM","1.0.0-beta.2","scientific|checksum","" -"1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json","ITEM","1.0.0-beta.2","datacube|eo|tiled-assets","" -"1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json","ITEM","1.0.0-beta.2","eo|tiled-assets","" -"1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json","ITEM","1.0.0-beta.2","timestamps","" -"1.0.0-beta.2/extensions/version/examples/collection.json","COLLECTION","1.0.0-beta.2","version","" -"1.0.0-beta.2/extensions/version/examples/item.json","ITEM","1.0.0-beta.2","version","" -"1.0.0-beta.2/extensions/view/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view","" -"1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json","ITEM","1.0.0-beta.2","projection|view","https://example.com/stac/cbers-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/datetimerange.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json","ITEM","1.0.0-beta.2","eo|projection|view","https://example.digitalglobe.com/stac/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/landsat8-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/stac/landsat-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/planet-sample.json","ITEM","1.0.0-beta.2","eo|view","https://example.planet.com/stac/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/sample-full.json","ITEM","1.0.0-beta.2","eo|view","https://example.com/cs-extension/1.0/schema.json" -"1.0.0-beta.2/item-spec/examples/sample.json","ITEM","1.0.0-beta.2","","" -"1.0.0-beta.2/item-spec/examples/sentinel2-sample.json","ITEM","1.0.0-beta.2","view|projection","" -"gee-0.6.2/CIESIN_GPWv411_GPW_National_Identifier_Grid.json","COLLECTION","0.6.2","scientific","" -"gee-0.6.2/LANDSAT_LT05_C01_T1_ANNUAL_NDWI.json","COLLECTION","0.6.2","","" -"gee-0.6.2/catalog.json","CATALOG","0.6.2","","" -"iserv-0.6.1/2013/03/27/IP0201303271418280967S05834W.json","ITEM","0.6.1","eo","" -"iserv-0.6.1/2013/03/27/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/2013/03/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/2013/catalog.json","CATALOG","0.6.1","","" -"iserv-0.6.1/catalog.json","COLLECTION","0.6.1","","" -"landsat-0.6.0/010/117/2015-01-02/LC80101172015002LGN00.json","ITEM","0.6.0","eo","" -"landsat-0.6.0/010/117/catalog.json","CATALOG","0.6.0","","" -"landsat-0.6.0/010/catalog.json","COLLECTION","0.6.0","","" -"landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json","ITEM","0.6.0","eo","" -"landsat-0.6.0/156/029/catalog.json","CATALOG","0.6.0","","" -"landsat-0.6.0/156/catalog.json","COLLECTION","0.6.0","","" -"landsat-0.6.0/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/2017-10-13/S2B_9VXK_20171013_0.json","ITEM","0.6.0","eo","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/V/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/9/catalog.json","CATALOG","0.6.0","","" -"sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","","" -"hand-0.9.0/collection.json","COLLECTION","0.9.0","","" -"hand-0.8.1/collection.json","COLLECTION","0.8.1","","" \ No newline at end of file +"0.4.1/extensions/examples/landsat8-merged.json","ITEM","0.4.1","eo" +"0.5.2/extensions/examples/landsat8-merged.json","ITEM","0.5.2","eo" +"0.7.0/extensions/sar/examples/sentinel1.json","ITEM","0.7.0","sar|datetime-range|checksum" +"0.8.1/catalog-spec/examples/catalog.json","CATALOG","0.8.1","" +"0.8.1/catalog-spec/examples/summaries-s2.json","CATALOG","0.8.1","" +"0.8.1/collection-spec/examples/landsat-collection.json","COLLECTION","0.8.1","" +"0.8.1/collection-spec/examples/landsat-item.json","ITEM","0.8.1","eo" +"0.8.1/collection-spec/examples/sentinel2.json","COLLECTION","0.8.1","" +"0.8.1/extensions/asset/examples/example-landsat8.json","COLLECTION","0.8.1","asset" +"0.8.1/extensions/checksum/examples/example-sentinel1.json","ITEM","0.8.1","checksum" +"0.8.1/extensions/datacube/examples/example.json","ITEM","0.8.1","datacube" +"0.8.1/extensions/datetime-range/examples/example-video.json","ITEM","0.8.1","datetime-range" +"0.8.1/extensions/eo/examples/example-landsat8.json","ITEM","0.8.1","eo|https://example.com/stac/landsat-extension/1.0/schema.json" +"0.8.1/extensions/label/examples/multidataset/catalog.json","CATALOG","0.8.1","" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.8.1","" +"0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.8.1","" +"0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.8.1","" +"0.8.1/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.8.1","label" +"0.8.1/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.8.1","" +"0.8.1/extensions/pointcloud/examples/example-autzen.json","ITEM","0.8.1","pointcloud" +"0.8.1/extensions/sar/examples/envisat.json","ITEM","0.8.1","sar|datetime-range" +"0.8.1/extensions/sar/examples/sentinel1.json","ITEM","0.8.1","checksum|sar|datetime-range" +"0.8.1/extensions/scientific/examples/collection.json","COLLECTION","0.8.1","scientific" +"0.8.1/extensions/scientific/examples/item.json","ITEM","0.8.1","datetime-range|checksum|scientific" +"0.8.1/item-spec/examples/digitalglobe-sample.json","ITEM","0.8.1","eo|https://example.digitalglobe.com/stac/1.0/schema.json" +"0.8.1/item-spec/examples/landsat8-sample.json","ITEM","0.8.1","eo|https://example.com/stac/landsat-extension/1.0/schema.json" +"0.8.1/item-spec/examples/planet-sample.json","ITEM","0.8.1","eo|https://example.planet.com/stac/1.0/schema.json" +"0.8.1/item-spec/examples/sample-full.json","ITEM","0.8.1","eo|https://example.com/cs-extension/1.0/schema.json" +"0.8.1/item-spec/examples/sample.json","ITEM","0.8.1","" +"0.8.1/item-spec/examples/sentinel2-sample.json","ITEM","0.8.1","eo" +"0.9.0/catalog-spec/examples/catalog.json","CATALOG","0.9.0","" +"0.9.0/collection-spec/examples/landsat-collection.json","COLLECTION","0.9.0","commons|view|eo" +"0.9.0/collection-spec/examples/landsat-item.json","ITEM","0.9.0","commons|eo|view|https://example.com/stac/landsat-extension/1.0/schema.json","INVALID" +"0.9.0/collection-spec/examples/sentinel2.json","COLLECTION","0.9.0","" +"0.9.0/extensions/asset/examples/example-landsat8.json","COLLECTION","0.9.0","asset|commons" +"0.9.0/extensions/checksum/examples/sentinel1.json","ITEM","0.9.0","checksum" +"0.9.0/extensions/commons/examples/landsat-collection.json","COLLECTION","0.9.0","commons" +"0.9.0/extensions/commons/examples/landsat-item.json","ITEM","0.9.0","commons|eo|sat|https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/extensions/datacube/examples/example-collection.json","COLLECTION","0.9.0","datacube" +"0.9.0/extensions/datacube/examples/example-item.json","ITEM","0.9.0","datacube" +"0.9.0/extensions/eo/examples/example-landsat8.json","ITEM","0.9.0","eo|view|commons|https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/extensions/label/examples/multidataset/catalog.json","CATALOG","0.9.0","" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","0.9.0","label|version","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","0.9.0","label|version","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","0.9.0","label|version","INVALID" +"0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","0.9.0","" +"0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","0.9.0","" +"0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","0.9.0","label|version","INVALID" +"0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","0.9.0","" +"0.9.0/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","0.9.0","label|version" +"0.9.0/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","0.9.0","" +"0.9.0/extensions/pointcloud/examples/example-autzen.json","ITEM","0.9.0","pointcloud" +"0.9.0/extensions/projection/examples/example-landsat8.json","ITEM","0.9.0","proj|commons" +"0.9.0/extensions/sar/examples/envisat.json","ITEM","0.9.0","sat|sar" +"0.9.0/extensions/sar/examples/sentinel1.json","ITEM","0.9.0","checksum|sar|sat" +"0.9.0/extensions/sat/examples/example-landsat8.json","ITEM","0.9.0","sat|view" +"0.9.0/extensions/scientific/examples/collection.json","COLLECTION","0.9.0","scientific" +"0.9.0/extensions/scientific/examples/item.json","ITEM","0.9.0","scientific|checksum" +"0.9.0/extensions/version/examples/collection.json","COLLECTION","0.9.0","version" +"0.9.0/extensions/version/examples/item.json","ITEM","0.9.0","version","INVALID" +"0.9.0/extensions/view/examples/example-landsat8.json","ITEM","0.9.0","sat|view" +"0.9.0/item-spec/examples/datetimerange.json","ITEM","0.9.0","" +"0.9.0/item-spec/examples/digitalglobe-sample.json","ITEM","0.9.0","eo|proj|view|https://example.digitalglobe.com/stac/1.0/schema.json" +"0.9.0/item-spec/examples/landsat8-sample.json","ITEM","0.9.0","eo|view|https://example.com/stac/landsat-extension/1.0/schema.json" +"0.9.0/item-spec/examples/planet-sample.json","ITEM","0.9.0","eo|view|https://example.planet.com/stac/1.0/schema.json" +"0.9.0/item-spec/examples/sample-full.json","ITEM","0.9.0","eo|view|https://example.com/cs-extension/1.0/schema.json" +"0.9.0/item-spec/examples/sample.json","ITEM","0.9.0","" +"0.9.0/item-spec/examples/sentinel2-sample.json","ITEM","0.9.0","eo|view|proj|commons" +"1.0.0-beta.2/catalog-spec/examples/catalog-items.json","CATALOG","1.0.0-beta.2","" +"1.0.0-beta.2/catalog-spec/examples/catalog.json","CATALOG","1.0.0-beta.2","" +"1.0.0-beta.2/collection-spec/examples/landsat-collection.json","COLLECTION","1.0.0-beta.2","" +"1.0.0-beta.2/collection-spec/examples/sentinel2.json","COLLECTION","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/checksum/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum" +"1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json","COLLECTION","1.0.0-beta.2","collection-assets|https://github.com/NCAR/esm-collection-spec/tree/v0.2.0/schema.json" +"1.0.0-beta.2/extensions/datacube/examples/example-collection.json","COLLECTION","1.0.0-beta.2","datacube" +"1.0.0-beta.2/extensions/datacube/examples/example-item.json","ITEM","1.0.0-beta.2","datacube" +"1.0.0-beta.2/extensions/eo/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|view|https://example.com/stac/landsat-extension/1.0/schema.json" +"1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json","COLLECTION","1.0.0-beta.2","item-assets" +"1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json","CATALOG","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json","COLLECTION","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json","COLLECTION","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json","COLLECTION","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json","ITEM","1.0.0-beta.2","label|version" +"1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json","ITEM","1.0.0-beta.2","" +"1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json","ITEM","1.0.0-beta.2","pointcloud" +"1.0.0-beta.2/extensions/projection/examples/example-landsat8.json","ITEM","1.0.0-beta.2","eo|projection" +"1.0.0-beta.2/extensions/sar/examples/envisat.json","ITEM","1.0.0-beta.2","sat|sar" +"1.0.0-beta.2/extensions/sar/examples/sentinel1.json","ITEM","1.0.0-beta.2","checksum|sar|sat" +"1.0.0-beta.2/extensions/sat/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view" +"1.0.0-beta.2/extensions/scientific/examples/collection.json","COLLECTION","1.0.0-beta.2","scientific" +"1.0.0-beta.2/extensions/scientific/examples/item.json","ITEM","1.0.0-beta.2","scientific|checksum" +"1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json","ITEM","1.0.0-beta.2","datacube|eo|tiled-assets" +"1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json","ITEM","1.0.0-beta.2","eo|tiled-assets" +"1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json","ITEM","1.0.0-beta.2","timestamps" +"1.0.0-beta.2/extensions/version/examples/collection.json","COLLECTION","1.0.0-beta.2","version" +"1.0.0-beta.2/extensions/version/examples/item.json","ITEM","1.0.0-beta.2","version" +"1.0.0-beta.2/extensions/view/examples/example-landsat8.json","ITEM","1.0.0-beta.2","sat|view" +"1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json","ITEM","1.0.0-beta.2","projection|view|https://example.com/stac/cbers-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/datetimerange.json","ITEM","1.0.0-beta.2","" +"1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json","ITEM","1.0.0-beta.2","eo|projection|view|https://example.digitalglobe.com/stac/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/landsat8-sample.json","ITEM","1.0.0-beta.2","eo|view|https://example.com/stac/landsat-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/planet-sample.json","ITEM","1.0.0-beta.2","eo|view|https://example.planet.com/stac/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/sample-full.json","ITEM","1.0.0-beta.2","eo|view|https://example.com/cs-extension/1.0/schema.json" +"1.0.0-beta.2/item-spec/examples/sample.json","ITEM","1.0.0-beta.2","" +"1.0.0-beta.2/item-spec/examples/sentinel2-sample.json","ITEM","1.0.0-beta.2","view|projection" +"gee-0.6.2/CIESIN_GPWv411_GPW_National_Identifier_Grid.json","COLLECTION","0.6.2","scientific" +"gee-0.6.2/LANDSAT_LT05_C01_T1_ANNUAL_NDWI.json","COLLECTION","0.6.2","" +"gee-0.6.2/catalog.json","CATALOG","0.6.2","" +"iserv-0.6.1/2013/03/27/IP0201303271418280967S05834W.json","ITEM","0.6.1","eo" +"iserv-0.6.1/2013/03/27/catalog.json","CATALOG","0.6.1","" +"iserv-0.6.1/2013/03/catalog.json","CATALOG","0.6.1","" +"iserv-0.6.1/2013/catalog.json","CATALOG","0.6.1","" +"iserv-0.6.1/catalog.json","COLLECTION","0.6.1","" +"landsat-0.6.0/010/117/2015-01-02/LC80101172015002LGN00.json","ITEM","0.6.0","eo" +"landsat-0.6.0/010/117/catalog.json","CATALOG","0.6.0","" +"landsat-0.6.0/010/catalog.json","COLLECTION","0.6.0","" +"landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json","ITEM","0.6.0","eo" +"landsat-0.6.0/156/029/catalog.json","CATALOG","0.6.0","" +"landsat-0.6.0/156/catalog.json","COLLECTION","0.6.0","" +"landsat-0.6.0/catalog.json","CATALOG","0.6.0","" +"sentinel-0.6.0/catalog.json","CATALOG","0.6.0","" +"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/2017-10-13/S2B_9VXK_20171013_0.json","ITEM","0.6.0","eo" +"sentinel-0.6.0/sentinel-2-l1c/9/V/XK/catalog.json","CATALOG","0.6.0","" +"sentinel-0.6.0/sentinel-2-l1c/9/V/catalog.json","CATALOG","0.6.0","" +"sentinel-0.6.0/sentinel-2-l1c/9/catalog.json","CATALOG","0.6.0","" +"sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","" +"hand-0.9.0/collection.json","COLLECTION","0.9.0","" +"hand-0.8.1/collection.json","COLLECTION","0.8.1","" \ No newline at end of file diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index 389914470..c95723957 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -7,20 +7,21 @@ import json from tempfile import TemporaryDirectory from subprocess import call +from typing import Any, Dict, List from urllib.error import HTTPError -import pystac -from pystac.serialization import identify_stac_object, STACObjectType +import pystac as ps +from pystac.serialization import identify_stac_object -def remove_bad_collection(js): - links = js.get('links') +def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: + links: List[Dict[str, Any]] = js.get('links') if links is not None: - filtered_links = [] + filtered_links: List[Dict[str, Any]] = [] for link in links: rel = link.get('rel') if rel is not None and rel == 'collection': - href = link['href'] + href: str = link['href'] try: json.loads(ps.STAC_IO.read_text(href)) filtered_links.append(link) @@ -49,7 +50,7 @@ def remove_bad_collection(js): with TemporaryDirectory() as tmp_dir: call(['git', 'clone', '--depth', '1', '--branch', stac_spec_tag, stac_repo, tmp_dir]) - example_dirs = [] + example_dirs: List[str] = [] for root, _, _ in os.walk(tmp_dir): example_dirs.append(os.path.join(root)) @@ -62,7 +63,7 @@ def remove_bad_collection(js): path = os.path.join(root, fname) with open(path) as f: try: - js = json.loads(f.read()) + js: Dict[str, Any] = json.loads(f.read()) except json.decoder.JSONDecodeError: # Account for bad examples that can't be parsed. js = {} @@ -79,7 +80,7 @@ def remove_bad_collection(js): # Handle the case where there are collection links that # don't exist. - if info.object_type == STACObjectType.ITEM: + if info.object_type == ps.STACObjectType.ITEM: js = remove_bad_collection(js) d = os.path.dirname(target_path) @@ -90,9 +91,9 @@ def remove_bad_collection(js): f.write(json.dumps(js, indent=4)) # Add info to the new example-info.csv lines - line_info = [ + line_info: List[str] = [ relpath, info.object_type, example_version, - '|'.join(info.common_extensions), '|'.join(info.custom_extensions) + '|'.join(info.extensions) ] line = '"{}"'.format('","'.join(line_info)) example_csv_lines.add(line) diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index 71b4c09ff..781912732 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -18,8 +18,8 @@ def setUp(self): def test_identify(self): collection_cache = CollectionCache() for example in self.examples: - with self.subTest(example['path']): - path = example['path'] + with self.subTest(example.path): + path = example.path d = STAC_IO.read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: try: @@ -36,14 +36,11 @@ def test_identify(self): msg = 'Failed {}:'.format(path) - self.assertEqual(actual.object_type, example['object_type'], msg=msg) - version_contained_in_range = actual.version_range.contains(example['stac_version']) + self.assertEqual(actual.object_type, example.object_type, msg=msg) + version_contained_in_range = actual.version_range.contains(example.stac_version) self.assertTrue(version_contained_in_range, msg=msg) self.assertEqual(set(actual.extensions), - set(example['common_extensions']), - msg=msg) - self.assertEqual(set(actual.extensions), - set(example['custom_extensions']), + set(example.extensions), msg=msg) diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index 571bab31b..8cc04466d 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -18,8 +18,8 @@ def setUp(self): def test_migrate(self): collection_cache = CollectionCache() for example in self.examples: - with self.subTest(example['path']): - path = example['path'] + with self.subTest(example.path): + path = example.path d = STAC_IO.read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: @@ -35,6 +35,10 @@ def test_migrate(self): self.assertEqual(migrated_info.version_range.latest_valid_version(), ps.get_stac_version()) + # Ensure all stac_extensions are schema URIs + for e_id in migrated_d['stac_extensions']: + self.assertTrue(e_id.endswith('.json'), f"{e_id} is not a JSON schema URI") + # Test that PySTAC can read it without errors. if info.object_type != ps.STACObjectType.ITEMCOLLECTION: self.assertIsInstance(ps.read_dict(migrated_d, href=path), STACObject) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 8cf3cbe16..34ab051bb 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -11,7 +11,7 @@ from pystac.extensions.label import LabelClasses, LabelExtension, LabelType, label_ext from pystac.validation import STACValidationError from pystac.utils import is_absolute_href -from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX, MockStacIO) +from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, MockStacIO) class CatalogTypeTest(unittest.TestCase): @@ -82,8 +82,8 @@ def test_clear_items_removes_from_cache(self): subcat = Catalog(id='subcat', description='test') catalog.add_child(subcat) item = Item(id='test-item', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={'key': 'one'}) subcat.add_item(item) @@ -94,8 +94,8 @@ def test_clear_items_removes_from_cache(self): subcat.clear_items() item = Item(id='test-item', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={'key': 'two'}) subcat.add_item(item) @@ -106,8 +106,8 @@ def test_clear_items_removes_from_cache(self): subcat.remove_item('test-item') item = Item(id='test-item', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={'key': 'three'}) subcat.add_item(item) @@ -343,15 +343,15 @@ def test_generate_subcatalogs_works_after_adding_more_items(self): properties = dict(property1='A', property2=1) catalog.add_item( Item(id='item1', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties=properties)) catalog.generate_subcatalogs('${property1}/${property2}') catalog.add_item( Item(id='item2', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties=properties)) catalog.generate_subcatalogs('${property1}/${property2}') @@ -372,8 +372,8 @@ def test_generate_subcatalogs_works_for_branched_subcatalogs(self): for ni, properties in enumerate(item_properties): catalog.add_item( Item(id='item{}'.format(ni), - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties=properties)) result = catalog.generate_subcatalogs('${property1}/${property2}/${property3}') @@ -394,8 +394,8 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self): for ni, properties in enumerate(item_properties): catalog.add_item( Item(id='item{}'.format(ni), - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties=properties)) result = catalog.generate_subcatalogs('${property1}/${property2}') @@ -470,8 +470,8 @@ def item_mapper(item: ps.Item) -> List[ps.Item]: def test_map_items_multiple_2(self): catalog = Catalog(id='test-1', description='Test1') item1 = Item(id='item1', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) item1.add_asset('ortho', Asset(href='/some/ortho.tif')) @@ -479,8 +479,8 @@ def test_map_items_multiple_2(self): kitten = Catalog(id='test-kitten', description='A cuter version of catalog') catalog.add_child(kitten) item2 = Item(id='item2', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) item2.add_asset('ortho', Asset(href='/some/other/ortho.tif')) @@ -878,8 +878,8 @@ def test_full_copy_1(self): cat = Catalog(id='test', description='test catalog') item = Item(id='test_item', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) @@ -896,8 +896,8 @@ def test_full_copy_2(self): with TemporaryDirectory() as tmp_dir: cat = Catalog(id='test', description='test catalog') image_item = Item(id='Imagery', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) for key in ['ortho', 'dsm']: @@ -905,8 +905,8 @@ def test_full_copy_2(self): key, Asset(href='some/{}.tif'.format(key), media_type=MediaType.GEOTIFF)) label_item = Item(id='Labels', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) LabelExtension.add_to(label_item) diff --git a/tests/test_collection.py b/tests/test_collection.py index 46371d0c2..92f8e801e 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -10,14 +10,14 @@ from pystac.validation import validate_dict from pystac import (Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType) from pystac.utils import datetime_to_str -from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX) +from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX) TEST_DATETIME = datetime(2020, 3, 14, 16, 32) class CollectionTest(unittest.TestCase): def test_spatial_extent_from_coordinates(self): - extent = SpatialExtent.from_coordinates(RANDOM_GEOM['coordinates']) + extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM['coordinates']) self.assertEqual(len(extent.bboxes), 1) bbox = extent.bboxes[0] @@ -101,14 +101,14 @@ def test_update_extents(self): collection = base_collection.clone() item1 = Item(id='test-item-1', - geometry=RANDOM_GEOM, + geometry=ARBITRARY_GEOM, bbox=[-180, -90, 180, 90], datetime=TEST_DATETIME, properties={'key': 'one'}, stac_extensions=['eo', 'commons']) item2 = Item(id='test-item-1', - geometry=RANDOM_GEOM, + geometry=ARBITRARY_GEOM, bbox=[-180, -90, 180, 90], datetime=None, properties={ @@ -138,7 +138,7 @@ def test_update_extents(self): def test_supplying_href_in_init_does_not_fail(self): test_href = "http://example.com/collection.json" - spatial_extent = SpatialExtent(bboxes=[RANDOM_BBOX]) + spatial_extent = SpatialExtent(bboxes=[ARBITRARY_BBOX]) temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) @@ -164,7 +164,7 @@ 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) + spatial_extent = SpatialExtent(bboxes=ARBITRARY_BBOX) collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) @@ -177,13 +177,13 @@ def test_spatial_allows_single_bbox(self): def test_from_items(self): item1 = Item(id='test-item-1', - geometry=RANDOM_GEOM, + geometry=ARBITRARY_GEOM, bbox=[-10, -20, 0, -10], datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), properties={}) item2 = Item(id='test-item-2', - geometry=RANDOM_GEOM, + geometry=ARBITRARY_GEOM, bbox=[0, -9, 10, 1], datetime=None, properties={ @@ -194,7 +194,7 @@ def test_from_items(self): }) item3 = Item(id='test-item-2', - geometry=RANDOM_GEOM, + geometry=ARBITRARY_GEOM, bbox=[-5, -20, 5, 0], datetime=None, properties={ diff --git a/tests/test_layout.py b/tests/test_layout.py index a5a682dd2..af115d863 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -7,7 +7,7 @@ import pystac as ps from pystac.layout import (LayoutTemplate, CustomLayoutStrategy, TemplateLayoutStrategy, BestPracticesLayoutStrategy, TemplateError) -from tests.utils import (TestCases, RANDOM_GEOM, RANDOM_BBOX) +from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX) class LayoutTemplateTest(unittest.TestCase): @@ -20,7 +20,7 @@ def test_templates_item_datetime(self): template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') - item = ps.Item('test', geometry=RANDOM_GEOM, bbox=RANDOM_BBOX, datetime=dt, properties={}) + item = ps.Item('test', geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=dt, properties={}) parts = template.get_template_values(item) @@ -44,8 +44,8 @@ def test_templates_item_start_datetime(self): template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') item = ps.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=None, properties={ 'start_datetime': dt.isoformat(), @@ -96,8 +96,8 @@ def test_nested_properties(self): template = LayoutTemplate('${test.prop}/${ext:extra.test.prop}/item.json') item = ps.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=dt, properties={'test': { 'prop': 4326 @@ -125,8 +125,8 @@ def test_substitute_with_colon_properties(self): template = LayoutTemplate('${ext:prop}/item.json') item = ps.Item('test', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=dt, properties={'ext:prop': 1}) diff --git a/tests/test_link.py b/tests/test_link.py index 411341932..e3b79a027 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -2,7 +2,7 @@ import unittest import pystac as ps -from tests.utils.test_cases import RANDOM_EXTENT +from tests.utils.test_cases import ARBITRARY_EXTENT TEST_DATETIME: datetime.datetime = datetime.datetime(2020, 3, 14, 16, 32) @@ -118,13 +118,13 @@ def test_from_dict_failures(self): ps.Link.from_dict(d) def test_collection(self): - c = ps.Collection('collection id', 'desc', extent=RANDOM_EXTENT) + c = ps.Collection('collection id', 'desc', extent=ARBITRARY_EXTENT) link = ps.Link.collection(c) expected = {'rel': 'collection', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) def test_child(self): - c = ps.Collection('collection id', 'desc', extent=RANDOM_EXTENT) + c = ps.Collection('collection id', 'desc', extent=ARBITRARY_EXTENT) link = ps.Link.child(c) expected = {'rel': 'child', 'href': None, 'type': 'application/json'} self.assertEqual(expected, link.to_dict()) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 055bc6acb..668774c8a 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa -from tests.utils.test_cases import (TestCases, RANDOM_GEOM, RANDOM_BBOX, RANDOM_EXTENT) +from tests.utils.test_cases import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, ARBITRARY_EXTENT) from copy import deepcopy from datetime import datetime diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index b176f0925..36664e66c 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -1,8 +1,10 @@ +from dataclasses import dataclass import os from datetime import datetime import csv from typing import Any, Dict, List +import pystac as ps from pystac import (Catalog, Collection, Item, Asset, Extent, TemporalExtent, SpatialExtent, MediaType) from pystac.extensions.label import (LabelExtension, LabelOverview, LabelClasses, LabelCount, @@ -35,7 +37,7 @@ } } -RANDOM_GEOM: Dict[str, Any] = { +ARBITRARY_GEOM: Dict[str, Any] = { "type": "Polygon", "coordinates": [[[-2.5048828125, 3.8916575492899987], [-1.9610595703125, 3.8916575492899987], @@ -43,13 +45,21 @@ [-2.5048828125, 3.8916575492899987]]] } -RANDOM_BBOX: List[float] = [ - RANDOM_GEOM['coordinates'][0][0][0], RANDOM_GEOM['coordinates'][0][0][1], - RANDOM_GEOM['coordinates'][0][1][0], RANDOM_GEOM['coordinates'][0][1][1] +ARBITRARY_BBOX: List[float] = [ + ARBITRARY_GEOM['coordinates'][0][0][0], ARBITRARY_GEOM['coordinates'][0][0][1], + ARBITRARY_GEOM['coordinates'][0][1][0], ARBITRARY_GEOM['coordinates'][0][1][1] ] -RANDOM_EXTENT = Extent(spatial=SpatialExtent.from_coordinates(RANDOM_GEOM['coordinates']), - temporal=TemporalExtent.from_now()) # noqa: E126 +ARBITRARY_EXTENT = Extent(spatial=SpatialExtent.from_coordinates(ARBITRARY_GEOM['coordinates']), + temporal=TemporalExtent.from_now()) # noqa: E126 + +@dataclass +class ExampleInfo: + path: str + object_type: ps.STACObjectType + stac_version: str + extensions: List[str] + valid: bool class TestCases: @@ -58,8 +68,8 @@ def get_path(rel_path: str) -> str: return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', rel_path)) @staticmethod - def get_examples_info() -> List[Dict[str, Any]]: - examples = [] + def get_examples_info() -> List[ExampleInfo]: + examples: List[ExampleInfo] = [] info_path = TestCases.get_path('data-files/examples/example-info.csv') with open(TestCases.get_path('data-files/examples/example-info.csv')) as f: @@ -67,27 +77,24 @@ def get_examples_info() -> List[Dict[str, Any]]: path = os.path.abspath(os.path.join(os.path.dirname(info_path), row[0])) object_type = row[1] stac_version = row[2] - common_extensions = [] + extensions: List[str] = [] if row[3]: - common_extensions = row[3].split('|') - custom_extensions = [] - if row[4]: - custom_extensions = row[4].split('|') + extensions = row[3].split('|') valid = True - if len(row) > 5: + if len(row) > 4: # The 5th column will be "INVALID" if the example # shouldn't pass validation - valid = row[5] != 'INVALID' - - examples.append({ - 'path': path, - 'object_type': object_type, - 'stac_version': stac_version, - 'common_extensions': common_extensions, - 'custom_extensions': custom_extensions, - 'valid': valid - }) + valid = row[4] != 'INVALID' + + examples.append( + ExampleInfo( + path=path, + object_type=ps.STACObjectType(object_type), + stac_version=stac_version, + extensions=extensions, + valid=valid + )) return examples @staticmethod @@ -115,8 +122,8 @@ def test_case_3() -> Catalog: root_cat = Catalog(id='test3', description='test case 3 catalog', title='test case 3 title') image_item = Item(id='imagery-item', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) @@ -129,8 +136,8 @@ def test_case_3() -> Catalog: ] label_item = Item(id='label-items', - geometry=RANDOM_GEOM, - bbox=RANDOM_BBOX, + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), properties={}) diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 8f0a452ce..82c0e5622 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -35,10 +35,10 @@ def test_validate_current_version(self): def test_validate_examples(self): for example in TestCases.get_examples_info(): - with self.subTest(example['path']): - stac_version = example['stac_version'] - path = example['path'] - valid = example['valid'] + with self.subTest(example.path): + stac_version = example.stac_version + path = example.path + valid = example.valid if stac_version < '0.8': with open(path) as f: @@ -52,7 +52,7 @@ def test_validate_examples(self): # Check if common properties need to be merged if stac_version < '1.0': - if example['object_type'] == ps.STACObjectType.ITEM: + if example.object_type == ps.STACObjectType.ITEM: collection_cache = CollectionCache() merge_common_properties(stac_json, collection_cache, path) From d614036053e6c9fb098c371487b31724cb71ae77 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 10:41:21 -0400 Subject: [PATCH 17/51] Update version schema to fixed published version --- pystac/extensions/version.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 5703027ba..c77f34532 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -14,8 +14,7 @@ T = TypeVar('T', ps.Collection, ps.Item) -# TODO: Modify this to released version with the fix. -SCHEMA_URI = "https://raw.githubusercontent.com/lossyrob/version/fix/rde/schema-id/json-schema/schema.json" +SCHEMA_URI = "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog.json" # STAC fields - These are unusual for an extension in that they do not have # a prefix. e.g. nothing like "ver:" From dc80bb585c447dd13110874c355a1ad1f4d2ed27 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 10:53:35 -0400 Subject: [PATCH 18/51] Migrate checksum -> file --- pystac/extensions/file.py | 38 +++++++++++++++++++++++++++++++- pystac/serialization/identify.py | 2 +- pystac/serialization/migrate.py | 8 ++++++- tests/extensions/test_file.py | 14 +++++++++--- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 36824bd7d..1d2ce5cca 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,5 +1,6 @@ import enum -from typing import Any, Generic, List, Optional, Set, TypeVar, cast +from pystac.serialization.identify import OldExtensionShortIDs, STACJSONDescription, STACVersionID +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension @@ -187,6 +188,40 @@ class FileExtensionHooks(ExtensionHooks): prev_extension_ids: Set[str] = set(['file']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + # The checksum field was previously it's own extension. + old_checksum: Optional[Dict[str, str]] = None + if info.version_range.latest_valid_version() < 'v1.0.0-rc.2': + if OldExtensionShortIDs.CHECKSUM.value in info.extensions: + old_item_checksum = obj['properties'].get('checksum:multihash') + if old_item_checksum is not None: + if old_checksum is None: + old_checksum = {} + old_checksum['__item__'] = old_item_checksum + for asset_key, asset in obj['assets'].items(): + old_asset_checksum = asset.get('checksum:multihash') + if old_asset_checksum is not None: + if old_checksum is None: + old_checksum = {} + old_checksum[asset_key] = old_asset_checksum + + try: + obj['stac_extensions'].remove(OldExtensionShortIDs.CHECKSUM.value) + except ValueError: + pass + + super().migrate(obj, version, info) + + if old_checksum is not None: + if SCHEMA_URI not in obj['stac_extensions']: + obj['stac_extensions'].append(SCHEMA_URI) + for key in old_checksum: + if key == '__item__': + obj['properties'][CHECKSUM_PROP] = old_checksum[key] + else: + obj['assets'][key][CHECKSUM_PROP] = old_checksum[key] + def file_ext(obj: T) -> FileExtension[T]: if isinstance(obj, ps.Item): @@ -200,4 +235,5 @@ def file_ext(obj: T) -> FileExtension[T]: def file_summaries(obj: ps.Collection) -> SummariesFileExtension: return SummariesFileExtension(obj) + FILE_EXTENSION_HOOKS = FileExtensionHooks() \ No newline at end of file diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 30c32cb09..76f7107d2 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -11,7 +11,7 @@ class OldExtensionShortIDs(Enum): """Enumerates the IDs of common extensions.""" - CHECKSUM = 'checksum' # REMOVED + CHECKSUM = 'checksum' COLLECTION_ASSETS = 'collection-assets' # REMOVED DATACUBE = 'datacube' # TODO EO = 'eo' diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 677f1344e..90afab0fd 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -114,7 +114,7 @@ def get_schema_map(cls) -> Dict[str, Any]: })]), OldExtensionShortIDs.POINTCLOUD.value: ( { - # Poincloud schema was broken in 1.0.0-beta.2 and prior; + # Pointcloud schema was broken in 1.0.0-beta.2 and prior; # Use this schema version (corresponding to 1.0.0-rc.1) # to allow for proper validation ps.STACObjectType.ITEM: @@ -253,6 +253,12 @@ def _get_object_migrations( def _get_removed_extension_migrations( ) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: + """Handles removed extensions. + + This does not handle renamed extension or extensions that were absorbed + by other extensions; for instance the FileExtensions handles the migration of + the since replaced 'checksum' extension. + """ return { # Removed in 0.9.0 'dtr': _migrate_datetime_range, diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index 1e4131620..e4b66e6cb 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -3,7 +3,7 @@ import pystac as ps from tests.utils import (TestCases, test_to_from_dict) -from pystac.extensions.file import file_ext, FileDataType +from pystac.extensions.file import FileExtension, file_ext, FileDataType class FileTest(unittest.TestCase): @@ -39,8 +39,7 @@ def test_asset_checksum(self): asset = item.assets["thumbnail"] # Get - self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", - file_ext(asset).checksum) + self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", file_ext(asset).checksum) # Set new_checksum = "90e40210163700a8a6501eccd00b6d3b44ddaed0" @@ -73,3 +72,12 @@ def test_asset_nodata(self): file_ext(asset).nodata = new_nodata self.assertEqual(new_nodata, file_ext(asset).nodata) item.validate() + + def test_migrates_old_checksum(self): + example_path = TestCases.get_path( + 'data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json') + item = ps.Item.from_file(example_path) + + self.assertTrue(FileExtension.has_extension(item)) + self.assertEqual( + file_ext(item.assets['noises']).checksum, "90e40210a30d1711e81a4b11ef67b28744321659") From 77ea376ac71c7328e5a786f1149e65e925250f20 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 19:02:32 -0400 Subject: [PATCH 19/51] Remove collection-assets extension --- pystac/serialization/migrate.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 90afab0fd..3b1c3037f 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -260,7 +260,13 @@ def _get_removed_extension_migrations( the since replaced 'checksum' extension. """ return { - # Removed in 0.9.0 + # -- Removed in 1.0 + + # assets in collections became a core property + OldExtensionShortIDs.COLLECTION_ASSETS.value: lambda a, b, c: None, + + # -- Removed in 0.9.0 + 'dtr': _migrate_datetime_range, 'datetime-range': _migrate_datetime_range, 'commons': lambda a, b, c: None # No changes needed, just remove the extension_id From aa98fde56970702daa3d057d86c571c5bab10566 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 19:02:43 -0400 Subject: [PATCH 20/51] Update STAC version to 1.0.0-rc.3 --- pystac/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pystac/version.py b/pystac/version.py index 8898abd20..6c8f04bf5 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -6,7 +6,7 @@ class STACVersion: - DEFAULT_STAC_VERSION = '1.0.0-rc.2' + DEFAULT_STAC_VERSION = '1.0.0-rc.3' """Latest STAC version supported by PySTAC""" # Version that holds a user-set STAC version to use. From 9b8e1bcd0f12cc379b8650437dbef9965879bcf3 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 19:04:55 -0400 Subject: [PATCH 21/51] Set href during migrate --- tests/data-files/change_stac_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index e379159ac..4c4e57fb7 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -24,7 +24,7 @@ def migrate(path: str) -> None: if not cur_ver == TARGET_VERSION: print(' - Migrating {} from {} to {}...'.format( path, cur_ver, TARGET_VERSION)) - obj = ps.read_dict(stac_json) + obj = ps.read_dict(stac_json, href=path) migrated = obj.to_dict() with open(path, 'w') as f: json.dump(migrated, f, indent=2) From f566aa964a06c94f71c42a22bfd3442bcc9e1d97 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 29 Apr 2021 21:11:56 -0400 Subject: [PATCH 22/51] Change extension API to use static method on extension class This moves from using a package function to a static method on the extension class, e.g. view_ext(item) -> ViewExtension.ext(item) --- pystac/extensions/eo.py | 32 +++--- pystac/extensions/file.py | 26 ++--- pystac/extensions/label.py | 7 +- pystac/extensions/pointcloud.py | 18 ++-- pystac/extensions/projection.py | 16 +-- pystac/extensions/sar.py | 33 +++--- pystac/extensions/sat.py | 17 +-- pystac/extensions/scientific.py | 17 +-- pystac/extensions/timestamps.py | 18 ++-- pystac/extensions/version.py | 18 ++-- pystac/extensions/view.py | 18 ++-- pystac/validation/__init__.py | 4 +- tests/extensions/test_custom.py | 42 +++++--- tests/extensions/test_eo.py | 47 +++++---- tests/extensions/test_file.py | 28 ++--- tests/extensions/test_label.py | 75 ++++++------- tests/extensions/test_pointcloud.py | 31 +++--- tests/extensions/test_projection.py | 158 ++++++++++++++-------------- tests/extensions/test_sar.py | 34 +++--- tests/extensions/test_sat.py | 56 +++++----- tests/extensions/test_scientific.py | 126 +++++++++++----------- tests/extensions/test_timestamps.py | 70 ++++++------ tests/extensions/test_version.py | 114 ++++++++++---------- tests/extensions/test_view.py | 104 +++++++++--------- tests/serialization/test_migrate.py | 11 +- tests/test_catalog.py | 10 +- tests/test_item.py | 19 ++-- tests/utils/test_cases.py | 4 +- 28 files changed, 589 insertions(+), 564 deletions(-) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index b65d057ab..97a459bc8 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -85,7 +85,7 @@ def name(self) -> str: Returns: str """ - return get_required(self.properties['name'], self, 'name') + return get_required(self.properties['name'], self, 'name') @name.setter def name(self, v: str) -> None: @@ -278,6 +278,19 @@ def cloud_cover(self, v: Optional[float]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "EOExtension[T]": + if isinstance(obj, ps.Item): + return cast(EOExtension[T], ItemEOExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(EOExtension[T], AssetEOExtension(obj)) + else: + raise ExtensionException(f"EO extension does not apply to type {type(obj)}") + + @staticmethod + def summaries(obj: ps.Collection) -> "SummariesEOExtension": + return SummariesEOExtension(obj) + class ItemEOExtension(EOExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -340,12 +353,14 @@ def cloud_cover(self) -> Optional[RangeSummary[float]]: def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: self._set_summary(CLOUD_COVER_PROP, v) + class EOExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(['eo']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: if version < '0.5': if 'eo:crs' in obj['properties']: # Try to pull out the EPSG code. @@ -427,17 +442,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDes super().migrate(obj, version, info) -def eo_ext(obj: T) -> EOExtension[T]: - if isinstance(obj, ps.Item): - return cast(EOExtension[T], ItemEOExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(EOExtension[T], AssetEOExtension(obj)) - else: - raise ExtensionException(f"EO extension does not apply to type {type(obj)}") - - -def eo_summaries(obj: ps.Collection) -> SummariesEOExtension: - return SummariesEOExtension(obj) - - EO_EXTENSION_HOOKS: ExtensionHooks = EOExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 1d2ce5cca..503004094 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -124,6 +124,19 @@ def checksum(self, v: Optional[str]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "FileExtension[T]": + if isinstance(obj, ps.Item): + return cast(FileExtension[T], ItemFileExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(FileExtension[T], AssetFileExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + + @staticmethod + def summaries(obj: ps.Collection) -> "SummariesFileExtension": + return SummariesFileExtension(obj) + class ItemFileExtension(FileExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -223,17 +236,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, obj['assets'][key][CHECKSUM_PROP] = old_checksum[key] -def file_ext(obj: T) -> FileExtension[T]: - if isinstance(obj, ps.Item): - return cast(FileExtension[T], ItemFileExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(FileExtension[T], AssetFileExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - - -def file_summaries(obj: ps.Collection) -> SummariesFileExtension: - return SummariesFileExtension(obj) - - FILE_EXTENSION_HOOKS = FileExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 3d24dbbde..052b579c2 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -703,7 +703,8 @@ def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: return ['source'] return None - def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': props = obj['properties'] # Migrate 0.8.0-rc1 non-pluralized forms @@ -727,8 +728,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDes super().migrate(obj, version, info) -def label_ext(item: ps.Item) -> LabelExtension: - return LabelExtension.ext(item) - - LABEL_EXTENSION_HOOKS: ExtensionHooks = LabelExtensionHooks() diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 5bb8f4d09..f1e106a64 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -484,6 +484,15 @@ def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "PointcloudExtension[T]": + if isinstance(obj, ps.Item): + return cast(PointcloudExtension[T], ItemPointcloudExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemPointcloudExtension(PointcloudExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -513,13 +522,4 @@ class PointcloudExtensionHooks(ExtensionHooks): prev_extension_ids: Set[str] = set(['pointcloud']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - -def pointcloud_ext(obj: T) -> PointcloudExtension[T]: - if isinstance(obj, ps.Item): - return cast(PointcloudExtension[T], ItemPointcloudExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - POINTCLOUD_EXTENSION_HOOKS = PointcloudExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 5629929d0..773b89b62 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -239,6 +239,15 @@ def transform(self, v: Optional[List[float]]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "ProjectionExtension[T]": + if isinstance(obj, ps.Item): + return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemProjectionExtension(ProjectionExtension[ps.Item]): def __init__(self, item: ps.Item): self.item = item @@ -263,12 +272,5 @@ class ProjectionExtensionHooks(ExtensionHooks): prev_extension_ids: Set[str] = set(['proj', 'projection']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -def projection_ext(obj: T) -> ProjectionExtension[T]: - if isinstance(obj, ps.Item): - return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") PROJECTION_EXTENSION_HOOKS = ProjectionExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index ff07acaa4..f0b889fab 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -175,10 +175,7 @@ def polarizations(self) -> List[Polarization]: """ return get_required( map_opt(lambda values: [Polarization(v) for v in values], - self._get_property(POLARIZATIONS, List[str])), - self, - POLARIZATIONS - ) + self._get_property(POLARIZATIONS, List[str])), self, POLARIZATIONS) @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: @@ -193,11 +190,7 @@ def product_type(self) -> str: Returns: str """ - return get_required( - self._get_property(POLARIZATIONS, str), - self, - POLARIZATIONS - ) + return get_required(self._get_property(POLARIZATIONS, str), self, POLARIZATIONS) @product_type.setter def product_type(self, v: str) -> None: @@ -291,6 +284,15 @@ def observation_direction(self, v: Optional[ObservationDirection]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "SarExtension[T]": + if isinstance(obj, ps.Item): + return cast(SarExtension[T], ItemSarExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(SarExtension[T], AssetSarExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemSarExtension(SarExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -311,12 +313,14 @@ def __init__(self, asset: ps.Asset): def __repr__(self) -> str: return ''.format(self.asset_href) + class SarExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI prev_extension_ids: Set[str] = set(['sar']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: if version < '0.9': # Some sar fields became common_metadata if 'sar:platform' in obj['properties'] and 'platform' not in obj['properties']: @@ -334,13 +338,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDes super().migrate(obj, version, info) -def sar_ext(obj: T) -> SarExtension[T]: - if isinstance(obj, ps.Item): - return cast(SarExtension[T], ItemSarExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(SarExtension[T], AssetSarExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - - SAR_EXTENSION_HOOKS: ExtensionHooks = SarExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 9e098b63c..bbb377a73 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -86,6 +86,15 @@ def relative_orbit(self, v: Optional[int]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "SatExtension[T]": + if isinstance(obj, ps.Item): + return cast(SatExtension[T], ItemSatExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(SatExtension[T], AssetSatExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemSatExtension(SatExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -113,12 +122,4 @@ class SatExtensionHooks(ExtensionHooks): stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -def sat_ext(obj: T) -> SatExtension[T]: - if isinstance(obj, ps.Item): - return cast(SatExtension[T], ItemSatExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(SatExtension[T], AssetSatExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - SAT_EXTENSION_HOOKS = SatExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 04bcb403f..ff7002b88 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -176,6 +176,15 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "ScientificExtension[T]": + if isinstance(obj, ps.Collection): + return cast(ScientificExtension[T], CollectionScientificExtension(obj)) + if isinstance(obj, ps.Item): + return cast(ScientificExtension[T], ItemScientificExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class CollectionScientificExtension(ScientificExtension[ps.Collection]): def __init__(self, collection: ps.Collection): @@ -206,12 +215,4 @@ class ScientificExtensionHooks(ExtensionHooks): [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) -def scientific_ext(obj: T) -> ScientificExtension[T]: - if isinstance(obj, ps.Collection): - return cast(ScientificExtension[T], CollectionScientificExtension(obj)) - if isinstance(obj, ps.Item): - return cast(ScientificExtension[T], ItemScientificExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - SCIENTIFIC_EXTENSION_HOOKS = ScientificExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 3717ee28c..acf8dfeb2 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -103,6 +103,15 @@ def unpublished(self, v: Optional[Datetime]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "TimestampsExtension[T]": + if isinstance(obj, ps.Item): + return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemTimestampsExtension(TimestampsExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -130,13 +139,4 @@ class TimestampsExtensionHooks(ExtensionHooks): stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -def timestamps_ext(obj: T) -> TimestampsExtension[T]: - if isinstance(obj, ps.Item): - return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - - TIMESTAMPS_EXTENSION_HOOKS = TimestampsExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index c77f34532..d45893efc 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -137,6 +137,15 @@ def successor(self, item: Optional[T]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "VersionExtension[T]": + if isinstance(obj, ps.Collection): + return cast(VersionExtension[T], CollectionVersionExtension(obj)) + if isinstance(obj, ps.Item): + return cast(VersionExtension[T], ItemVersionExtension(obj)) + else: + raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class CollectionVersionExtension(VersionExtension[ps.Collection]): def __init__(self, collection: ps.Collection): @@ -172,13 +181,4 @@ def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: return None -def version_ext(obj: T) -> VersionExtension[T]: - if isinstance(obj, ps.Collection): - return cast(VersionExtension[T], CollectionVersionExtension(obj)) - if isinstance(obj, ps.Item): - return cast(VersionExtension[T], ItemVersionExtension(obj)) - else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") - - VERSION_EXTENSION_HOOKS: ExtensionHooks = VersionExtensionHooks() \ No newline at end of file diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index a4ec6a8de..bcb9b759b 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -14,6 +14,7 @@ SUN_AZIMUTH_PROP = 'view:sun_azimuth' SUN_ELEVATION_PROP = 'view:sun_elevation' + class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """ViewItemExt is the extension of the Item in the View Geometry Extension. View Geometry adds metadata related to angles of sensors and other radiance angles @@ -134,6 +135,15 @@ def sun_elevation(self, v: Optional[float]) -> None: def get_schema_uri(cls) -> str: return SCHEMA_URI + @staticmethod + def ext(obj: T) -> "ViewExtension[T]": + if isinstance(obj, ps.Item): + return cast(ViewExtension[T], ItemViewExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(ViewExtension[T], AssetViewExtension(obj)) + else: + raise ExtensionException(f"View extension does not apply to type {type(obj)}") + class ItemViewExtension(ViewExtension[ps.Item]): def __init__(self, item: ps.Item): @@ -154,17 +164,11 @@ def __init__(self, asset: ps.Asset): def __repr__(self) -> str: return ''.format(self.asset_href) + class ViewExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI prev_extension_ids: Set[str] = set(['view']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -def view_ext(obj: T) -> ViewExtension[T]: - if isinstance(obj, ps.Item): - return cast(ViewExtension[T], ItemViewExtension(obj)) - elif isinstance(obj, ps.Asset): - return cast(ViewExtension[T], AssetViewExtension(obj)) - else: - raise ExtensionException(f"View extension does not apply to type {type(obj)}") VIEW_EXTENSION_HOOKS = ViewExtensionHooks() \ No newline at end of file diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index cb28695cb..464955929 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -90,7 +90,7 @@ def validate_dict(stac_dict: Dict[str, Any], if extensions is None: if info is None: info = identify_stac_object(stac_dict) - extensions = info.common_extensions + extensions = list(info.extensions) return RegisteredValidator.get_validator().validate(stac_dict, stac_object_type, stac_version, extensions, href) @@ -118,7 +118,7 @@ def validate_all(stac_dict: Dict[str, Any], href: str) -> None: validate_dict(stac_dict, stac_object_type=info.object_type, stac_version=str(info.version_range.latest_valid_version()), - extensions=info.common_extensions, + extensions=list(info.extensions), href=href) if info.object_type != ps.STACObjectType.ITEM: diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index e4672c4c8..df1a063b6 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -1,12 +1,13 @@ """Tests creating a custom extension""" +from pystac.collection import RangeSummary from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast import unittest import pystac as ps from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions import ExtensionError -from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension, SummariesExtension from pystac.extensions.hooks import ExtensionHooks @@ -44,6 +45,23 @@ def add_link(self, target: ps.STACObject) -> None: def get_schema_uri(cls) -> str: return super().get_schema_uri() + @staticmethod + def custom_ext(obj: T) -> "CustomExtension[T]": + if isinstance(obj, ps.Asset): + return cast(CustomExtension[T], AssetCustomExtension(obj)) + if isinstance(obj, ps.Item): + return cast(CustomExtension[T], ItemCustomExtension(obj)) + if isinstance(obj, ps.Collection): + return cast(CustomExtension[T], CollectionCustomExtension(obj)) + if isinstance(obj, ps.Catalog): + return cast(CustomExtension[T], CatalogCustomExtension(obj)) + + raise ExtensionError(f'Custom extension does not apply to {type(obj)}') + + @staticmethod + def summaries(obj: ps.Collection) -> "SummariesCustomExtension": + return SummariesCustomExtension(obj) + class CatalogCustomExtension(CustomExtension[ps.Catalog]): def __init__(self, catalog: ps.Catalog) -> None: @@ -77,6 +95,15 @@ def __init__(self, asset: ps.Asset) -> None: self.additional_read_properties = [asset.owner.extra_fields] super().__init__(None) +class SummariesCustomExtension(SummariesExtension): + @property + def test_prop(self) -> Optional[RangeSummary[str]]: + return self.summaries.get_range(TEST_PROP, str) + + @test_prop.setter + def test_prop(self, v: Optional[RangeSummary[str]]) -> None: + self._set_summary(TEST_PROP, v) + class CustomExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI @@ -94,19 +121,6 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDes super().migrate(obj, version, info) -def custom_ext(obj: T) -> CustomExtension[T]: - if isinstance(obj, ps.Asset): - return cast(CustomExtension[T], AssetCustomExtension(obj)) - if isinstance(obj, ps.Item): - return cast(CustomExtension[T], ItemCustomExtension(obj)) - if isinstance(obj, ps.Collection): - return cast(CustomExtension[T], CollectionCustomExtension(obj)) - if isinstance(obj, ps.Catalog): - return cast(CustomExtension[T], CatalogCustomExtension(obj)) - - raise ExtensionError(f'Custom extension does not apply to {type(obj)}') - - class CustomExtensionTest(unittest.TestCase): def setUp(self): ps.EXTENSION_HOOKS.add_extension_hooks(CustomExtensionHooks()) diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index 3033dd5b7..65f5a9983 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -1,11 +1,11 @@ import json -from pystac.collection import RangeSummary import unittest import pystac as ps from pystac import Item +from pystac.collection import RangeSummary from pystac.utils import get_opt -from pystac.extensions.eo import eo_ext, Band, eo_summaries +from pystac.extensions.eo import EOExtension, Band from tests.utils import (TestCases, test_to_from_dict) @@ -33,7 +33,7 @@ def test_bands(self): # Get self.assertIn("eo:bands", item.properties) - bands = eo_ext(item).bands + bands = EOExtension.ext(item).bands assert bands is not None self.assertEqual(list(map(lambda x: x.name, bands)), ['band1', 'band2', 'band3', 'band4']) @@ -44,10 +44,10 @@ def test_bands(self): Band.create(name="blue", description=Band.band_description("blue")), ] - eo_ext(item).bands = new_bands + EOExtension.ext(item).bands = new_bands self.assertEqual('Common name: red, Range: 0.6 to 0.7', item.properties['eo:bands'][0]['description']) - self.assertEqual(len(eo_ext(item).bands or []), 3) + self.assertEqual(len(EOExtension.ext(item).bands or []), 3) item.validate() def test_asset_bands(self): @@ -56,25 +56,25 @@ def test_asset_bands(self): # Get b1_asset = item.assets['B1'] - asset_bands = eo_ext(b1_asset).bands + asset_bands = EOExtension.ext(b1_asset).bands assert asset_bands is not None self.assertEqual(len(asset_bands), 1) self.assertEqual(asset_bands[0].name, 'B1') index_asset = item.assets['index'] - asset_bands = eo_ext(index_asset).bands + asset_bands = EOExtension.ext(index_asset).bands self.assertIs(None, asset_bands) # No asset specified - item_bands = eo_ext(item).bands + item_bands = EOExtension.ext(item).bands self.assertIsNot(None, item_bands) # Set b2_asset = item.assets['B2'] - self.assertEqual(get_opt(eo_ext(b2_asset).bands)[0].name, "B2") - eo_ext(b2_asset).bands = eo_ext(b1_asset).bands + self.assertEqual(get_opt(EOExtension.ext(b2_asset).bands)[0].name, "B2") + EOExtension.ext(b2_asset).bands = EOExtension.ext(b1_asset).bands - new_b2_asset_bands = eo_ext(item.assets['B2']).bands + new_b2_asset_bands = EOExtension.ext(item.assets['B2']).bands self.assertEqual(get_opt(new_b2_asset_bands)[0].name, 'B1') @@ -87,7 +87,7 @@ def test_asset_bands(self): Band.create(name="blue", description=Band.band_description("blue")), ] asset = ps.Asset(href="some/path.tif", media_type=ps.MediaType.GEOTIFF) - eo_ext(asset).bands = new_bands + EOExtension.ext(asset).bands = new_bands item.add_asset("test", asset) self.assertEqual(len(item.assets["test"].properties["eo:bands"]), 3) @@ -97,43 +97,44 @@ def test_cloud_cover(self): # Get self.assertIn("eo:cloud_cover", item.properties) - cloud_cover = eo_ext(item).cloud_cover + cloud_cover = EOExtension.ext(item).cloud_cover self.assertEqual(cloud_cover, 78) # Set - eo_ext(item).cloud_cover = 50 + EOExtension.ext(item).cloud_cover = 50 self.assertEqual(item.properties['eo:cloud_cover'], 50) # Get from Asset b2_asset = item.assets['B2'] - self.assertEqual(eo_ext(b2_asset).cloud_cover, eo_ext(item).cloud_cover) + self.assertEqual(EOExtension.ext(b2_asset).cloud_cover, EOExtension.ext(item).cloud_cover) b3_asset = item.assets['B3'] - self.assertEqual(eo_ext(b3_asset).cloud_cover, 20) + self.assertEqual(EOExtension.ext(b3_asset).cloud_cover, 20) # Set on Asset - eo_ext(b2_asset).cloud_cover = 10 - self.assertEqual(eo_ext(b2_asset).cloud_cover, 10) + EOExtension.ext(b2_asset).cloud_cover = 10 + self.assertEqual(EOExtension.ext(b2_asset).cloud_cover, 10) item.validate() def test_summaries(self): col = ps.Collection.from_file(self.EO_COLLECTION_URI) + eo_summaries = EOExtension.summaries(col) # Get - cloud_cover_summaries = eo_summaries(col).cloud_cover + cloud_cover_summaries = eo_summaries.cloud_cover self.assertEqual(cloud_cover_summaries.minimum, 0.0) self.assertEqual(cloud_cover_summaries.maximum, 80.0) - bands = eo_summaries(col).bands + bands = eo_summaries.bands assert bands is not None self.assertEqual(len(bands), 11) # Set - eo_summaries(col).cloud_cover = RangeSummary(1.0, 2.0) - eo_summaries(col).bands = [Band.create(name='test')] + eo_summaries.cloud_cover = RangeSummary(1.0, 2.0) + eo_summaries.bands = [Band.create(name='test')] col_dict = col.to_dict() self.assertEqual(len(col_dict['summaries']['eo:bands']), 1) @@ -152,7 +153,7 @@ def test_reads_asset_bands_in_pre_1_0_version(self): TestCases.get_path('data-files/examples/0.9.0/item-spec/examples/' 'landsat8-sample.json')) - bands = eo_ext(item.assets['B9']).bands + bands = EOExtension.ext(item.assets['B9']).bands self.assertEqual(len(bands or []), 1) self.assertEqual(get_opt(bands)[0].common_name, 'cirrus') diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index e4b66e6cb..c46b69009 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -3,7 +3,7 @@ import pystac as ps from tests.utils import (TestCases, test_to_from_dict) -from pystac.extensions.file import FileExtension, file_ext, FileDataType +from pystac.extensions.file import FileExtension, FileDataType class FileTest(unittest.TestCase): @@ -26,12 +26,12 @@ def test_asset_size(self): asset = item.assets["thumbnail"] # Get - self.assertEqual(146484, file_ext(asset).size) + self.assertEqual(146484, FileExtension.ext(asset).size) # Set new_size = 1 - file_ext(asset).size = new_size - self.assertEqual(new_size, file_ext(asset).size) + FileExtension.ext(asset).size = new_size + self.assertEqual(new_size, FileExtension.ext(asset).size) item.validate() def test_asset_checksum(self): @@ -39,12 +39,12 @@ def test_asset_checksum(self): asset = item.assets["thumbnail"] # Get - self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", file_ext(asset).checksum) + self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", FileExtension.ext(asset).checksum) # Set new_checksum = "90e40210163700a8a6501eccd00b6d3b44ddaed0" - file_ext(asset).checksum = new_checksum - self.assertEqual(new_checksum, file_ext(asset).checksum) + FileExtension.ext(asset).checksum = new_checksum + self.assertEqual(new_checksum, FileExtension.ext(asset).checksum) item.validate() def test_asset_data_type(self): @@ -52,12 +52,12 @@ def test_asset_data_type(self): asset = item.assets["thumbnail"] # Get - self.assertEqual(FileDataType.UINT8, file_ext(asset).data_type) + self.assertEqual(FileDataType.UINT8, FileExtension.ext(asset).data_type) # Set new_data_type = FileDataType.UINT16 - file_ext(asset).data_type = new_data_type - self.assertEqual(new_data_type, file_ext(asset).data_type) + FileExtension.ext(asset).data_type = new_data_type + self.assertEqual(new_data_type, FileExtension.ext(asset).data_type) item.validate() def test_asset_nodata(self): @@ -65,12 +65,12 @@ def test_asset_nodata(self): asset = item.assets["thumbnail"] # Get - self.assertEqual([], file_ext(asset).nodata) + self.assertEqual([], FileExtension.ext(asset).nodata) # Set new_nodata = [-1] - file_ext(asset).nodata = new_nodata - self.assertEqual(new_nodata, file_ext(asset).nodata) + FileExtension.ext(asset).nodata = new_nodata + self.assertEqual(new_nodata, FileExtension.ext(asset).nodata) item.validate() def test_migrates_old_checksum(self): @@ -80,4 +80,4 @@ def test_migrates_old_checksum(self): self.assertTrue(FileExtension.has_extension(item)) self.assertEqual( - file_ext(item.assets['noises']).checksum, "90e40210a30d1711e81a4b11ef67b28744321659") + FileExtension.ext(item.assets['noises']).checksum, "90e40210a30d1711e81a4b11ef67b28744321659") diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index f161260cd..e86c6c599 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -1,14 +1,14 @@ import json import os -from pystac.extensions.label import LabelClasses, LabelCount, LabelOverview, LabelStatistics, LabelType -from pystac.utils import get_opt import unittest from tempfile import TemporaryDirectory import pystac as ps from pystac import (Catalog, Item, CatalogType, STAC_IO) -from pystac.extensions.label import label_ext +from pystac.extensions.label import (LabelExtension, LabelClasses, LabelCount, LabelOverview, + LabelStatistics, LabelType) import pystac.validation +from pystac.utils import get_opt from tests.utils import (TestCases, test_to_from_dict) @@ -27,12 +27,12 @@ def test_to_from_dict(self): def test_from_file(self): label_example_1 = Item.from_file(self.label_example_1_uri) - overviews = get_opt(label_ext(label_example_1).label_overviews) + overviews = get_opt(LabelExtension.ext(label_example_1).label_overviews) self.assertEqual(len(get_opt(overviews[0].counts)), 2) label_example_1.validate() label_example_2 = Item.from_file(self.label_example_2_uri) - overviews2 = get_opt(label_ext(label_example_2).label_overviews) + overviews2 = get_opt(LabelExtension.ext(label_example_2).label_overviews) self.assertEqual(len(get_opt(overviews2[0].counts)), 2) label_example_2.validate() @@ -51,7 +51,7 @@ def test_from_file_pre_081(self): d['properties'].pop('label:tasks') label_example_1 = ps.Item.from_dict(d, migrate=True) - self.assertEqual(len(label_ext(label_example_1).label_tasks or []), 2) + self.assertEqual(len(LabelExtension.ext(label_example_1).label_tasks or []), 2) def test_get_sources(self): cat = TestCases.test_case_1() @@ -60,8 +60,8 @@ def test_get_sources(self): item_ids = set([i.id for i in items]) for li in items: - if label_ext(li).has_extension: - sources = list(label_ext(li).get_sources() or []) + if LabelExtension.ext(li).has_extension: + sources = list(LabelExtension.ext(li).get_sources() or []) self.assertEqual(len(sources), 1) self.assertTrue(sources[0].id in item_ids) @@ -80,7 +80,8 @@ def test_validate_label(self): label_item_read.validate() def test_read_label_item_owns_asset(self): - item = next(x for x in TestCases.test_case_2().get_all_items() if label_ext(x).has_extension) + item = next(x for x in TestCases.test_case_2().get_all_items() + if LabelExtension.ext(x).has_extension) assert len(item.assets) > 0 for asset_key in item.assets: self.assertEqual(item.assets[asset_key].owner, item) @@ -90,11 +91,11 @@ def test_label_description(self): # Get self.assertIn("label:description", label_item.properties) - label_desc = label_ext(label_item).label_description + label_desc = LabelExtension.ext(label_item).label_description self.assertEqual(label_desc, label_item.properties['label:description']) # Set - label_ext(label_item).label_description = "A detailed description" + LabelExtension.ext(label_item).label_description = "A detailed description" self.assertEqual("A detailed description", label_item.properties['label:description']) label_item.validate() @@ -103,11 +104,11 @@ def test_label_type(self): # Get self.assertIn("label:type", label_item.properties) - label_type = label_ext(label_item).label_type + label_type = LabelExtension.ext(label_item).label_type self.assertEqual(label_type, label_item.properties['label:type']) # Set - label_ext(label_item).label_type = LabelType.RASTER + LabelExtension.ext(label_item).label_type = LabelType.RASTER self.assertEqual(LabelType.RASTER, label_item.properties['label:type']) label_item.validate() @@ -117,20 +118,20 @@ def test_label_properties(self): # Get self.assertIn("label:properties", label_item.properties) - label_prop = label_ext(label_item).label_properties + label_prop = LabelExtension.ext(label_item).label_properties self.assertEqual(label_prop, label_item.properties['label:properties']) - raster_label_prop = label_ext(label_item2).label_properties + raster_label_prop = LabelExtension.ext(label_item2).label_properties self.assertEqual(raster_label_prop, None) # Set - label_ext(label_item).label_properties = ["prop1", "prop2"] + LabelExtension.ext(label_item).label_properties = ["prop1", "prop2"] self.assertEqual(["prop1", "prop2"], label_item.properties['label:properties']) label_item.validate() def test_label_classes(self): # Get label_item = ps.Item.from_file(self.label_example_1_uri) - label_classes = label_ext(label_item).label_classes + label_classes = LabelExtension.ext(label_item).label_classes self.assertEqual(len(get_opt(label_classes)), 2) self.assertEqual(get_opt(label_classes)[1].classes, ["three", "four"]) @@ -141,7 +142,7 @@ def test_label_classes(self): LabelClasses.create(name="label", classes=["seven", "eight"]) ] - label_ext(label_item).label_classes = new_classes + LabelExtension.ext(label_item).label_classes = new_classes self.assertEqual([ class_name for lc in label_item.properties["label:classes"] for class_name in lc["classes"] @@ -154,11 +155,11 @@ def test_label_tasks(self): # Get self.assertIn("label:tasks", label_item.properties) - label_prop = label_ext(label_item).label_tasks + label_prop = LabelExtension.ext(label_item).label_tasks self.assertEqual(label_prop, ["classification", "regression"]) # Set - label_ext(label_item).label_tasks = ["classification"] + LabelExtension.ext(label_item).label_tasks = ["classification"] self.assertEqual(["classification"], label_item.properties['label:tasks']) label_item.validate() @@ -167,21 +168,23 @@ def test_label_methods(self): # Get self.assertIn("label:methods", label_item.properties) - label_prop = label_ext(label_item).label_methods + label_prop = LabelExtension.ext(label_item).label_methods self.assertEqual(label_prop, ["manual"]) # Set - label_ext(label_item).label_methods = ["manual", "automated"] + LabelExtension.ext(label_item).label_methods = ["manual", "automated"] self.assertEqual(["manual", "automated"], label_item.properties['label:methods']) label_item.validate() def test_label_overviews(self): # Get label_item = ps.Item.from_file(self.label_example_1_uri) - label_overviews = get_opt(label_ext(label_item).label_overviews) + label_ext = LabelExtension.ext(label_item) + label_overviews = get_opt(label_ext.label_overviews) label_item2 = ps.Item.from_file(self.label_example_2_uri) - label_overviews2 = get_opt(label_ext(label_item2).label_overviews) + label_ext2 = LabelExtension.ext(label_item2) + label_overviews2 = get_opt(label_ext2.label_overviews) self.assertEqual(len(label_overviews), 2) self.assertEqual(label_overviews[1].property_key, "label-reg") @@ -189,30 +192,30 @@ def test_label_overviews(self): label_counts = get_opt(label_overviews[0].counts) self.assertEqual(label_counts[1].count, 17) - get_opt(label_ext(label_item).label_overviews)[0].counts[1].count = 18 + get_opt(label_ext.label_overviews)[0].counts[1].count = 18 self.assertEqual(label_item.properties['label:overviews'][0]['counts'][1]['count'], 18) - label_statistics = label_overviews[1].statistics + label_statistics = get_opt(label_overviews[1].statistics) self.assertEqual(label_statistics[0].name, "mean") - get_opt(label_ext(label_item).label_overviews)[1].statistics[0].name = "avg" + get_opt(label_ext.label_overviews)[1].statistics[0].name = "avg" self.assertEqual(label_item.properties['label:overviews'][1]['statistics'][0]['name'], "avg") # Set new_overviews = [ LabelOverview.create(property_key="label2", - counts=[ - LabelCount.create(name="one", count=1), - LabelCount.create(name="two", count=1), - ]), + counts=[ + LabelCount.create(name="one", count=1), + LabelCount.create(name="two", count=1), + ]), LabelOverview.create(property_key="label-reg", - statistics=[ - LabelStatistics.create(name="min", value=0.1), - LabelStatistics.create(name="max", value=1.0), - ]) + statistics=[ + LabelStatistics.create(name="min", value=0.1), + LabelStatistics.create(name="max", value=1.0), + ]) ] - label_ext(label_item).label_overviews = new_overviews + label_ext.label_overviews = new_overviews self.assertEqual([(count['name'], count['count']) for count in label_item.properties["label:overviews"][0]['counts']], [("one", 1), ("two", 1)]) diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 27231aadb..d187dd5c1 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -4,7 +4,6 @@ # from copy import deepcopy import pystac as ps -from pystac.extensions.pointcloud import pointcloud_ext from pystac.extensions.pointcloud import PointcloudExtension, PointcloudSchema, PointcloudStatistic from tests.utils import (TestCases, test_to_from_dict) @@ -27,7 +26,7 @@ def test_apply(self): self.assertFalse(PointcloudExtension.has_extension(item)) PointcloudExtension.add_to(item) - pointcloud_ext(item).apply(1000, 'lidar', 'laszip', + PointcloudExtension.ext(item).apply(1000, 'lidar', 'laszip', [PointcloudSchema({ 'name': 'X', 'size': 8, @@ -44,11 +43,11 @@ def test_count(self): # Get self.assertIn("pc:count", pc_item.properties) - pc_count = pointcloud_ext(pc_item).count + pc_count = PointcloudExtension.ext(pc_item).count self.assertEqual(pc_count, pc_item.properties['pc:count']) # Set - pointcloud_ext(pc_item).count = pc_count + 100 + PointcloudExtension.ext(pc_item).count = pc_count + 100 self.assertEqual(pc_count + 100, pc_item.properties['pc:count']) # Validate @@ -58,7 +57,7 @@ def test_count(self): # Ensure setting bad count fails validation with self.assertRaises(ps.STACValidationError): - pointcloud_ext(pc_item).count = 'not_an_int' # type:ignore + PointcloudExtension.ext(pc_item).count = 'not_an_int' # type:ignore pc_item.validate() def test_type(self): @@ -66,11 +65,11 @@ def test_type(self): # Get self.assertIn("pc:type", pc_item.properties) - pc_type = pointcloud_ext(pc_item).type + pc_type = PointcloudExtension.ext(pc_item).type self.assertEqual(pc_type, pc_item.properties['pc:type']) # Set - pointcloud_ext(pc_item).type = 'sonar' + PointcloudExtension.ext(pc_item).type = 'sonar' self.assertEqual('sonar', pc_item.properties['pc:type']) # Validate @@ -81,11 +80,11 @@ def test_encoding(self): # Get self.assertIn("pc:encoding", pc_item.properties) - pc_encoding = pointcloud_ext(pc_item).encoding + pc_encoding = PointcloudExtension.ext(pc_item).encoding self.assertEqual(pc_encoding, pc_item.properties['pc:encoding']) # Set - pointcloud_ext(pc_item).encoding = 'binary' + PointcloudExtension.ext(pc_item).encoding = 'binary' self.assertEqual('binary', pc_item.properties['pc:encoding']) # Validate @@ -96,12 +95,12 @@ def test_schemas(self): # Get self.assertIn("pc:schemas", pc_item.properties) - pc_schemas = [s.to_dict() for s in pointcloud_ext(pc_item).schemas] + pc_schemas = [s.to_dict() for s in PointcloudExtension.ext(pc_item).schemas] self.assertEqual(pc_schemas, pc_item.properties['pc:schemas']) # Set schema = [PointcloudSchema({'name': 'X', 'size': 8, 'type': 'floating'})] - pointcloud_ext(pc_item).schemas = schema + PointcloudExtension.ext(pc_item).schemas = schema self.assertEqual([s.to_dict() for s in schema], pc_item.properties['pc:schemas']) # Validate @@ -112,7 +111,7 @@ def test_statistics(self): # Get self.assertIn("pc:statistics", pc_item.properties) - pc_statistics = [s.to_dict() for s in pointcloud_ext(pc_item).statistics] + pc_statistics = [s.to_dict() for s in PointcloudExtension.ext(pc_item).statistics] self.assertEqual(pc_statistics, pc_item.properties['pc:statistics']) # Set @@ -128,7 +127,7 @@ def test_statistics(self): "variance": 1 }) ] - pointcloud_ext(pc_item).statistics = stats + PointcloudExtension.ext(pc_item).statistics = stats self.assertEqual([s.to_dict() for s in stats], pc_item.properties['pc:statistics']) # Validate @@ -138,11 +137,11 @@ def test_density(self): pc_item = ps.Item.from_file(self.example_uri) # Get self.assertIn("pc:density", pc_item.properties) - pc_density = pointcloud_ext(pc_item).density + pc_density = PointcloudExtension.ext(pc_item).density self.assertEqual(pc_density, pc_item.properties['pc:density']) # Set density = 100 - pointcloud_ext(pc_item).density = density + PointcloudExtension.ext(pc_item).density = density self.assertEqual(density, pc_item.properties['pc:density']) # Validate pc_item.validate @@ -190,4 +189,4 @@ def test_pointcloud_statistics(self): def test_statistics_accessor_when_no_stats(self): pc_item = ps.Item.from_file(self.example_uri_no_statistics) - self.assertEqual(pointcloud_ext(pc_item).statistics, None) + self.assertEqual(PointcloudExtension.ext(pc_item).statistics, None) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 576bc934f..9d97c74bd 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -5,7 +5,7 @@ import pystac as ps from pystac.validation import STACValidationError -from pystac.extensions.projection import ProjectionExtension, projection_ext +from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt from tests.utils import (TestCases, test_to_from_dict) @@ -84,7 +84,7 @@ def test_apply(self): self.assertFalse(ProjectionExtension.has_extension(item)) ProjectionExtension.add_to(item) - projection_ext(item).apply( + ProjectionExtension.ext(item).apply( 4326, wkt2=WKT2, projjson=PROJJSON, @@ -100,9 +100,9 @@ def test_apply(self): def test_partial_apply(self): proj_item = ps.Item.from_file(self.example_uri) - projection_ext(proj_item).apply(epsg=1111) + ProjectionExtension.ext(proj_item).apply(epsg=1111) - self.assertEqual(projection_ext(proj_item).epsg, 1111) + self.assertEqual(ProjectionExtension.ext(proj_item).epsg, 1111) proj_item.validate() def test_validate_proj(self): @@ -114,25 +114,25 @@ def test_epsg(self): # Get self.assertIn("proj:epsg", proj_item.properties) - proj_epsg = projection_ext(proj_item).epsg + proj_epsg = ProjectionExtension.ext(proj_item).epsg self.assertEqual(proj_epsg, proj_item.properties['proj:epsg']) # Set - projection_ext(proj_item).epsg = proj_epsg + 100 + ProjectionExtension.ext(proj_item).epsg = proj_epsg + 100 self.assertEqual(proj_epsg + 100, proj_item.properties['proj:epsg']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).epsg, - projection_ext(proj_item).epsg) - self.assertEqual(projection_ext(asset_prop).epsg, 9999) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, + ProjectionExtension.ext(proj_item).epsg) + self.assertEqual(ProjectionExtension.ext(asset_prop).epsg, 9999) # Set to Asset - projection_ext(asset_no_prop).epsg = 8888 - self.assertNotEqual(projection_ext(asset_no_prop).epsg, - projection_ext(proj_item).epsg) - self.assertEqual(projection_ext(asset_no_prop).epsg, 8888) + ProjectionExtension.ext(asset_no_prop).epsg = 8888 + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).epsg, + ProjectionExtension.ext(proj_item).epsg) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, 8888) # Validate proj_item.validate @@ -142,26 +142,26 @@ def test_wkt2(self): # Get self.assertIn("proj:wkt2", proj_item.properties) - proj_wkt2 = projection_ext(proj_item).wkt2 + proj_wkt2 = ProjectionExtension.ext(proj_item).wkt2 self.assertEqual(proj_wkt2, proj_item.properties['proj:wkt2']) # Set - projection_ext(proj_item).wkt2 = WKT2 + ProjectionExtension.ext(proj_item).wkt2 = WKT2 self.assertEqual(WKT2, proj_item.properties['proj:wkt2']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).wkt2, - projection_ext(proj_item).wkt2) - self.assertTrue('TEST_TEXT' in get_opt(projection_ext(asset_prop).wkt2)) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, + ProjectionExtension.ext(proj_item).wkt2) + self.assertTrue('TEST_TEXT' in get_opt(ProjectionExtension.ext(asset_prop).wkt2)) # Set to Asset asset_value = "TEST TEXT 2" - projection_ext(asset_no_prop).wkt2 = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).wkt2, - projection_ext(proj_item).wkt2) - self.assertEqual(projection_ext(asset_no_prop).wkt2, asset_value) + ProjectionExtension.ext(asset_no_prop).wkt2 = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).wkt2, + ProjectionExtension.ext(proj_item).wkt2) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, asset_value) # Validate proj_item.validate() @@ -171,34 +171,34 @@ def test_projjson(self): # Get self.assertIn("proj:projjson", proj_item.properties) - proj_projjson = projection_ext(proj_item).projjson + proj_projjson = ProjectionExtension.ext(proj_item).projjson self.assertEqual(proj_projjson, proj_item.properties['proj:projjson']) # Set - projection_ext(proj_item).projjson = PROJJSON + ProjectionExtension.ext(proj_item).projjson = PROJJSON self.assertEqual(PROJJSON, proj_item.properties['proj:projjson']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).projjson, - projection_ext(proj_item).projjson) - self.assertEqual(projection_ext(asset_prop).projjson['id']['code'], 9999) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).projjson, + ProjectionExtension.ext(proj_item).projjson) + self.assertEqual(ProjectionExtension.ext(asset_prop).projjson['id']['code'], 9999) # Set to Asset asset_value = deepcopy(PROJJSON) asset_value['id']['code'] = 7777 - projection_ext(asset_no_prop).projjson = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).projjson, - projection_ext(proj_item).projjson) - self.assertEqual(projection_ext(asset_no_prop).projjson['id']['code'], 7777) + ProjectionExtension.ext(asset_no_prop).projjson = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).projjson, + ProjectionExtension.ext(proj_item).projjson) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).projjson['id']['code'], 7777) # Validate proj_item.validate() # Ensure setting bad projjson fails validation with self.assertRaises(STACValidationError): - projection_ext(proj_item).projjson = {"bad": "data"} + ProjectionExtension.ext(proj_item).projjson = {"bad": "data"} proj_item.validate() def test_geometry(self): @@ -206,34 +206,34 @@ def test_geometry(self): # Get self.assertIn("proj:geometry", proj_item.properties) - proj_geometry = projection_ext(proj_item).geometry + proj_geometry = ProjectionExtension.ext(proj_item).geometry self.assertEqual(proj_geometry, proj_item.properties['proj:geometry']) # Set - projection_ext(proj_item).geometry = proj_item.geometry + ProjectionExtension.ext(proj_item).geometry = proj_item.geometry self.assertEqual(proj_item.geometry, proj_item.properties['proj:geometry']) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).geometry, - projection_ext(proj_item).geometry) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, + ProjectionExtension.ext(proj_item).geometry) self.assertEqual( - projection_ext(asset_prop).geometry['coordinates'][0][0], [0.0, 0.0]) + ProjectionExtension.ext(asset_prop).geometry['coordinates'][0][0], [0.0, 0.0]) # Set to Asset asset_value: Dict[str, Any] = {'type': 'Point', 'coordinates': [1.0, 2.0]} - projection_ext(asset_no_prop).geometry = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).geometry, - projection_ext(proj_item).geometry) - self.assertEqual(projection_ext(asset_no_prop).geometry, asset_value) + ProjectionExtension.ext(asset_no_prop).geometry = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).geometry, + ProjectionExtension.ext(proj_item).geometry) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, asset_value) # Validate proj_item.validate() # Ensure setting bad geometry fails validation with self.assertRaises(STACValidationError): - projection_ext(proj_item).geometry = {"bad": "data"} + ProjectionExtension.ext(proj_item).geometry = {"bad": "data"} proj_item.validate() def test_bbox(self): @@ -241,26 +241,26 @@ def test_bbox(self): # Get self.assertIn("proj:bbox", proj_item.properties) - proj_bbox = projection_ext(proj_item).bbox + proj_bbox = ProjectionExtension.ext(proj_item).bbox self.assertEqual(proj_bbox, proj_item.properties['proj:bbox']) # Set - projection_ext(proj_item).bbox = [1.0, 2.0, 3.0, 4.0] + ProjectionExtension.ext(proj_item).bbox = [1.0, 2.0, 3.0, 4.0] self.assertEqual(proj_item.properties['proj:bbox'], [1.0, 2.0, 3.0, 4.0]) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).bbox, - projection_ext(proj_item).bbox) - self.assertEqual(projection_ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, + ProjectionExtension.ext(proj_item).bbox) + self.assertEqual(ProjectionExtension.ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) # Set to Asset asset_value = [10.0, 20.0, 30.0, 40.0] - projection_ext(asset_no_prop).bbox = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).bbox, - projection_ext(proj_item).bbox) - self.assertEqual(projection_ext(asset_no_prop).bbox, asset_value) + ProjectionExtension.ext(asset_no_prop).bbox = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).bbox, + ProjectionExtension.ext(proj_item).bbox) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, asset_value) # Validate proj_item.validate() @@ -270,37 +270,37 @@ def test_centroid(self): # Get self.assertIn("proj:centroid", proj_item.properties) - proj_centroid = projection_ext(proj_item).centroid + proj_centroid = ProjectionExtension.ext(proj_item).centroid self.assertEqual(proj_centroid, proj_item.properties['proj:centroid']) # Set new_val = {'lat': 2.0, 'lon': 3.0} - projection_ext(proj_item).centroid = new_val + ProjectionExtension.ext(proj_item).centroid = new_val self.assertEqual(proj_item.properties['proj:centroid'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).centroid, - projection_ext(proj_item).centroid) - self.assertEqual(projection_ext(asset_prop).centroid, { + self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, + ProjectionExtension.ext(proj_item).centroid) + self.assertEqual(ProjectionExtension.ext(asset_prop).centroid, { "lat": 0.5, "lon": 0.3 }) # Set to Asset asset_value = {"lat": 1.5, "lon": 1.3} - projection_ext(asset_no_prop).centroid = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).centroid, - projection_ext(proj_item).centroid) - self.assertEqual(projection_ext(asset_no_prop).centroid, asset_value) + ProjectionExtension.ext(asset_no_prop).centroid = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).centroid, + ProjectionExtension.ext(proj_item).centroid) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, asset_value) # Validate proj_item.validate() # Ensure setting bad centroid fails validation with self.assertRaises(STACValidationError): - projection_ext(proj_item).centroid = {'lat': 2.0, 'lng': 3.0} + ProjectionExtension.ext(proj_item).centroid = {'lat': 2.0, 'lng': 3.0} proj_item.validate() def test_shape(self): @@ -308,27 +308,27 @@ def test_shape(self): # Get self.assertIn("proj:shape", proj_item.properties) - proj_shape = projection_ext(proj_item).shape + proj_shape = ProjectionExtension.ext(proj_item).shape self.assertEqual(proj_shape, proj_item.properties['proj:shape']) # Set new_val = [100, 200] - projection_ext(proj_item).shape = new_val + ProjectionExtension.ext(proj_item).shape = new_val self.assertEqual(proj_item.properties['proj:shape'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).shape, - projection_ext(proj_item).shape) - self.assertEqual(projection_ext(asset_prop).shape, [16781, 16621]) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, + ProjectionExtension.ext(proj_item).shape) + self.assertEqual(ProjectionExtension.ext(asset_prop).shape, [16781, 16621]) # Set to Asset asset_value = [1, 2] - projection_ext(asset_no_prop).shape = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).shape, - projection_ext(proj_item).shape) - self.assertEqual(projection_ext(asset_no_prop).shape, asset_value) + ProjectionExtension.ext(asset_no_prop).shape = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).shape, + ProjectionExtension.ext(proj_item).shape) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, asset_value) # Validate proj_item.validate() @@ -338,28 +338,28 @@ def test_transform(self): # Get self.assertIn("proj:transform", proj_item.properties) - proj_transform = projection_ext(proj_item).transform + proj_transform = ProjectionExtension.ext(proj_item).transform self.assertEqual(proj_transform, proj_item.properties['proj:transform']) # Set new_val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - projection_ext(proj_item).transform = new_val + ProjectionExtension.ext(proj_item).transform = new_val self.assertEqual(proj_item.properties['proj:transform'], new_val) # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(projection_ext(asset_no_prop).transform, - projection_ext(proj_item).transform) - self.assertEqual(projection_ext(asset_prop).transform, + self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, + ProjectionExtension.ext(proj_item).transform) + self.assertEqual(ProjectionExtension.ext(asset_prop).transform, [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0]) # Set to Asset asset_value = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0] - projection_ext(asset_no_prop).transform = asset_value - self.assertNotEqual(projection_ext(asset_no_prop).transform, - projection_ext(proj_item).transform) - self.assertEqual(projection_ext(asset_no_prop).transform, asset_value) + ProjectionExtension.ext(asset_no_prop).transform = asset_value + self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).transform, + ProjectionExtension.ext(proj_item).transform) + self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, asset_value) # Validate proj_item.validate() diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index ebe1c35b1..ce00e84d1 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -6,7 +6,7 @@ import pystac as ps from pystac.extensions import sar -from pystac.extensions.sar import sar_ext, SarExtension +from pystac.extensions.sar import SarExtension def make_item() -> ps.Item: @@ -31,17 +31,17 @@ def test_required(self): frequency_band: sar.FrequencyBand = sar.FrequencyBand.P polarizations: List[sar.Polarization] = [sar.Polarization.HV, sar.Polarization.VH] product_type: str = 'Some product' - sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type) - self.assertEqual(mode, sar_ext(self.item).instrument_mode) + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type) + self.assertEqual(mode, SarExtension.ext(self.item).instrument_mode) self.assertIn(sar.INSTRUMENT_MODE, self.item.properties) - self.assertEqual(frequency_band, sar_ext(self.item).frequency_band) + self.assertEqual(frequency_band, SarExtension.ext(self.item).frequency_band) self.assertIn(sar.FREQUENCY_BAND, self.item.properties) - self.assertEqual(polarizations, sar_ext(self.item).polarizations) + self.assertEqual(polarizations, SarExtension.ext(self.item).polarizations) self.assertIn(sar.POLARIZATIONS, self.item.properties) - self.assertEqual(product_type, sar_ext(self.item).product_type) + self.assertEqual(product_type, SarExtension.ext(self.item).product_type) self.assertIn(sar.PRODUCT_TYPE, self.item.properties) self.item.validate() @@ -61,36 +61,36 @@ def test_all(self): looks_equivalent_number: float = 9.1 observation_direction: sar.ObservationDirection = sar.ObservationDirection.LEFT - sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type, + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type, center_frequency, resolution_range, resolution_azimuth, pixel_spacing_range, pixel_spacing_azimuth, looks_range, looks_azimuth, looks_equivalent_number, observation_direction) - self.assertEqual(center_frequency, sar_ext(self.item).center_frequency) + self.assertEqual(center_frequency, SarExtension.ext(self.item).center_frequency) self.assertIn(sar.CENTER_FREQUENCY, self.item.properties) - self.assertEqual(resolution_range, sar_ext(self.item).resolution_range) + self.assertEqual(resolution_range, SarExtension.ext(self.item).resolution_range) self.assertIn(sar.RESOLUTION_RANGE, self.item.properties) - self.assertEqual(resolution_azimuth, sar_ext(self.item).resolution_azimuth) + self.assertEqual(resolution_azimuth, SarExtension.ext(self.item).resolution_azimuth) self.assertIn(sar.RESOLUTION_AZIMUTH, self.item.properties) - self.assertEqual(pixel_spacing_range, sar_ext(self.item).pixel_spacing_range) + self.assertEqual(pixel_spacing_range, SarExtension.ext(self.item).pixel_spacing_range) self.assertIn(sar.PIXEL_SPACING_RANGE, self.item.properties) - self.assertEqual(pixel_spacing_azimuth, sar_ext(self.item).pixel_spacing_azimuth) + self.assertEqual(pixel_spacing_azimuth, SarExtension.ext(self.item).pixel_spacing_azimuth) self.assertIn(sar.PIXEL_SPACING_AZIMUTH, self.item.properties) - self.assertEqual(looks_range, sar_ext(self.item).looks_range) + self.assertEqual(looks_range, SarExtension.ext(self.item).looks_range) self.assertIn(sar.LOOKS_RANGE, self.item.properties) - self.assertEqual(looks_azimuth, sar_ext(self.item).looks_azimuth) + self.assertEqual(looks_azimuth, SarExtension.ext(self.item).looks_azimuth) self.assertIn(sar.LOOKS_AZIMUTH, self.item.properties) - self.assertEqual(looks_equivalent_number, sar_ext(self.item).looks_equivalent_number) + self.assertEqual(looks_equivalent_number, SarExtension.ext(self.item).looks_equivalent_number) self.assertIn(sar.LOOKS_EQUIVALENT_NUMBER, self.item.properties) - self.assertEqual(observation_direction, sar_ext(self.item).observation_direction) + self.assertEqual(observation_direction, SarExtension.ext(self.item).observation_direction) self.assertIn(sar.OBSERVATION_DIRECTION, self.item.properties) self.item.validate() @@ -102,7 +102,7 @@ def test_polarization_must_be_list(self): polarizations = sar.Polarization.HV product_type: str = 'Some product' with self.assertRaises(ps.STACError): - sar_ext(self.item).apply(mode, frequency_band, polarizations, product_type) # type:ignore + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type) # type:ignore if __name__ == '__main__': diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index 76495d5d3..acfb4daef 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -7,7 +7,7 @@ import pystac as ps from pystac.extensions import sat -from pystac.extensions.sat import sat_ext, SatExtension +from pystac.extensions.sat import SatExtension def make_item() -> ps.Item: @@ -29,49 +29,49 @@ def test_stac_extensions(self): self.assertTrue(SatExtension.has_extension(self.item)) def test_no_args_fails(self): - sat_ext(self.item).apply() + SatExtension.ext(self.item).apply() with self.assertRaises(STACValidationError): self.item.validate() def test_orbit_state(self): orbit_state = sat.OrbitState.ASCENDING - sat_ext(self.item).apply(orbit_state) - self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) + SatExtension.ext(self.item).apply(orbit_state) + self.assertEqual(orbit_state, SatExtension.ext(self.item).orbit_state) self.assertNotIn(sat.RELATIVE_ORBIT, self.item.properties) - self.assertFalse(sat_ext(self.item).relative_orbit) + self.assertFalse(SatExtension.ext(self.item).relative_orbit) self.item.validate() def test_relative_orbit(self): relative_orbit = 1234 - sat_ext(self.item).apply(None, relative_orbit) - self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) + SatExtension.ext(self.item).apply(None, relative_orbit) + self.assertEqual(relative_orbit, SatExtension.ext(self.item).relative_orbit) self.assertNotIn(sat.ORBIT_STATE, self.item.properties) - self.assertFalse(sat_ext(self.item).orbit_state) + self.assertFalse(SatExtension.ext(self.item).orbit_state) self.item.validate() def test_relative_orbit_no_negative(self): negative_relative_orbit = -2 - sat_ext(self.item).apply(None, negative_relative_orbit) + SatExtension.ext(self.item).apply(None, negative_relative_orbit) with self.assertRaises(STACValidationError): self.item.validate() def test_both(self): orbit_state = sat.OrbitState.DESCENDING relative_orbit = 4321 - sat_ext(self.item).apply(orbit_state, relative_orbit) - self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) - self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) + SatExtension.ext(self.item).apply(orbit_state, relative_orbit) + self.assertEqual(orbit_state, SatExtension.ext(self.item).orbit_state) + self.assertEqual(relative_orbit, SatExtension.ext(self.item).relative_orbit) self.item.validate() def test_modify(self): - sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) + SatExtension.ext(self.item).apply(sat.OrbitState.DESCENDING, 999) orbit_state = sat.OrbitState.GEOSTATIONARY - sat_ext(self.item).orbit_state = orbit_state + SatExtension.ext(self.item).orbit_state = orbit_state relative_orbit = 1000 - sat_ext(self.item).relative_orbit = relative_orbit - self.assertEqual(orbit_state, sat_ext(self.item).orbit_state) - self.assertEqual(relative_orbit, sat_ext(self.item).relative_orbit) + SatExtension.ext(self.item).relative_orbit = relative_orbit + self.assertEqual(orbit_state, SatExtension.ext(self.item).orbit_state) + self.assertEqual(relative_orbit, SatExtension.ext(self.item).relative_orbit) self.item.validate() def test_from_dict(self): @@ -92,31 +92,31 @@ def test_from_dict(self): 'stac_extensions': ['sat'] } item = ps.Item.from_dict(d) - self.assertEqual(orbit_state, sat_ext(item).orbit_state) - self.assertEqual(relative_orbit, sat_ext(item).relative_orbit) + self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) + self.assertEqual(relative_orbit, SatExtension.ext(item).relative_orbit) def test_to_from_dict(self): orbit_state = sat.OrbitState.GEOSTATIONARY relative_orbit = 1002 - sat_ext(self.item).apply(orbit_state, relative_orbit) + SatExtension.ext(self.item).apply(orbit_state, relative_orbit) d = self.item.to_dict() self.assertEqual(orbit_state.value, d['properties'][sat.ORBIT_STATE]) self.assertEqual(relative_orbit, d['properties'][sat.RELATIVE_ORBIT]) item = ps.Item.from_dict(d) - self.assertEqual(orbit_state, sat_ext(item).orbit_state) - self.assertEqual(relative_orbit, sat_ext(item).relative_orbit) + self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) + self.assertEqual(relative_orbit, SatExtension.ext(item).relative_orbit) def test_clear_orbit_state(self): - sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) + SatExtension.ext(self.item).apply(sat.OrbitState.DESCENDING, 999) - sat_ext(self.item).orbit_state = None - self.assertIsNone(sat_ext(self.item).orbit_state) + SatExtension.ext(self.item).orbit_state = None + self.assertIsNone(SatExtension.ext(self.item).orbit_state) self.item.validate() def test_clear_relative_orbit(self): - sat_ext(self.item).apply(sat.OrbitState.DESCENDING, 999) + SatExtension.ext(self.item).apply(sat.OrbitState.DESCENDING, 999) - sat_ext(self.item).relative_orbit = None - self.assertIsNone(sat_ext(self.item).relative_orbit) + SatExtension.ext(self.item).relative_orbit = None + self.assertIsNone(SatExtension.ext(self.item).relative_orbit) self.item.validate() diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index adc917e02..0c5d43be9 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -5,7 +5,7 @@ import pystac as ps from pystac.extensions import scientific -from pystac.extensions.scientific import scientific_ext, ScientificExtension +from pystac.extensions.scientific import ScientificExtension URL_TEMPLATE = 'http://example.com/catalog/%s.json' @@ -46,8 +46,8 @@ def test_stac_extensions(self): self.assertTrue(ScientificExtension.has_extension(self.item)) def test_doi(self): - scientific_ext(self.item).apply(DOI) - self.assertEqual(DOI, scientific_ext(self.item).doi) + ScientificExtension.ext(self.item).apply(DOI) + self.assertEqual(DOI, ScientificExtension.ext(self.item).doi) self.assertIn(scientific.DOI, self.item.properties) link = self.item.get_links(scientific.CITE_AS)[0] self.assertEqual(DOI_URL, link.get_href()) @@ -56,29 +56,29 @@ def test_doi(self): # Check that setting the doi does not cause extra links. # Same doi. - scientific_ext(self.item).doi = DOI + ScientificExtension.ext(self.item).doi = DOI self.assertEqual(1, len(self.item.get_links(scientific.CITE_AS))) self.item.validate() # Different doi. - scientific_ext(self.item).doi = PUB1_DOI + ScientificExtension.ext(self.item).doi = PUB1_DOI self.assertEqual(1, len(self.item.get_links(scientific.CITE_AS))) link = self.item.get_links(scientific.CITE_AS)[0] self.assertEqual(PUB1_DOI_URL, link.get_href()) self.item.validate() def test_citation(self): - scientific_ext(self.item).apply(citation=CITATION) - self.assertEqual(CITATION, scientific_ext(self.item).citation) + ScientificExtension.ext(self.item).apply(citation=CITATION) + self.assertEqual(CITATION, ScientificExtension.ext(self.item).citation) self.assertIn(scientific.CITATION, self.item.properties) self.assertFalse(self.item.get_links(scientific.CITE_AS)) self.item.validate() def test_publications_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.item).apply(publications=publications) + ScientificExtension.ext(self.item).apply(publications=publications) self.assertEqual([1], [int('1')]) - self.assertEqual(publications, scientific_ext(self.item).publications) + self.assertEqual(publications, ScientificExtension.ext(self.item).publications) self.assertIn(scientific.PUBLICATIONS, self.item.properties) links = self.item.get_links(scientific.CITE_AS) @@ -88,8 +88,8 @@ def test_publications_one(self): self.item.validate() def test_publications(self): - scientific_ext(self.item).apply(publications=PUBLICATIONS) - self.assertEqual(PUBLICATIONS, scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).apply(publications=PUBLICATIONS) + self.assertEqual(PUBLICATIONS, ScientificExtension.ext(self.item).publications) self.assertIn(scientific.PUBLICATIONS, self.item.properties) links = self.item.get_links(scientific.CITE_AS) @@ -100,9 +100,9 @@ def test_publications(self): def test_remove_publication_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.item).apply(DOI, publications=publications) - scientific_ext(self.item).remove_publication(publications[0]) - self.assertFalse(scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).apply(DOI, publications=publications) + ScientificExtension.ext(self.item).remove_publication(publications[0]) + self.assertFalse(ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -110,61 +110,61 @@ def test_remove_publication_one(self): def test_remove_all_publications_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.item).apply(DOI, publications=publications) - scientific_ext(self.item).remove_publication() - self.assertFalse(scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).apply(DOI, publications=publications) + ScientificExtension.ext(self.item).remove_publication() + self.assertFalse(ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_publication_forward(self): - scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.item).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.item).remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[0]) + self.assertEqual([PUBLICATIONS[1]], ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) self.assertEqual(PUB2_DOI_URL, links[1].target) self.item.validate() - scientific_ext(self.item).remove_publication(PUBLICATIONS[1]) - self.assertFalse(scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[1]) + self.assertFalse(ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_publication_reverse(self): - scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.item).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.item).remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[1]) + self.assertEqual([PUBLICATIONS[0]], ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) self.item.validate() - scientific_ext(self.item).remove_publication(PUBLICATIONS[0]) + ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[0]) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_all_publications_with_some(self): - scientific_ext(self.item).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.item).remove_publication() - self.assertFalse(scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.item).remove_publication() + self.assertFalse(ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.item.validate() def test_remove_all_publications_with_none(self): - scientific_ext(self.item).apply(DOI) - scientific_ext(self.item).remove_publication() - self.assertFalse(scientific_ext(self.item).publications) + ScientificExtension.ext(self.item).apply(DOI) + ScientificExtension.ext(self.item).remove_publication() + self.assertFalse(ScientificExtension.ext(self.item).publications) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -195,8 +195,8 @@ def test_stac_extensions(self): self.assertTrue(ScientificExtension.has_extension(self.collection)) def test_doi(self): - scientific_ext(self.collection).apply(DOI) - self.assertEqual(DOI, scientific_ext(self.collection).doi) + ScientificExtension.ext(self.collection).apply(DOI) + self.assertEqual(DOI, ScientificExtension.ext(self.collection).doi) self.assertIn(scientific.DOI, self.collection.extra_fields) link = self.collection.get_links(scientific.CITE_AS)[0] self.assertEqual(DOI_URL, link.get_href()) @@ -205,28 +205,28 @@ def test_doi(self): # Check that setting the doi does not cause extra links. # Same doi. - scientific_ext(self.collection).doi = DOI + ScientificExtension.ext(self.collection).doi = DOI self.assertEqual(1, len(self.collection.get_links(scientific.CITE_AS))) self.collection.validate() # Different doi. - scientific_ext(self.collection).doi = PUB1_DOI + ScientificExtension.ext(self.collection).doi = PUB1_DOI self.assertEqual(1, len(self.collection.get_links(scientific.CITE_AS))) link = self.collection.get_links(scientific.CITE_AS)[0] self.assertEqual(PUB1_DOI_URL, link.get_href()) self.collection.validate() def test_citation(self): - scientific_ext(self.collection).apply(citation=CITATION) - self.assertEqual(CITATION, scientific_ext(self.collection).citation) + ScientificExtension.ext(self.collection).apply(citation=CITATION) + self.assertEqual(CITATION, ScientificExtension.ext(self.collection).citation) self.assertIn(scientific.CITATION, self.collection.extra_fields) self.assertFalse(self.collection.get_links(scientific.CITE_AS)) self.collection.validate() def test_publications_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.collection).apply(publications=publications) - self.assertEqual(publications, scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(publications=publications) + self.assertEqual(publications, ScientificExtension.ext(self.collection).publications) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -237,8 +237,8 @@ def test_publications_one(self): self.collection.validate() def test_publications(self): - scientific_ext(self.collection).apply(publications=PUBLICATIONS) - self.assertEqual(PUBLICATIONS, scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(publications=PUBLICATIONS) + self.assertEqual(PUBLICATIONS, ScientificExtension.ext(self.collection).publications) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -250,9 +250,9 @@ def test_publications(self): def test_remove_publication_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.collection).apply(DOI, publications=publications) - scientific_ext(self.collection).remove_publication(publications[0]) - self.assertFalse(scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(DOI, publications=publications) + ScientificExtension.ext(self.collection).remove_publication(publications[0]) + self.assertFalse(ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -260,61 +260,61 @@ def test_remove_publication_one(self): def test_remove_all_publications_one(self): publications = PUBLICATIONS[:1] - scientific_ext(self.collection).apply(DOI, publications=publications) - scientific_ext(self.collection).remove_publication() - self.assertFalse(scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(DOI, publications=publications) + ScientificExtension.ext(self.collection).remove_publication() + self.assertFalse(ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_publication_forward(self): - scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.collection).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.collection).remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[0]) + self.assertEqual([PUBLICATIONS[1]], ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) self.assertEqual(PUB2_DOI_URL, links[1].target) self.collection.validate() - scientific_ext(self.collection).remove_publication(PUBLICATIONS[1]) - self.assertFalse(scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[1]) + self.assertFalse(ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_publication_reverse(self): - scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.collection).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.collection).remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[1]) + self.assertEqual([PUBLICATIONS[0]], ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) self.collection.validate() - scientific_ext(self.collection).remove_publication(PUBLICATIONS[0]) + ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[0]) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_all_publications_with_some(self): - scientific_ext(self.collection).apply(DOI, publications=PUBLICATIONS) - scientific_ext(self.collection).remove_publication() - self.assertFalse(scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(DOI, publications=PUBLICATIONS) + ScientificExtension.ext(self.collection).remove_publication() + self.assertFalse(ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) self.collection.validate() def test_remove_all_publications_with_none(self): - scientific_ext(self.collection).apply(DOI) - scientific_ext(self.collection).remove_publication() - self.assertFalse(scientific_ext(self.collection).publications) + ScientificExtension.ext(self.collection).apply(DOI) + ScientificExtension.ext(self.collection).remove_publication() + self.assertFalse(ScientificExtension.ext(self.collection).publications) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(1, len(links)) self.assertEqual(DOI_URL, links[0].target) diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index cb078325c..90c59f872 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -3,7 +3,7 @@ from datetime import datetime import pystac as ps -from pystac.extensions.timestamps import timestamps_ext, TimestampsExtension +from pystac.extensions.timestamps import TimestampsExtension from pystac.utils import (get_opt, str_to_datetime, datetime_to_str) from tests.utils import (TestCases, test_to_from_dict) @@ -26,14 +26,14 @@ def test_apply(self): TimestampsExtension.add_to(item) self.assertTrue(TimestampsExtension.has_extension(item)) - timestamps_ext(item).apply(published=str_to_datetime("2020-01-03T06:45:55Z"), + TimestampsExtension.ext(item).apply(published=str_to_datetime("2020-01-03T06:45:55Z"), expires=str_to_datetime("2020-02-03T06:45:55Z"), unpublished=str_to_datetime("2020-03-03T06:45:55Z")) for d in [ - timestamps_ext(item).published, - timestamps_ext(item).expires, - timestamps_ext(item).unpublished + TimestampsExtension.ext(item).published, + TimestampsExtension.ext(item).expires, + TimestampsExtension.ext(item).unpublished ]: self.assertIsInstance(d, datetime) @@ -41,11 +41,11 @@ def test_apply(self): self.assertIsInstance(item.properties[p], str) published_str = "2020-04-03T06:45:55Z" - timestamps_ext(item).apply(published=str_to_datetime(published_str)) - self.assertIsInstance(timestamps_ext(item).published, datetime) + TimestampsExtension.ext(item).apply(published=str_to_datetime(published_str)) + self.assertIsInstance(TimestampsExtension.ext(item).published, datetime) self.assertEqual(item.properties['published'], published_str) - for d in [timestamps_ext(item).expires, timestamps_ext(item).unpublished]: + for d in [TimestampsExtension.ext(item).expires, TimestampsExtension.ext(item).unpublished]: self.assertIsNone(d) for p in ('expires', 'unpublished'): @@ -60,29 +60,29 @@ def test_expires(self): # Get self.assertIn("expires", timestamps_item.properties) - timestamps_expires = timestamps_ext(timestamps_item).expires + timestamps_expires = TimestampsExtension.ext(timestamps_item).expires self.assertIsInstance(timestamps_expires, datetime) self.assertEqual(datetime_to_str(get_opt(timestamps_expires)), timestamps_item.properties['expires']) # Set - timestamps_ext(timestamps_item).expires = self.sample_datetime + TimestampsExtension.ext(timestamps_item).expires = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['expires']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_ext(asset_no_prop).expires, - timestamps_ext(timestamps_item).expires) - self.assertEqual(timestamps_ext(asset_prop).expires, + self.assertEqual(TimestampsExtension.ext(asset_no_prop).expires, + TimestampsExtension.ext(timestamps_item).expires) + self.assertEqual(TimestampsExtension.ext(asset_prop).expires, str_to_datetime("2018-12-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_ext(asset_no_prop).expires =asset_value - self.assertNotEqual(timestamps_ext(asset_no_prop).expires, - timestamps_ext(timestamps_item).expires) - self.assertEqual(timestamps_ext(asset_no_prop).expires, asset_value) + TimestampsExtension.ext(asset_no_prop).expires =asset_value + self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).expires, + TimestampsExtension.ext(timestamps_item).expires) + self.assertEqual(TimestampsExtension.ext(asset_no_prop).expires, asset_value) # Validate timestamps_item.validate() @@ -92,29 +92,29 @@ def test_published(self): # Get self.assertIn("published", timestamps_item.properties) - timestamps_published = timestamps_ext(timestamps_item).published + timestamps_published = TimestampsExtension.ext(timestamps_item).published self.assertIsInstance(timestamps_published, datetime) self.assertEqual(datetime_to_str(get_opt(timestamps_published)), timestamps_item.properties['published']) # Set - timestamps_ext(timestamps_item).published = self.sample_datetime + TimestampsExtension.ext(timestamps_item).published = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['published']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_ext(asset_no_prop).published, - timestamps_ext(timestamps_item).published) - self.assertEqual(timestamps_ext(asset_prop).published, + self.assertEqual(TimestampsExtension.ext(asset_no_prop).published, + TimestampsExtension.ext(timestamps_item).published) + self.assertEqual(TimestampsExtension.ext(asset_prop).published, str_to_datetime("2018-11-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_ext(asset_no_prop).published = asset_value - self.assertNotEqual(timestamps_ext(asset_no_prop).published, - timestamps_ext(timestamps_item).published) - self.assertEqual(timestamps_ext(asset_no_prop).published, asset_value) + TimestampsExtension.ext(asset_no_prop).published = asset_value + self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).published, + TimestampsExtension.ext(timestamps_item).published) + self.assertEqual(TimestampsExtension.ext(asset_no_prop).published, asset_value) # Validate timestamps_item.validate() @@ -124,27 +124,27 @@ def test_unpublished(self): # Get self.assertNotIn("unpublished", timestamps_item.properties) - timestamps_unpublished = timestamps_ext(timestamps_item).unpublished + timestamps_unpublished = TimestampsExtension.ext(timestamps_item).unpublished self.assertIsNone(timestamps_unpublished, datetime) # Set - timestamps_ext(timestamps_item).unpublished = self.sample_datetime + TimestampsExtension.ext(timestamps_item).unpublished = self.sample_datetime self.assertEqual(self.sample_datetime_str, timestamps_item.properties['unpublished']) # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(timestamps_ext(asset_no_prop).unpublished, - timestamps_ext(timestamps_item).unpublished) - self.assertEqual(timestamps_ext(asset_prop).unpublished, + self.assertEqual(TimestampsExtension.ext(asset_no_prop).unpublished, + TimestampsExtension.ext(timestamps_item).unpublished) + self.assertEqual(TimestampsExtension.ext(asset_prop).unpublished, str_to_datetime("2019-01-02T00:00:00Z")) # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - timestamps_ext(asset_no_prop).unpublished = asset_value - self.assertNotEqual(timestamps_ext(asset_no_prop).unpublished, - timestamps_ext(timestamps_item).unpublished) - self.assertEqual(timestamps_ext(asset_no_prop).unpublished, asset_value) + TimestampsExtension.ext(asset_no_prop).unpublished = asset_value + self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).unpublished, + TimestampsExtension.ext(timestamps_item).unpublished) + self.assertEqual(TimestampsExtension.ext(asset_no_prop).unpublished, asset_value) # Validate timestamps_item.validate() diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 02923ff84..20f74958e 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -6,7 +6,7 @@ import pystac as ps from pystac.extensions import version -from pystac.extensions.version import version_ext, VersionExtension +from pystac.extensions.version import VersionExtension from tests.utils import TestCases URL_TEMPLATE: str = 'http://example.com/catalog/%s.json' @@ -36,35 +36,35 @@ def test_stac_extensions(self): self.assertTrue(VersionExtension.has_extension(self.item)) def test_add_version(self): - version_ext(self.item).apply(self.version) - self.assertEqual(self.version, version_ext(self.item).version) + VersionExtension.ext(self.item).apply(self.version) + self.assertEqual(self.version, VersionExtension.ext(self.item).version) self.assertNotIn(version.DEPRECATED, self.item.properties) - self.assertFalse(version_ext(self.item).deprecated) + self.assertFalse(VersionExtension.ext(self.item).deprecated) self.item.validate() def test_version_in_properties(self): - version_ext(self.item).apply(self.version, deprecated=True) + VersionExtension.ext(self.item).apply(self.version, deprecated=True) self.assertIn(version.VERSION, self.item.properties) self.assertIn(version.DEPRECATED, self.item.properties) self.item.validate() def test_add_not_deprecated_version(self): - version_ext(self.item).apply(self.version, deprecated=False) + VersionExtension.ext(self.item).apply(self.version, deprecated=False) self.assertIn(version.DEPRECATED, self.item.properties) - self.assertFalse(version_ext(self.item).deprecated) + self.assertFalse(VersionExtension.ext(self.item).deprecated) self.item.validate() def test_add_deprecated_version(self): - version_ext(self.item).apply(self.version, deprecated=True) + VersionExtension.ext(self.item).apply(self.version, deprecated=True) self.assertIn(version.DEPRECATED, self.item.properties) - self.assertTrue(version_ext(self.item).deprecated) + self.assertTrue(VersionExtension.ext(self.item).deprecated) self.item.validate() def test_latest(self): year = 2013 latest = make_item(year) - version_ext(self.item).apply(self.version, latest=latest) - latest_result = version_ext(self.item).latest + VersionExtension.ext(self.item).apply(self.version, latest=latest) + latest_result = VersionExtension.ext(self.item).latest self.assertIs(latest, latest_result) expected_href = URL_TEMPLATE % year @@ -75,8 +75,8 @@ def test_latest(self): def test_predecessor(self): year = 2010 predecessor = make_item(year) - version_ext(self.item).apply(self.version, predecessor=predecessor) - predecessor_result = version_ext(self.item).predecessor + VersionExtension.ext(self.item).apply(self.version, predecessor=predecessor) + predecessor_result = VersionExtension.ext(self.item).predecessor self.assertIs(predecessor, predecessor_result) expected_href = URL_TEMPLATE % year @@ -87,8 +87,8 @@ def test_predecessor(self): def test_successor(self): year = 2012 successor = make_item(year) - version_ext(self.item).apply(self.version, successor=successor) - successor_result = version_ext(self.item).successor + VersionExtension.ext(self.item).apply(self.version, successor=successor) + successor_result = VersionExtension.ext(self.item).successor self.assertIs(successor, successor_result) expected_href = URL_TEMPLATE % year @@ -105,7 +105,7 @@ def test_all_links(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - version_ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) self.item.validate() def test_full_copy(self): @@ -123,8 +123,8 @@ def test_full_copy(self): VersionExtension.add_to(item1) VersionExtension.add_to(item2) - version_ext(item1).apply(version='2.0', predecessor=item2) - version_ext(item2).apply(version='1.0', successor=item1, latest=item1) + VersionExtension.ext(item1).apply(version='2.0', predecessor=item2) + VersionExtension.ext(item2).apply(version='1.0', successor=item1, latest=item1) # Make a full copy of the catalog cat_copy = cat.full_copy() @@ -148,34 +148,34 @@ def test_setting_none_clears_link(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - version_ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) - version_ext(self.item).latest = None + VersionExtension.ext(self.item).latest = None links = self.item.get_links(version.LATEST) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.item).latest) + self.assertIsNone(VersionExtension.ext(self.item).latest) - version_ext(self.item).predecessor = None + VersionExtension.ext(self.item).predecessor = None links = self.item.get_links(version.PREDECESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.item).predecessor) + self.assertIsNone(VersionExtension.ext(self.item).predecessor) - version_ext(self.item).successor = None + VersionExtension.ext(self.item).successor = None links = self.item.get_links(version.SUCCESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.item).successor) + self.assertIsNone(VersionExtension.ext(self.item).successor) def test_multiple_link_setting(self): deprecated = False latest1 = make_item(2013) predecessor1 = make_item(2010) successor1 = make_item(2012) - version_ext(self.item).apply(self.version, deprecated, latest1, predecessor1, successor1) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest1, predecessor1, successor1) year = 2015 latest2 = make_item(year) expected_href = URL_TEMPLATE % year - version_ext(self.item).latest = latest2 + VersionExtension.ext(self.item).latest = latest2 links = self.item.get_links(version.LATEST) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -183,7 +183,7 @@ def test_multiple_link_setting(self): year = 2009 predecessor2 = make_item(year) expected_href = URL_TEMPLATE % year - version_ext(self.item).predecessor = predecessor2 + VersionExtension.ext(self.item).predecessor = predecessor2 links = self.item.get_links(version.PREDECESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -191,7 +191,7 @@ def test_multiple_link_setting(self): year = 2014 successor2 = make_item(year) expected_href = URL_TEMPLATE % year - version_ext(self.item).successor = successor2 + VersionExtension.ext(self.item).successor = successor2 links = self.item.get_links(version.SUCCESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -225,35 +225,35 @@ def test_stac_extensions(self): self.assertTrue(VersionExtension.has_extension(self.collection)) def test_add_version(self): - version_ext(self.collection).apply(self.version) - self.assertEqual(self.version, version_ext(self.collection).version) + VersionExtension.ext(self.collection).apply(self.version) + self.assertEqual(self.version, VersionExtension.ext(self.collection).version) self.assertNotIn(version.DEPRECATED, self.collection.extra_fields) - self.assertFalse(version_ext(self.collection).deprecated) + self.assertFalse(VersionExtension.ext(self.collection).deprecated) self.collection.validate() def test_version_deprecated(self): - version_ext(self.collection).apply(self.version, deprecated=True) + VersionExtension.ext(self.collection).apply(self.version, deprecated=True) self.assertIn(version.VERSION, self.collection.extra_fields) self.assertIn(version.DEPRECATED, self.collection.extra_fields) self.collection.validate() def test_add_not_deprecated_version(self): - version_ext(self.collection).apply(self.version, deprecated=False) + VersionExtension.ext(self.collection).apply(self.version, deprecated=False) self.assertIn(version.DEPRECATED, self.collection.extra_fields) - self.assertFalse(version_ext(self.collection).deprecated) + self.assertFalse(VersionExtension.ext(self.collection).deprecated) self.collection.validate() def test_add_deprecated_version(self): - version_ext(self.collection).apply(self.version, deprecated=True) + VersionExtension.ext(self.collection).apply(self.version, deprecated=True) self.assertIn(version.DEPRECATED, self.collection.extra_fields) - self.assertTrue(version_ext(self.collection).deprecated) + self.assertTrue(VersionExtension.ext(self.collection).deprecated) self.collection.validate() def test_latest(self): year = 2013 latest = make_collection(year) - version_ext(self.collection).apply(self.version, latest=latest) - latest_result = version_ext(self.collection).latest + VersionExtension.ext(self.collection).apply(self.version, latest=latest) + latest_result = VersionExtension.ext(self.collection).latest self.assertIs(latest, latest_result) expected_href = URL_TEMPLATE % year @@ -264,8 +264,8 @@ def test_latest(self): def test_predecessor(self): year = 2010 predecessor = make_collection(year) - version_ext(self.collection).apply(self.version, predecessor=predecessor) - predecessor_result = version_ext(self.collection).predecessor + VersionExtension.ext(self.collection).apply(self.version, predecessor=predecessor) + predecessor_result = VersionExtension.ext(self.collection).predecessor self.assertIs(predecessor, predecessor_result) expected_href = URL_TEMPLATE % year @@ -276,8 +276,8 @@ def test_predecessor(self): def test_successor(self): year = 2012 successor = make_collection(year) - version_ext(self.collection).apply(self.version, successor=successor) - successor_result = version_ext(self.collection).successor + VersionExtension.ext(self.collection).apply(self.version, successor=successor) + successor_result = VersionExtension.ext(self.collection).successor self.assertIs(successor, successor_result) expected_href = URL_TEMPLATE % year @@ -294,7 +294,7 @@ def test_validate_all(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - version_ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) self.collection.validate() def test_full_copy(self): @@ -311,8 +311,8 @@ def test_full_copy(self): VersionExtension.add_to(col1) VersionExtension.add_to(col2) - version_ext(col1).apply(version='2.0', predecessor=col2) - version_ext(col2).apply(version='1.0', successor=col1, latest=col1) + VersionExtension.ext(col1).apply(version='2.0', predecessor=col2) + VersionExtension.ext(col2).apply(version='1.0', successor=col1, latest=col1) # Make a full copy of the catalog cat_copy = cat.full_copy() @@ -336,35 +336,35 @@ def test_setting_none_clears_link(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - version_ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) - version_ext(self.collection).latest = None + VersionExtension.ext(self.collection).latest = None links = self.collection.get_links(version.LATEST) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.collection).latest) + self.assertIsNone(VersionExtension.ext(self.collection).latest) - version_ext(self.collection).predecessor = None + VersionExtension.ext(self.collection).predecessor = None links = self.collection.get_links(version.PREDECESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.collection).predecessor) + self.assertIsNone(VersionExtension.ext(self.collection).predecessor) - version_ext(self.collection).successor = None + VersionExtension.ext(self.collection).successor = None links = self.collection.get_links(version.SUCCESSOR) self.assertEqual(0, len(links)) - self.assertIsNone(version_ext(self.collection).successor) + self.assertIsNone(VersionExtension.ext(self.collection).successor) def test_multiple_link_setting(self): deprecated = False latest1 = make_collection(2013) predecessor1 = make_collection(2010) successor1 = make_collection(2012) - version_ext(self.collection).apply(self.version, deprecated, latest1, predecessor1, + VersionExtension.ext(self.collection).apply(self.version, deprecated, latest1, predecessor1, successor1) year = 2015 latest2 = make_collection(year) expected_href = URL_TEMPLATE % year - version_ext(self.collection).latest = latest2 + VersionExtension.ext(self.collection).latest = latest2 links = self.collection.get_links(version.LATEST) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -372,7 +372,7 @@ def test_multiple_link_setting(self): year = 2009 predecessor2 = make_collection(year) expected_href = URL_TEMPLATE % year - version_ext(self.collection).predecessor = predecessor2 + VersionExtension.ext(self.collection).predecessor = predecessor2 links = self.collection.get_links(version.PREDECESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) @@ -380,7 +380,7 @@ def test_multiple_link_setting(self): year = 2014 successor2 = make_collection(year) expected_href = URL_TEMPLATE % year - version_ext(self.collection).successor = successor2 + VersionExtension.ext(self.collection).successor = successor2 links = self.collection.get_links(version.SUCCESSOR) self.assertEqual(1, len(links)) self.assertEqual(expected_href, links[0].get_href()) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 045bbc773..b3cb6423f 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -2,7 +2,7 @@ import unittest import pystac as ps -from pystac.extensions.view import ViewExtension, view_ext +from pystac.extensions.view import ViewExtension, from tests.utils import (TestCases, test_to_from_dict) @@ -21,17 +21,17 @@ def test_apply(self): self.assertFalse(ViewExtension.has_extension(item)) ViewExtension.add_to(item) - view_ext(item).apply(off_nadir=1.0, + ViewExtension.ext(item).apply(off_nadir=1.0, incidence_angle=2.0, azimuth=3.0, sun_azimuth=4.0, sun_elevation=5.0) - self.assertEqual(view_ext(item).off_nadir, 1.0) - self.assertEqual(view_ext(item).incidence_angle, 2.0) - self.assertEqual(view_ext(item).azimuth, 3.0) - self.assertEqual(view_ext(item).sun_azimuth, 4.0) - self.assertEqual(view_ext(item).sun_elevation, 5.0) + self.assertEqual(ViewExtension.ext(item).off_nadir, 1.0) + self.assertEqual(ViewExtension.ext(item).incidence_angle, 2.0) + self.assertEqual(ViewExtension.ext(item).azimuth, 3.0) + self.assertEqual(ViewExtension.ext(item).sun_azimuth, 4.0) + self.assertEqual(ViewExtension.ext(item).sun_elevation, 5.0) def test_validate_view(self): item = ps.Item.from_file(self.example_uri) @@ -43,26 +43,26 @@ def test_off_nadir(self): # Get self.assertIn("view:off_nadir", view_item.properties) - view_off_nadir = view_ext(view_item).off_nadir + view_off_nadir = ViewExtension.ext(view_item).off_nadir self.assertEqual(view_off_nadir, view_item.properties['view:off_nadir']) # Set - view_ext(view_item).off_nadir = view_off_nadir + 10 + ViewExtension.ext(view_item).off_nadir = view_off_nadir + 10 self.assertEqual(view_off_nadir + 10, view_item.properties['view:off_nadir']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_ext(asset_no_prop).off_nadir, - view_ext(view_item).off_nadir) - self.assertEqual(view_ext(asset_prop).off_nadir, 3.0) + self.assertEqual(ViewExtension.ext(asset_no_prop).off_nadir, + ViewExtension.ext(view_item).off_nadir) + self.assertEqual(ViewExtension.ext(asset_prop).off_nadir, 3.0) # Set to Asset asset_value = 13.0 - view_ext(asset_no_prop).off_nadir = asset_value - self.assertNotEqual(view_ext(asset_no_prop).off_nadir, - view_ext(view_item).off_nadir) - self.assertEqual(view_ext(asset_no_prop).off_nadir, asset_value) + ViewExtension.ext(asset_no_prop).off_nadir = asset_value + self.assertNotEqual(ViewExtension.ext(asset_no_prop).off_nadir, + ViewExtension.ext(view_item).off_nadir) + self.assertEqual(ViewExtension.ext(asset_no_prop).off_nadir, asset_value) # Validate view_item.validate() @@ -72,26 +72,26 @@ def test_incidence_angle(self): # Get self.assertIn("view:incidence_angle", view_item.properties) - view_incidence_angle = view_ext(view_item).incidence_angle + view_incidence_angle = ViewExtension.ext(view_item).incidence_angle self.assertEqual(view_incidence_angle, view_item.properties['view:incidence_angle']) # Set - view_ext(view_item).incidence_angle = view_incidence_angle + 10 + ViewExtension.ext(view_item).incidence_angle = view_incidence_angle + 10 self.assertEqual(view_incidence_angle + 10, view_item.properties['view:incidence_angle']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_ext(asset_no_prop).incidence_angle, - view_ext(view_item).incidence_angle) - self.assertEqual(view_ext(asset_prop).incidence_angle, 4.0) + self.assertEqual(ViewExtension.ext(asset_no_prop).incidence_angle, + ViewExtension.ext(view_item).incidence_angle) + self.assertEqual(ViewExtension.ext(asset_prop).incidence_angle, 4.0) # Set to Asset asset_value = 14.0 - view_ext(asset_no_prop).incidence_angle = asset_value - self.assertNotEqual(view_ext(asset_no_prop).incidence_angle, - view_ext(view_item).incidence_angle) - self.assertEqual(view_ext(asset_no_prop).incidence_angle, asset_value) + ViewExtension.ext(asset_no_prop).incidence_angle = asset_value + self.assertNotEqual(ViewExtension.ext(asset_no_prop).incidence_angle, + ViewExtension.ext(view_item).incidence_angle) + self.assertEqual(ViewExtension.ext(asset_no_prop).incidence_angle, asset_value) # Validate view_item.validate() @@ -101,26 +101,26 @@ def test_azimuth(self): # Get self.assertIn("view:azimuth", view_item.properties) - view_azimuth = view_ext(view_item).azimuth + view_azimuth = ViewExtension.ext(view_item).azimuth self.assertEqual(view_azimuth, view_item.properties['view:azimuth']) # Set - view_ext(view_item).azimuth = view_azimuth + 100 + ViewExtension.ext(view_item).azimuth = view_azimuth + 100 self.assertEqual(view_azimuth + 100, view_item.properties['view:azimuth']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_ext(asset_no_prop).azimuth, - view_ext(view_item).azimuth) - self.assertEqual(view_ext(asset_prop).azimuth, 5.0) + self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, + ViewExtension.ext(view_item).azimuth) + self.assertEqual(ViewExtension.ext(asset_prop).azimuth, 5.0) # Set to Asset asset_value = 15.0 - view_ext(asset_no_prop).azimuth = asset_value - self.assertNotEqual(view_ext(asset_no_prop).azimuth, - view_ext(view_item).azimuth) - self.assertEqual(view_ext(asset_no_prop).azimuth, asset_value) + ViewExtension.ext(asset_no_prop).azimuth = asset_value + self.assertNotEqual(ViewExtension.ext(asset_no_prop).azimuth, + ViewExtension.ext(view_item).azimuth) + self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, asset_value) # Validate view_item.validate() @@ -130,26 +130,26 @@ def test_sun_azimuth(self): # Get self.assertIn("view:sun_azimuth", view_item.properties) - view_sun_azimuth = view_ext(view_item).sun_azimuth + view_sun_azimuth = ViewExtension.ext(view_item).sun_azimuth self.assertEqual(view_sun_azimuth, view_item.properties['view:sun_azimuth']) # Set - view_ext(view_item).sun_azimuth = view_sun_azimuth + 100 + ViewExtension.ext(view_item).sun_azimuth = view_sun_azimuth + 100 self.assertEqual(view_sun_azimuth + 100, view_item.properties['view:sun_azimuth']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_ext(asset_no_prop).sun_azimuth, - view_ext(view_item).sun_azimuth) - self.assertEqual(view_ext(asset_prop).sun_azimuth, 1.0) + self.assertEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, + ViewExtension.ext(view_item).sun_azimuth) + self.assertEqual(ViewExtension.ext(asset_prop).sun_azimuth, 1.0) # Set to Asset asset_value = 11.0 - view_ext(asset_no_prop).sun_azimuth = asset_value - self.assertNotEqual(view_ext(asset_no_prop).sun_azimuth, - view_ext(view_item).sun_azimuth) - self.assertEqual(view_ext(asset_no_prop).sun_azimuth, asset_value) + ViewExtension.ext(asset_no_prop).sun_azimuth = asset_value + self.assertNotEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, + ViewExtension.ext(view_item).sun_azimuth) + self.assertEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, asset_value) # Validate view_item.validate() @@ -159,26 +159,26 @@ def test_sun_elevation(self): # Get self.assertIn("view:sun_elevation", view_item.properties) - view_sun_elevation = view_ext(view_item).sun_elevation + view_sun_elevation = ViewExtension.ext(view_item).sun_elevation self.assertEqual(view_sun_elevation, view_item.properties['view:sun_elevation']) # Set - view_ext(view_item).sun_elevation = view_sun_elevation + 10 + ViewExtension.ext(view_item).sun_elevation = view_sun_elevation + 10 self.assertEqual(view_sun_elevation + 10, view_item.properties['view:sun_elevation']) # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(view_ext(asset_no_prop).sun_elevation, - view_ext(view_item).sun_elevation) - self.assertEqual(view_ext(asset_prop).sun_elevation, 2.0) + self.assertEqual(ViewExtension.ext(asset_no_prop).sun_elevation, + ViewExtension.ext(view_item).sun_elevation) + self.assertEqual(ViewExtension.ext(asset_prop).sun_elevation, 2.0) # Set to Asset asset_value = 12.0 - view_ext(asset_no_prop).sun_elevation = asset_value - self.assertNotEqual(view_ext(asset_no_prop).sun_elevation, - view_ext(view_item).sun_elevation) - self.assertEqual(view_ext(asset_no_prop).sun_elevation, asset_value) + ViewExtension.ext(asset_no_prop).sun_elevation = asset_value + self.assertNotEqual(ViewExtension.ext(asset_no_prop).sun_elevation, + ViewExtension.ext(view_item).sun_elevation) + self.assertEqual(ViewExtension.ext(asset_no_prop).sun_elevation, asset_value) # Validate view_item.validate() diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index 8cc04466d..f9e76c825 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -1,4 +1,4 @@ -from pystac.extensions.view import ViewExtension, view_ext +from pystac.extensions.view import ViewExtension import unittest import pystac as ps @@ -18,6 +18,8 @@ def setUp(self): def test_migrate(self): collection_cache = CollectionCache() for example in self.examples: + if example.path != "/home/rob/proj/stac/pystac/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json": + continue with self.subTest(example.path): path = example.path @@ -56,9 +58,10 @@ def test_migrates_added_extension(self): TestCases.get_path('data-files/examples/0.8.1/item-spec/' 'examples/planet-sample.json')) self.assertTrue(ViewExtension.has_extension(item)) - self.assertEqual(view_ext(item).sun_azimuth, 101.8) - self.assertEqual(view_ext(item).sun_elevation, 58.8) - self.assertEqual(view_ext(item).off_nadir, 1) + view_ext = ViewExtension.ext(item) + self.assertEqual(view_ext.sun_azimuth, 101.8) + self.assertEqual(view_ext.sun_elevation, 58.8) + self.assertEqual(view_ext.off_nadir, 1) def test_migrates_renamed_extension(self): collection = ps.Collection.from_file( diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 34ab051bb..e88f98dbb 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -8,7 +8,7 @@ import pystac as ps from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, HIERARCHICAL_LINKS) -from pystac.extensions.label import LabelClasses, LabelExtension, LabelType, label_ext +from pystac.extensions.label import LabelClasses, LabelExtension, LabelType from pystac.validation import STACValidationError from pystac.utils import is_absolute_href from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, MockStacIO) @@ -291,7 +291,7 @@ def test_normalize_href_works_with_label_source_links(self): catalog.normalize_hrefs('http://example.com') item = catalog.get_item('area-1-1-labels', recursive=True) assert item is not None - source = next(iter(label_ext(item).get_sources())) + source = next(iter(LabelExtension.ext(item).get_sources())) self.assertEqual( source.get_self_href(), "http://example.com/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json") @@ -501,7 +501,8 @@ def create_label_item(item: ps.Item) -> List[ps.Item]: datetime=datetime.utcnow(), properties={}) LabelExtension(label_item).add_to(label_item) - label_ext(label_item).apply( + label_ext = LabelExtension.ext(label_item) + label_ext.apply( label_description='labels', label_type=LabelType.VECTOR, label_properties=['label'], @@ -910,7 +911,8 @@ def test_full_copy_2(self): datetime=datetime.utcnow(), properties={}) LabelExtension.add_to(label_item) - label_ext(label_item).apply( + label_ext = LabelExtension.ext(label_item) + label_ext.apply( label_description='labels', label_type=LabelType.VECTOR, label_properties=['label'], diff --git a/tests/test_item.py b/tests/test_item.py index e5f49edae..b9fdb5818 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -1,6 +1,7 @@ import os from datetime import datetime import json +from typing import Any, Dict, List import unittest from tempfile import TemporaryDirectory @@ -36,7 +37,7 @@ def test_to_from_dict(self): 'http://cool-sat.com/catalog/products/analytic.json') self.assertEqual(len(item.assets['thumbnail'].properties), 0) - def test_set_self_href_doesnt_break_asset_hrefs(self): + def test_set_self_href_does_not_break_asset_hrefs(self): cat = TestCases.test_case_2() for item in cat.get_all_items(): for asset in item.assets.values(): @@ -207,7 +208,7 @@ def setUp(self): 'data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json') self.ITEM_2 = Item.from_file(self.URI_2) - self.EXAMPLE_CM_DICT = { + self.EXAMPLE_CM_DICT: Dict[str, Any] = { 'start_datetime': '2020-05-21T16:42:24.896Z', 'platform': @@ -295,14 +296,14 @@ def test_common_metadata_updated(self): def test_common_metadata_providers(self): x = self.ITEM_2.clone() - providers_dict_list = [{ + providers_dict_list: List[Dict[str, Any]] = [{ "name": "CoolSat", "roles": ["producer", "licensor"], "url": "https://cool-sat.com/" }] providers_object_list = [Provider.from_dict(d) for d in providers_dict_list] - example_providers_dict_list = [{ + example_providers_dict_list: List[Dict[str, Any]] = [{ "name": "ExampleProvider_1", "roles": ["example_role_1", "example_role_2"], "url": "https://exampleprovider1.com/" @@ -472,7 +473,7 @@ def test_asset_providers(self): item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata - item_value = cm.providers + item_value = get_opt(cm.providers) a2_known_value = [ ps.Provider(name="USGS", url="https://landsat.usgs.gov/", @@ -480,8 +481,8 @@ def test_asset_providers(self): ] # Get - a1_value = cm.get_providers(item.assets['analytic']) - a2_value = cm.get_providers(item.assets['thumbnail']) + a1_value: List[Provider] = get_opt(cm.get_providers(item.assets['analytic'])) + a2_value: List[Provider] = get_opt(cm.get_providers(item.assets['thumbnail'])) self.assertEqual(a1_value[0].to_dict(), item_value[0].to_dict()) self.assertNotEqual(a2_value[0].to_dict(), item_value[0].to_dict()) self.assertEqual(a2_value[0].to_dict(), a2_known_value[0].to_dict()) @@ -489,9 +490,9 @@ def test_asset_providers(self): # Set set_value = [ps.Provider(name="John Snow", url="https://cholera.com/", roles=["producer"])] cm.set_providers(set_value, item.assets['analytic']) - new_a1_value = cm.get_providers(item.assets['analytic']) + new_a1_value: List[Provider] = get_opt(cm.get_providers(item.assets['analytic'])) self.assertEqual(new_a1_value[0].to_dict(), set_value[0].to_dict()) - self.assertEqual(cm.providers[0].to_dict(), item_value[0].to_dict()) + self.assertEqual(get_opt(cm.providers)[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 36664e66c..f296f41f2 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -180,11 +180,11 @@ def test_case_6(): def test_case_7(): """Test case 4 as STAC version 0.8.1""" return Catalog.from_file( - TestCases.get_path('data-files/catalogs/label_catalog_0_8_1/catalog.json')) + TestCases.get_path('data-files/catalogs/label_catalog-v0.8.1/catalog.json')) @staticmethod def test_case_8() -> Collection: """Planet disaster data example catalog, 1.0.0-beta.2""" return Collection.from_file( TestCases.get_path('data-files/catalogs/' - 'planet-example-1.0.0-beta.2/collection.json')) + 'planet-example-v1.0.0-beta.2/collection.json')) From f69b305ac0228bd47704cb7e741bfb1ed11a9d38 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:24:27 -0400 Subject: [PATCH 23/51] Migrate test files to 1.0.0-rc.3 --- .../cbers-partial/CBERS4AWFI/collection.json | 3 +- .../cbers-partial/CBERS4MUX/collection.json | 219 +- .../CBERS4PAN10M/collection.json | 205 +- .../cbers-partial/CBERS4PAN5M/collection.json | 175 +- .../catalogs/cbers-partial/catalog.json | 56 +- .../acc/665946-labels/665946-labels.json | 19 +- .../acc/665946/665946.json | 17 +- .../acc/a42435-labels/a42435-labels.json | 19 +- .../acc/a42435/a42435.json | 17 +- .../acc/ca041a-labels/ca041a-labels.json | 19 +- .../acc/ca041a/ca041a.json | 17 +- .../label_catalog-v0.8.1/acc/collection.json | 5 +- .../acc/d41d81-labels/d41d81-labels.json | 19 +- .../acc/d41d81/d41d81.json | 17 +- .../dar/0a4c40-labels/0a4c40-labels.json | 19 +- .../dar/0a4c40/0a4c40.json | 15 +- .../dar/353093-labels/353093-labels.json | 19 +- .../dar/353093/353093.json | 17 +- .../dar/42f235-labels/42f235-labels.json | 19 +- .../dar/42f235/42f235.json | 15 +- .../dar/a017f9-labels/a017f9-labels.json | 19 +- .../dar/a017f9/a017f9.json | 17 +- .../dar/b15fce-labels/b15fce-labels.json | 19 +- .../dar/b15fce/b15fce.json | 17 +- .../label_catalog-v0.8.1/dar/collection.json | 5 +- .../dar/f883a0-labels/f883a0-labels.json | 19 +- .../dar/f883a0/f883a0.json | 15 +- .../kam/4e7c7f-labels/4e7c7f-labels.json | 19 +- .../kam/4e7c7f/4e7c7f.json | 17 +- .../label_catalog-v0.8.1/kam/collection.json | 5 +- .../mon/207cc7-labels/207cc7-labels.json | 19 +- .../mon/207cc7/207cc7.json | 17 +- .../mon/401175-labels/401175-labels.json | 19 +- .../mon/401175/401175.json | 17 +- .../mon/493701-labels/493701-labels.json | 19 +- .../mon/493701/493701.json | 17 +- .../label_catalog-v0.8.1/mon/collection.json | 5 +- .../mon/f15272-labels/f15272-labels.json | 19 +- .../mon/f15272/f15272.json | 17 +- .../nia/825a50-labels/825a50-labels.json | 19 +- .../nia/825a50/825a50.json | 15 +- .../label_catalog-v0.8.1/nia/collection.json | 5 +- .../ptn/abe1a3-labels/abe1a3-labels.json | 19 +- .../ptn/abe1a3/abe1a3.json | 17 +- .../label_catalog-v0.8.1/ptn/collection.json | 5 +- .../ptn/f49f31-labels/f49f31-labels.json | 19 +- .../ptn/f49f31/f49f31.json | 17 +- .../znz/06f252-labels/06f252-labels.json | 19 +- .../znz/06f252/06f252.json | 15 +- .../znz/076995-labels/076995-labels.json | 19 +- .../znz/076995/076995.json | 15 +- .../znz/33cae6-labels/33cae6-labels.json | 19 +- .../znz/33cae6/33cae6.json | 15 +- .../znz/3b20d4-labels/3b20d4-labels.json | 19 +- .../znz/3b20d4/3b20d4.json | 17 +- .../znz/3f8360-labels/3f8360-labels.json | 19 +- .../znz/3f8360/3f8360.json | 15 +- .../znz/425403-labels/425403-labels.json | 19 +- .../znz/425403/425403.json | 15 +- .../znz/75cdfa-labels/75cdfa-labels.json | 19 +- .../znz/75cdfa/75cdfa.json | 15 +- .../znz/9b8638-labels/9b8638-labels.json | 19 +- .../znz/9b8638/9b8638.json | 15 +- .../znz/aee7fd-labels/aee7fd-labels.json | 19 +- .../znz/aee7fd/aee7fd.json | 15 +- .../znz/bc32f1-labels/bc32f1-labels.json | 19 +- .../znz/bc32f1/bc32f1.json | 15 +- .../znz/bd5c14-labels/bd5c14-labels.json | 19 +- .../znz/bd5c14/bd5c14.json | 15 +- .../znz/c7415c-labels/c7415c-labels.json | 19 +- .../znz/c7415c/c7415c.json | 15 +- .../label_catalog-v0.8.1/znz/collection.json | 5 +- .../znz/e52478-labels/e52478-labels.json | 19 +- .../znz/e52478/e52478.json | 15 +- .../collection.json | 2 +- .../20170831_162740_ssc1d1.json | 6 +- .../20170831_172754_101c.json | 8 +- .../20170831_195425_SS02.json | 6 +- .../2017831_195552_SS02.json | 6 +- ...ston-East-20170831-103f-100d-0f4f-RGB.json | 10 +- .../catalogs/test-case-1/catalog.json | 6 +- .../area-1-1-imagery/area-1-1-imagery.json | 131 +- .../area-1-1-labels/area-1-1-labels.json | 170 +- .../country-1/area-1-1/collection.json | 4 +- .../area-1-2-imagery/area-1-2-imagery.json | 131 +- .../area-1-2-labels/area-1-2-labels.json | 170 +- .../country-1/area-1-2/collection.json | 4 +- .../test-case-1/country-1/catalog.json | 6 +- .../area-2-1-imagery/area-2-1-imagery.json | 131 +- .../area-2-1-labels/area-2-1-labels.json | 170 +- .../country-2/area-2-1/collection.json | 4 +- .../area-2-2-imagery/area-2-2-imagery.json | 131 +- .../area-2-2-labels/area-2-2-labels.json | 170 +- .../country-2/area-2-2/collection.json | 4 +- .../test-case-1/country-2/catalog.json | 6 +- .../cf73ec1a-d790-4b59-b077-e101738571ed.json | 1816 ++++++------ .../collection.json | 14 +- .../collection.json | 14 +- .../collection.json | 14 +- .../d43bead8-e3f8-4c51-95d6-e24e750a402b.json | 118 +- .../catalogs/test-case-2/catalog.json | 4 +- .../acc/665946-labels/665946-labels.json | 577 ++-- .../test-case-4/acc/665946/665946.json | 529 ++-- .../acc/a42435-labels/a42435-labels.json | 409 +-- .../test-case-4/acc/a42435/a42435.json | 361 +-- .../acc/ca041a-labels/ca041a-labels.json | 401 +-- .../test-case-4/acc/ca041a/ca041a.json | 353 +-- .../catalogs/test-case-4/acc/collection.json | 9 +- .../acc/d41d81-labels/d41d81-labels.json | 465 +-- .../test-case-4/acc/d41d81/d41d81.json | 417 +-- .../catalogs/test-case-4/catalog.json | 6 +- .../dar/0a4c40-labels/0a4c40-labels.json | 1215 ++++---- .../test-case-4/dar/0a4c40/0a4c40.json | 1167 ++++---- .../dar/353093-labels/353093-labels.json | 449 +-- .../test-case-4/dar/353093/353093.json | 401 +-- .../dar/42f235-labels/42f235-labels.json | 1695 +++++------ .../test-case-4/dar/42f235/42f235.json | 1651 +++++------ .../dar/a017f9-labels/a017f9-labels.json | 1641 +++++------ .../test-case-4/dar/a017f9/a017f9.json | 1593 +++++----- .../dar/b15fce-labels/b15fce-labels.json | 351 +-- .../test-case-4/dar/b15fce/b15fce.json | 303 +- .../catalogs/test-case-4/dar/collection.json | 9 +- .../dar/f883a0-labels/f883a0-labels.json | 2567 +++++++++-------- .../test-case-4/dar/f883a0/f883a0.json | 2523 ++++++++-------- .../kam/4e7c7f-labels/4e7c7f-labels.json | 841 +++--- .../test-case-4/kam/4e7c7f/4e7c7f.json | 793 ++--- .../catalogs/test-case-4/kam/collection.json | 9 +- .../mon/207cc7-labels/207cc7-labels.json | 473 +-- .../test-case-4/mon/207cc7/207cc7.json | 425 +-- .../mon/401175-labels/401175-labels.json | 457 +-- .../test-case-4/mon/401175/401175.json | 409 +-- .../mon/493701-labels/493701-labels.json | 393 +-- .../test-case-4/mon/493701/493701.json | 345 +-- .../catalogs/test-case-4/mon/collection.json | 9 +- .../mon/f15272-labels/f15272-labels.json | 369 +-- .../test-case-4/mon/f15272/f15272.json | 321 ++- .../nia/825a50-labels/825a50-labels.json | 245 +- .../test-case-4/nia/825a50/825a50.json | 199 +- .../catalogs/test-case-4/nia/collection.json | 9 +- .../ptn/abe1a3-labels/abe1a3-labels.json | 321 ++- .../test-case-4/ptn/abe1a3/abe1a3.json | 273 +- .../catalogs/test-case-4/ptn/collection.json | 9 +- .../ptn/f49f31-labels/f49f31-labels.json | 229 +- .../test-case-4/ptn/f49f31/f49f31.json | 183 +- .../znz/06f252-labels/06f252-labels.json | 179 +- .../test-case-4/znz/06f252/06f252.json | 133 +- .../znz/076995-labels/076995-labels.json | 203 +- .../test-case-4/znz/076995/076995.json | 157 +- .../znz/33cae6-labels/33cae6-labels.json | 2319 +++++++-------- .../test-case-4/znz/33cae6/33cae6.json | 2271 +++++++-------- .../znz/3b20d4-labels/3b20d4-labels.json | 171 +- .../test-case-4/znz/3b20d4/3b20d4.json | 125 +- .../znz/3f8360-labels/3f8360-labels.json | 179 +- .../test-case-4/znz/3f8360/3f8360.json | 133 +- .../znz/425403-labels/425403-labels.json | 171 +- .../test-case-4/znz/425403/425403.json | 125 +- .../znz/75cdfa-labels/75cdfa-labels.json | 179 +- .../test-case-4/znz/75cdfa/75cdfa.json | 133 +- .../znz/9b8638-labels/9b8638-labels.json | 287 +- .../test-case-4/znz/9b8638/9b8638.json | 239 +- .../znz/aee7fd-labels/aee7fd-labels.json | 179 +- .../test-case-4/znz/aee7fd/aee7fd.json | 133 +- .../znz/bc32f1-labels/bc32f1-labels.json | 179 +- .../test-case-4/znz/bc32f1/bc32f1.json | 133 +- .../znz/bd5c14-labels/bd5c14-labels.json | 179 +- .../test-case-4/znz/bd5c14/bd5c14.json | 133 +- .../znz/c7415c-labels/c7415c-labels.json | 179 +- .../test-case-4/znz/c7415c/c7415c.json | 133 +- .../catalogs/test-case-4/znz/collection.json | 9 +- .../znz/e52478-labels/e52478-labels.json | 179 +- .../test-case-4/znz/e52478/e52478.json | 133 +- .../CBERS_4_MUX_20190510_027_069_L2.json | 244 +- .../CBERS4-MUX-027/069/catalog.json | 6 +- .../CBERS4-MUX-027/collection.json | 42 +- .../CBERS4MUX/CBERS4-MUX-027/catalog.json | 8 +- .../CBERS4/CBERS4MUX/collection.json | 70 +- .../catalogs/test-case-5/CBERS4/catalog.json | 6 +- .../catalogs/test-case-5/catalog.json | 4 +- .../3c67b59c-2e6f-47fb-ba3c-0dd106941096.json | 138 +- .../collection.json | 66 +- .../3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json | 36 +- .../41c57cea-50ba-495c-9ee7-17ddba837380.json | 36 +- .../4514bbc6-cd17-4c14-816d-1709dfc61079.json | 36 +- .../52072b95-0275-4255-be5e-c567006f80cb.json | 36 +- .../84fe42c0-9d23-404a-bd0f-1406112cca8c.json | 36 +- .../884a939d-1d73-4351-9601-f9ee89a980b0.json | 36 +- .../a6bd2ff9-3805-40ab-96fa-b7f112b18973.json | 36 +- .../collection.json | 86 +- .../e40bb30d-0295-42ed-ba04-6cf563b057f9.json | 36 +- .../eb1124cc-3b45-4487-bc2c-4855ac877ad9.json | 36 +- .../collection.json | 72 +- .../catalogs/test-case-6/catalog.json | 10 +- tests/data-files/change_stac_version.py | 7 +- .../data-files/collections/multi-extent.json | 12 +- .../example-collection-with-commons.json | 91 +- .../example-collection-without-commons.json | 90 +- .../commons/example-item-with-commons.json | 133 +- .../commons/example-item-without-commons.json | 124 +- tests/data-files/eo/eo-collection.json | 294 +- tests/data-files/eo/eo-landsat-example.json | 451 +-- .../eo/sample-bands-in-item-properties.json | 100 +- .../029/2015-01-01/LC81560292015001LGN00.json | 217 +- tests/data-files/file/file-example.json | 40 +- tests/data-files/get_examples.py | 4 +- .../data-files/invalid/shared-id/catalog.json | 32 +- .../invalid/shared-id/test/collection.json | 82 +- .../test/test-item-1/test-item-1.json | 113 +- .../item/sample-item-asset-properties.json | 89 +- tests/data-files/item/sample-item.json | 52 +- tests/data-files/label/label-example-1.json | 18 +- tests/data-files/label/label-example-2.json | 18 +- .../pointcloud/example-laz-no-statistics.json | 265 +- tests/data-files/pointcloud/example-laz.json | 91 +- .../projection/example-landsat8.json | 406 +-- .../timestamps/example-landsat8.json | 9 +- tests/data-files/view/example-landsat8.json | 6 +- 216 files changed, 21625 insertions(+), 21205 deletions(-) diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json b/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json index f426f1f78..5afab0a5b 100644 --- a/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json +++ b/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "CBERS4AWFI", - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0-rc.3", "description": "CBERS4 AWFI camera catalog", "links": [ { diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json b/tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json index 06ba1205d..7ba7f8f51 100644 --- a/tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json +++ b/tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json @@ -1,119 +1,120 @@ { - "id": "CBERS4MUX", - "stac_version": "1.0.0-beta.2", - "description": "CBERS4 MUX camera catalog", - "links": [ - { - "rel": "root", - "href": "../catalog.json", - "type": "application/json" - }, + "type": "Collection", + "id": "CBERS4MUX", + "stac_version": "1.0.0-rc.3", + "description": "CBERS4 MUX camera catalog", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [ + "eo", + "item-assets" + ], + "providers": [ + { + "name": "Instituto Nacional de Pesquisas Espaciais, INPE", + "roles": [ + "producer" + ], + "url": "http://www.cbers.inpe.br" + }, + { + "name": "AMS Kepler", + "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", + "roles": [ + "processor" + ], + "url": "https://github.com/fredliporace/cbers-on-aws" + }, + { + "name": "Amazon Web Services", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/cbers/" + } + ], + "properties": { + "gsd": 20.0, + "platform": "CBERS-4", + "instruments": [ + "MUX" + ] + }, + "item_assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/jpeg" + }, + "metadata": { + "title": "INPE original metadata", + "type": "text/xml" + }, + "B5": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" + "name": "B5", + "common_name": "blue" } - ], - "stac_extensions": [ - "eo", - "item-assets" - ], - "providers": [ - { - "name": "Instituto Nacional de Pesquisas Espaciais, INPE", - "roles": [ - "producer" - ], - "url": "http://www.cbers.inpe.br" - }, - { - "name": "AMS Kepler", - "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", - "roles": [ - "processor" - ], - "url": "https://github.com/fredliporace/cbers-on-aws" - }, + ] + }, + "B6": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ { - "name": "Amazon Web Services", - "roles": [ - "host" - ], - "url": "https://registry.opendata.aws/cbers/" + "name": "B6", + "common_name": "green" } - ], - "properties": { - "gsd": 20.0, - "platform": "CBERS-4", - "instruments": [ - "MUX" - ] + ] }, - "item_assets": { - "thumbnail": { - "title": "Thumbnail", - "type": "image/jpeg" - }, - "metadata": { - "title": "INPE original metadata", - "type": "text/xml" - }, - "B5": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B5", - "common_name": "blue" - } - ] - }, - "B6": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B6", - "common_name": "green" - } - ] - }, - "B7": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B7", - "common_name": "red" - } - ] - }, - "B8": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B8", - "common_name": "nir" - } - ] + "B7": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B7", + "common_name": "red" } + ] }, - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -83.0, - 180.0, - 83.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2014-12-08T00:00:00Z", - null - ] - ] + "B8": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B8", + "common_name": "nir" } + ] + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] }, - "license": "CC-BY-SA-3.0" -} + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json b/tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json index f1d53143f..7e267e87b 100644 --- a/tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json +++ b/tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json @@ -1,110 +1,111 @@ { - "id": "CBERS4PAN10M", - "stac_version": "1.0.0-beta.2", - "description": "CBERS4 PAN10M camera catalog", - "links": [ - { - "rel": "root", - "href": "../catalog.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" - } - ], - "stac_extensions": [ - "eo", - "item-assets" - ], - "providers": [ - { - "name": "Instituto Nacional de Pesquisas Espaciais, INPE", - "roles": [ - "producer" - ], - "url": "http://www.cbers.inpe.br" - }, - { - "name": "AMS Kepler", - "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", - "roles": [ - "processor" - ], - "url": "https://github.com/fredliporace/cbers-on-aws" - }, + "type": "Collection", + "id": "CBERS4PAN10M", + "stac_version": "1.0.0-rc.3", + "description": "CBERS4 PAN10M camera catalog", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [ + "eo", + "item-assets" + ], + "providers": [ + { + "name": "Instituto Nacional de Pesquisas Espaciais, INPE", + "roles": [ + "producer" + ], + "url": "http://www.cbers.inpe.br" + }, + { + "name": "AMS Kepler", + "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", + "roles": [ + "processor" + ], + "url": "https://github.com/fredliporace/cbers-on-aws" + }, + { + "name": "Amazon Web Services", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/cbers/" + } + ], + "properties": { + "gsd": 10.0, + "platform": "CBERS-4", + "instruments": [ + "PAN10M" + ] + }, + "item_assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/jpeg" + }, + "metadata": { + "title": "INPE original metadata", + "type": "text/xml" + }, + "B2": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ { - "name": "Amazon Web Services", - "roles": [ - "host" - ], - "url": "https://registry.opendata.aws/cbers/" + "name": "B2", + "common_name": "green" } - ], - "properties": { - "gsd": 10.0, - "platform": "CBERS-4", - "instruments": [ - "PAN10M" - ] + ] }, - "item_assets": { - "thumbnail": { - "title": "Thumbnail", - "type": "image/jpeg" - }, - "metadata": { - "title": "INPE original metadata", - "type": "text/xml" - }, - "B2": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B2", - "common_name": "green" - } - ] - }, - "B3": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B3", - "common_name": "red" - } - ] - }, - "B4": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B4", - "common_name": "nir" - } - ] + "B3": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B3", + "common_name": "red" } + ] }, - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -83.0, - 180.0, - 83.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2014-12-08T00:00:00Z", - null - ] - ] + "B4": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B4", + "common_name": "nir" } + ] + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] }, - "license": "CC-BY-SA-3.0" -} + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json b/tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json index 2ac16b9f4..c85a652fb 100644 --- a/tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json +++ b/tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json @@ -1,92 +1,93 @@ { - "id": "CBERS4PAN5M", - "stac_version": "1.0.0-beta.2", - "description": "CBERS4 PAN5M camera catalog", - "links": [ - { - "rel": "root", - "href": "../catalog.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" - } - ], - "stac_extensions": [ - "eo", - "item-assets" - ], - "providers": [ - { - "name": "Instituto Nacional de Pesquisas Espaciais, INPE", - "roles": [ - "producer" - ], - "url": "http://www.cbers.inpe.br" - }, - { - "name": "AMS Kepler", - "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", - "roles": [ - "processor" - ], - "url": "https://github.com/fredliporace/cbers-on-aws" - }, - { - "name": "Amazon Web Services", - "roles": [ - "host" - ], - "url": "https://registry.opendata.aws/cbers/" - } - ], - "properties": { - "gsd": 5.0, - "platform": "CBERS-4", - "instruments": [ - "PAN5M" - ] + "type": "Collection", + "id": "CBERS4PAN5M", + "stac_version": "1.0.0-rc.3", + "description": "CBERS4 PAN5M camera catalog", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" }, - "item_assets": { - "thumbnail": { - "title": "Thumbnail", - "type": "image/jpeg" - }, - "metadata": { - "title": "INPE original metadata", - "type": "text/xml" - }, - "B1": { - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "eo:bands": [ - { - "name": "B1", - "common_name": "pan" - } - ] - } + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [ + "eo", + "item-assets" + ], + "providers": [ + { + "name": "Instituto Nacional de Pesquisas Espaciais, INPE", + "roles": [ + "producer" + ], + "url": "http://www.cbers.inpe.br" + }, + { + "name": "AMS Kepler", + "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", + "roles": [ + "processor" + ], + "url": "https://github.com/fredliporace/cbers-on-aws" + }, + { + "name": "Amazon Web Services", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/cbers/" + } + ], + "properties": { + "gsd": 5.0, + "platform": "CBERS-4", + "instruments": [ + "PAN5M" + ] + }, + "item_assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/jpeg" + }, + "metadata": { + "title": "INPE original metadata", + "type": "text/xml" }, - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -83.0, - 180.0, - 83.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2014-12-08T00:00:00Z", - null - ] - ] + "B1": { + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "eo:bands": [ + { + "name": "B1", + "common_name": "pan" } + ] + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] }, - "license": "CC-BY-SA-3.0" -} + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/cbers-partial/catalog.json b/tests/data-files/catalogs/cbers-partial/catalog.json index 4ac54b6fd..d5ee92404 100644 --- a/tests/data-files/catalogs/cbers-partial/catalog.json +++ b/tests/data-files/catalogs/cbers-partial/catalog.json @@ -1,28 +1,30 @@ { - "id": "CBERS4", - "stac_version": "1.0.0-beta.2", - "description": "Catalogs of CBERS-4 mission's imagery on AWS", - "links": [ - { - "rel": "root", - "href": "https://cbers-stac-1-0.s3.amazonaws.com/catalog.json", - "type": "application/json" - }, - { - "rel": "child", - "href": "./CBERS4MUX/collection.json" - }, - { - "rel": "child", - "href": "./CBERS4AWFI/collection.json" - }, - { - "rel": "child", - "href": "./CBERS4PAN10M/collection.json" - }, - { - "rel": "child", - "href": "./CBERS4PAN5M/collection.json" - } - ] -} + "type": "Catalog", + "id": "CBERS4", + "stac_version": "1.0.0-rc.3", + "description": "Catalogs of CBERS-4 mission's imagery on AWS", + "links": [ + { + "rel": "root", + "href": "https://cbers-stac-1-0.s3.amazonaws.com/catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./CBERS4MUX/collection.json" + }, + { + "rel": "child", + "href": "./CBERS4AWFI/collection.json" + }, + { + "rel": "child", + "href": "./CBERS4PAN10M/collection.json" + }, + { + "rel": "child", + "href": "./CBERS4PAN5M/collection.json" + } + ], + "stac_extensions": [] +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json index 429604d14..0f825fb88 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "665946-labels", "properties": { "label:description": "Geojson building labels for scene 665946", @@ -258,12 +258,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.24297113100354567, - 5.620077394512653, - -0.22777618020007248, - 5.647261088925513 - ], "links": [ { "rel": "collection", @@ -287,7 +281,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -0.24297113100354567, + 5.620077394512653, + -0.22777618020007248, + 5.647261088925513 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json index eeb106f92..bd4c9dd4f 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "665946", "properties": { "area": "acc", @@ -234,12 +234,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.24297113100354567, - 5.620077394512653, - -0.22777618020007248, - 5.647261088925513 - ], "links": [ { "rel": "collection", @@ -264,5 +258,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -0.24297113100354567, + 5.620077394512653, + -0.22777618020007248, + 5.647261088925513 + ], + "stac_extensions": [], "collection": "acc" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json index bb4580866..52a1042e2 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "a42435-labels", "properties": { "label:description": "Geojson building labels for scene a42435", @@ -174,12 +174,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.2498678517428029, - 5.607877073189545, - -0.23326449513459932, - 5.619166711344742 - ], "links": [ { "rel": "collection", @@ -203,7 +197,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -0.2498678517428029, + 5.607877073189545, + -0.23326449513459932, + 5.619166711344742 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json index a1c96212c..df352a839 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "a42435", "properties": { "area": "acc", @@ -150,12 +150,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.2498678517428029, - 5.607877073189545, - -0.23326449513459932, - 5.619166711344742 - ], "links": [ { "rel": "collection", @@ -180,5 +174,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -0.2498678517428029, + 5.607877073189545, + -0.23326449513459932, + 5.619166711344742 + ], + "stac_extensions": [], "collection": "acc" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json index ae1f6c44c..41b4279a4 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "ca041a-labels", "properties": { "label:description": "Geojson building labels for scene ca041a", @@ -170,12 +170,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.22707525357332697, - 5.585527399115482, - -0.20581415249279408, - 5.610742610987594 - ], "links": [ { "rel": "collection", @@ -199,7 +193,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -0.22707525357332697, + 5.585527399115482, + -0.20581415249279408, + 5.610742610987594 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json index bcd9428ed..d2abc4c05 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "ca041a", "properties": { "area": "acc", @@ -146,12 +146,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.22707525357332697, - 5.585527399115482, - -0.20581415249279408, - 5.610742610987594 - ], "links": [ { "rel": "collection", @@ -176,5 +170,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -0.22707525357332697, + 5.585527399115482, + -0.20581415249279408, + 5.610742610987594 + ], + "stac_extensions": [], "collection": "acc" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json index baf12f11a..0de579cda 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json @@ -74,8 +74,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json index e3cdba650..799c1d6b0 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "d41d81-labels", "properties": { "label:description": "Geojson building labels for scene d41d81", @@ -202,12 +202,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.20863145179911316, - 5.573262528211078, - -0.18948660187120017, - 5.593203677296213 - ], "links": [ { "rel": "collection", @@ -231,7 +225,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -0.20863145179911316, + 5.573262528211078, + -0.18948660187120017, + 5.593203677296213 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json index 53bc3a41c..7649f0f2e 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "d41d81", "properties": { "area": "acc", @@ -178,12 +178,6 @@ ], "type": "Polygon" }, - "bbox": [ - -0.20863145179911316, - 5.573262528211078, - -0.18948660187120017, - 5.593203677296213 - ], "links": [ { "rel": "collection", @@ -208,5 +202,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -0.20863145179911316, + 5.573262528211078, + -0.18948660187120017, + 5.593203677296213 + ], + "stac_extensions": [], "collection": "acc" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json index a39a234e3..f69f203cf 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "0a4c40-labels", "properties": { "label:description": "Geojson building labels for scene 0a4c40", @@ -578,12 +578,6 @@ ] ] }, - "bbox": [ - 39.276084515013736, - -6.831966577377629, - 39.304456754694705, - -6.801144663537702 - ], "links": [ { "rel": "collection", @@ -607,7 +601,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.276084515013736, + -6.831966577377629, + 39.304456754694705, + -6.801144663537702 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json index 1fc513512..20eb50e69 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "0a4c40", "properties": { "area": "dar", @@ -554,12 +554,6 @@ ] ] }, - "bbox": [ - 39.276084515013736, - -6.831966577377629, - 39.304456754694705, - -6.801144663537702 - ], "links": [ { "rel": "collection", @@ -584,5 +578,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.276084515013736, + -6.831966577377629, + 39.304456754694705, + -6.801144663537702 + ], + "stac_extensions": [], "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json index ef1bfd2b7..63e7bcb9c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "353093-labels", "properties": { "label:description": "Geojson building labels for scene 353093", @@ -194,12 +194,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.20390891675323, - -6.809511989589884, - 39.2270869431941, - -6.783684392169557 - ], "links": [ { "rel": "collection", @@ -223,7 +217,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.20390891675323, + -6.809511989589884, + 39.2270869431941, + -6.783684392169557 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json index ca670a9c0..b24d05651 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "353093", "properties": { "area": "dar", @@ -170,12 +170,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.20390891675323, - -6.809511989589884, - 39.2270869431941, - -6.783684392169557 - ], "links": [ { "rel": "collection", @@ -200,5 +194,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.20390891675323, + -6.809511989589884, + 39.2270869431941, + -6.783684392169557 + ], + "stac_extensions": [], "collection": "dar" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json index 00895b300..670590586 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "42f235-labels", "properties": { "label:description": "Geojson building labels for scene 42f235", @@ -820,12 +820,6 @@ ] ] }, - "bbox": [ - 39.2484489815814, - -6.832170132981521, - 39.28214024571535, - -6.800774407651622 - ], "links": [ { "rel": "collection", @@ -849,7 +843,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.2484489815814, + -6.832170132981521, + 39.28214024571535, + -6.800774407651622 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json index 3bfc184a7..18ffed606 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "42f235", "properties": { "area": "dar", @@ -796,12 +796,6 @@ ] ] }, - "bbox": [ - 39.2484489815814, - -6.832170132981521, - 39.28214024571535, - -6.800774407651622 - ], "links": [ { "rel": "collection", @@ -826,5 +820,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.2484489815814, + -6.832170132981521, + 39.28214024571535, + -6.800774407651622 + ], + "stac_extensions": [], "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json index 7666a78d1..1025e7bf8 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "a017f9-labels", "properties": { "label:description": "Geojson building labels for scene a017f9", @@ -790,12 +790,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.25403029430106, - -6.779784892393674, - 39.2731659202684, - -6.759102343973786 - ], "links": [ { "rel": "collection", @@ -819,7 +813,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.25403029430106, + -6.779784892393674, + 39.2731659202684, + -6.759102343973786 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json index b66ecbc72..be22c5b66 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "a017f9", "properties": { "area": "dar", @@ -766,12 +766,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.25403029430106, - -6.779784892393674, - 39.2731659202684, - -6.759102343973786 - ], "links": [ { "rel": "collection", @@ -796,5 +790,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.25403029430106, + -6.779784892393674, + 39.2731659202684, + -6.759102343973786 + ], + "stac_extensions": [], "collection": "dar" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json index b91a33414..80c95e106 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "b15fce-labels", "properties": { "label:description": "Geojson building labels for scene b15fce", @@ -146,12 +146,6 @@ ] ] }, - "bbox": [ - 39.231583919750726, - -6.806085900208043, - 39.25215435443532, - -6.786630132161881 - ], "links": [ { "rel": "collection", @@ -175,7 +169,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.231583919750726, + -6.806085900208043, + 39.25215435443532, + -6.786630132161881 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json index 46d5eb70a..9920d57d4 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "b15fce", "properties": { "area": "dar", @@ -122,12 +122,6 @@ ] ] }, - "bbox": [ - 39.231583919750726, - -6.806085900208043, - 39.25215435443532, - -6.786630132161881 - ], "links": [ { "rel": "collection", @@ -152,5 +146,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.231583919750726, + -6.806085900208043, + 39.25215435443532, + -6.786630132161881 + ], + "stac_extensions": [], "collection": "dar" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json index a04b1d5db..651125b70 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json @@ -94,8 +94,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json index 03223cfe9..1bb93fb69 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f883a0-labels", "properties": { "label:description": "Geojson building labels for scene f883a0", @@ -1256,12 +1256,6 @@ ] ] }, - "bbox": [ - 39.24966102436161, - -6.804292908938633, - 39.28114400293776, - -6.773980646345491 - ], "links": [ { "rel": "collection", @@ -1285,7 +1279,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.24966102436161, + -6.804292908938633, + 39.28114400293776, + -6.773980646345491 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json index 76379599f..817441517 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f883a0", "properties": { "area": "dar", @@ -1232,12 +1232,6 @@ ] ] }, - "bbox": [ - 39.24966102436161, - -6.804292908938633, - 39.28114400293776, - -6.773980646345491 - ], "links": [ { "rel": "collection", @@ -1262,5 +1256,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.24966102436161, + -6.804292908938633, + 39.28114400293776, + -6.773980646345491 + ], + "stac_extensions": [], "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json index ea534e7fd..38250e5ff 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "4e7c7f-labels", "properties": { "label:description": "Geojson building labels for scene 4e7c7f", @@ -390,12 +390,6 @@ ], "type": "Polygon" }, - "bbox": [ - 32.626179443806286, - 0.24927592951780594, - 32.638649276300896, - 0.26207074609958386 - ], "links": [ { "rel": "collection", @@ -419,7 +413,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 32.626179443806286, + 0.24927592951780594, + 32.638649276300896, + 0.26207074609958386 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "kam" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json index 83e039500..a49d7626f 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "4e7c7f", "properties": { "area": "kam", @@ -366,12 +366,6 @@ ], "type": "Polygon" }, - "bbox": [ - 32.626179443806286, - 0.24927592951780594, - 32.638649276300896, - 0.26207074609958386 - ], "links": [ { "rel": "collection", @@ -396,5 +390,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 32.626179443806286, + 0.24927592951780594, + 32.638649276300896, + 0.26207074609958386 + ], + "stac_extensions": [], "collection": "kam" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json index d3b1207af..45b550a46 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json @@ -44,8 +44,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json index f596cd68c..55fc79920 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "207cc7-labels", "properties": { "label:description": "Geojson building labels for scene 207cc7", @@ -206,12 +206,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.798182715496184, - 6.325934341609507, - -10.788359662453985, - 6.333146686841442 - ], "links": [ { "rel": "collection", @@ -235,7 +229,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -10.798182715496184, + 6.325934341609507, + -10.788359662453985, + 6.333146686841442 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json index 4d1e7f29d..e5ec355f9 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "207cc7", "properties": { "area": "mon", @@ -182,12 +182,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.798182715496184, - 6.325934341609507, - -10.788359662453985, - 6.333146686841442 - ], "links": [ { "rel": "collection", @@ -212,5 +206,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -10.798182715496184, + 6.325934341609507, + -10.788359662453985, + 6.333146686841442 + ], + "stac_extensions": [], "collection": "mon" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json index 76dd87f00..3bfc716aa 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "401175-labels", "properties": { "label:description": "Geojson building labels for scene 401175", @@ -198,12 +198,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.793204246358947, - 6.330781306914856, - -10.783947405680633, - 6.339080445680323 - ], "links": [ { "rel": "collection", @@ -227,7 +221,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -10.793204246358947, + 6.330781306914856, + -10.783947405680633, + 6.339080445680323 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json index ea51ca04b..26a75cca8 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "401175", "properties": { "area": "mon", @@ -174,12 +174,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.793204246358947, - 6.330781306914856, - -10.783947405680633, - 6.339080445680323 - ], "links": [ { "rel": "collection", @@ -204,5 +198,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -10.793204246358947, + 6.330781306914856, + -10.783947405680633, + 6.339080445680323 + ], + "stac_extensions": [], "collection": "mon" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json index 15688928c..691d1632f 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "493701-labels", "properties": { "label:description": "Geojson building labels for scene 493701", @@ -166,12 +166,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.789750111567438, - 6.326344897772844, - -10.7812814574604, - 6.334626743333399 - ], "links": [ { "rel": "collection", @@ -195,7 +189,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -10.789750111567438, + 6.326344897772844, + -10.7812814574604, + 6.334626743333399 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json index 34646bdab..951db2aab 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "493701", "properties": { "area": "mon", @@ -142,12 +142,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.789750111567438, - 6.326344897772844, - -10.7812814574604, - 6.334626743333399 - ], "links": [ { "rel": "collection", @@ -172,5 +166,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -10.789750111567438, + 6.326344897772844, + -10.7812814574604, + 6.334626743333399 + ], + "stac_extensions": [], "collection": "mon" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json index 821f5e8b1..c3026539c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json @@ -74,8 +74,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json index 30353de35..ac04f9b15 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f15272-labels", "properties": { "label:description": "Geojson building labels for scene f15272", @@ -154,12 +154,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.801045209329727, - 6.32481451523328, - -10.793669675961551, - 6.331302091125989 - ], "links": [ { "rel": "collection", @@ -183,7 +177,14 @@ "type": "application/geo+json" } }, + "bbox": [ + -10.801045209329727, + 6.32481451523328, + -10.793669675961551, + 6.331302091125989 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json index 81f17576a..1d5f9fbb3 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f15272", "properties": { "area": "mon", @@ -130,12 +130,6 @@ ], "type": "Polygon" }, - "bbox": [ - -10.801045209329727, - 6.32481451523328, - -10.793669675961551, - 6.331302091125989 - ], "links": [ { "rel": "collection", @@ -160,5 +154,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + -10.801045209329727, + 6.32481451523328, + -10.793669675961551, + 6.331302091125989 + ], + "stac_extensions": [], "collection": "mon" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json index 75754c985..a14743923 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "825a50-labels", "properties": { "label:description": "Geojson building labels for scene 825a50", @@ -94,12 +94,6 @@ ], "type": "Polygon" }, - "bbox": [ - 2.000607112710697, - 13.570445755015827, - 2.0089069498293726, - 13.583969811536608 - ], "links": [ { "rel": "collection", @@ -123,7 +117,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 2.000607112710697, + 13.570445755015827, + 2.0089069498293726, + 13.583969811536608 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "nia" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json index 2383367ca..506945f9c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "825a50", "properties": { "area": "nia", @@ -70,12 +70,6 @@ ], "type": "Polygon" }, - "bbox": [ - 2.000607112710697, - 13.570445755015827, - 2.0089069498293726, - 13.583969811536608 - ], "links": [ { "rel": "collection", @@ -100,5 +94,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 2.000607112710697, + 13.570445755015827, + 2.0089069498293726, + 13.583969811536608 + ], + "stac_extensions": [], "collection": "nia" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json index 31df4ab11..f3dbfa603 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json @@ -44,8 +44,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json index 7eaa50740..3896ab604 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "abe1a3-labels", "properties": { "label:description": "Geojson building labels for scene abe1a3", @@ -130,12 +130,6 @@ ], "type": "Polygon" }, - "bbox": [ - 11.879921982584099, - -4.780589144725401, - 11.894047213717943, - -4.7665107950056145 - ], "links": [ { "rel": "collection", @@ -159,7 +153,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 11.879921982584099, + -4.780589144725401, + 11.894047213717943, + -4.7665107950056145 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json index 8f4d3787f..b8deb4c6a 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "abe1a3", "properties": { "area": "ptn", @@ -106,12 +106,6 @@ ], "type": "Polygon" }, - "bbox": [ - 11.879921982584099, - -4.780589144725401, - 11.894047213717943, - -4.7665107950056145 - ], "links": [ { "rel": "collection", @@ -136,5 +130,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 11.879921982584099, + -4.780589144725401, + 11.894047213717943, + -4.7665107950056145 + ], + "stac_extensions": [], "collection": "ptn" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json index a630ec58c..c721cc9f8 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json @@ -54,8 +54,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json index 3aa4aec06..b29df6e49 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f49f31-labels", "properties": { "label:description": "Geojson building labels for scene f49f31", @@ -86,12 +86,6 @@ ], "type": "Polygon" }, - "bbox": [ - 11.885939170933694, - -4.805243717903951, - 11.897849273966862, - -4.797722543259397 - ], "links": [ { "rel": "collection", @@ -115,7 +109,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 11.885939170933694, + -4.805243717903951, + 11.897849273966862, + -4.797722543259397 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json index 4c06b33ac..777c31a96 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "f49f31", "properties": { "area": "ptn", @@ -62,12 +62,6 @@ ], "type": "Polygon" }, - "bbox": [ - 11.885939170933694, - -4.805243717903951, - 11.897849273966862, - -4.797722543259397 - ], "links": [ { "rel": "collection", @@ -92,5 +86,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 11.885939170933694, + -4.805243717903951, + 11.897849273966862, + -4.797722543259397 + ], + "stac_extensions": [], "collection": "ptn" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json index bf5e9f682..2c8ea9eef 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "06f252-labels", "properties": { "label:description": "Geojson building labels for scene 06f252", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31367952293228, - -5.905851570308218, - 39.340797007857546, - -5.878724233277496 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.31367952293228, + -5.905851570308218, + 39.340797007857546, + -5.878724233277496 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json index a1b17c7d7..0650d7bec 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "06f252", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31367952293228, - -5.905851570308218, - 39.340797007857546, - -5.878724233277496 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.31367952293228, + -5.905851570308218, + 39.340797007857546, + -5.878724233277496 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json index fb4052842..366e5f7f0 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "076995-labels", "properties": { "label:description": "Geojson building labels for scene 076995", @@ -73,12 +73,6 @@ ] ] }, - "bbox": [ - 39.34073844240503, - -5.851576358919395, - 39.36335800442592, - -5.824424385662977 - ], "links": [ { "rel": "collection", @@ -102,7 +96,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.34073844240503, + -5.851576358919395, + 39.36335800442592, + -5.824424385662977 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json index 2608615d6..b411ef394 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "076995", "properties": { "area": "znz", @@ -49,12 +49,6 @@ ] ] }, - "bbox": [ - 39.34073844240503, - -5.851576358919395, - 39.36335800442592, - -5.824424385662977 - ], "links": [ { "rel": "collection", @@ -79,5 +73,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.34073844240503, + -5.851576358919395, + 39.36335800442592, + -5.824424385662977 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json index 3934a2bb1..c7e40d47f 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "33cae6-labels", "properties": { "label:description": "Geojson building labels for scene 33cae6", @@ -1129,12 +1129,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.287711527683925, - -5.743060299502631, - 39.31360456846548, - -5.719012276837555 - ], "links": [ { "rel": "collection", @@ -1158,7 +1152,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.287711527683925, + -5.743060299502631, + 39.31360456846548, + -5.719012276837555 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json index bed2ece41..f1928cb44 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "33cae6", "properties": { "area": "znz", @@ -1105,12 +1105,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.287711527683925, - -5.743060299502631, - 39.31360456846548, - -5.719012276837555 - ], "links": [ { "rel": "collection", @@ -1135,5 +1129,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.287711527683925, + -5.743060299502631, + 39.31360456846548, + -5.719012276837555 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json index 882ec3f02..32b53802c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "3b20d4-labels", "properties": { "label:description": "Geojson building labels for scene 3b20d4", @@ -57,12 +57,6 @@ ] ] }, - "bbox": [ - 39.31347857904143, - -5.851778449857214, - 39.34092291979187, - -5.824334109106769 - ], "links": [ { "rel": "collection", @@ -86,7 +80,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.31347857904143, + -5.851778449857214, + 39.34092291979187, + -5.824334109106769 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json index 37ebb8aac..9a385cf3c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "3b20d4", "properties": { "area": "znz", @@ -33,12 +33,6 @@ ] ] }, - "bbox": [ - 39.31347857904143, - -5.851778449857214, - 39.34092291979187, - -5.824334109106769 - ], "links": [ { "rel": "collection", @@ -63,5 +57,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.31347857904143, + -5.851778449857214, + 39.34092291979187, + -5.824334109106769 + ], + "stac_extensions": [], "collection": "znz" -} +} \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json index 6a8b72d52..4a0d84832 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "3f8360-labels", "properties": { "label:description": "Geojson building labels for scene 3f8360", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34079687232617, - -5.933001162750248, - 39.36446684258708, - -5.905820682536527 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.34079687232617, + -5.933001162750248, + 39.36446684258708, + -5.905820682536527 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json index f389c8db4..e002c85c8 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "3f8360", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34079687232617, - -5.933001162750248, - 39.36446684258708, - -5.905820682536527 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.34079687232617, + -5.933001162750248, + 39.36446684258708, + -5.905820682536527 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json index b3281171a..336ba134f 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "425403-labels", "properties": { "label:description": "Geojson building labels for scene 425403", @@ -57,12 +57,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31371005982263, - -5.960174243786799, - 39.34082996500427, - -5.933001378219889 - ], "links": [ { "rel": "collection", @@ -86,7 +80,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.31371005982263, + -5.960174243786799, + 39.34082996500427, + -5.933001378219889 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json index f4037a1f1..d15a6f17c 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "425403", "properties": { "area": "znz", @@ -33,12 +33,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31371005982263, - -5.960174243786799, - 39.34082996500427, - -5.933001378219889 - ], "links": [ { "rel": "collection", @@ -63,5 +57,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.31371005982263, + -5.960174243786799, + 39.34082996500427, + -5.933001378219889 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json index bc9cd83c8..967f03394 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "75cdfa-labels", "properties": { "label:description": "Geojson building labels for scene 75cdfa", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31366435148484, - -5.8787400187081476, - 39.34078034637437, - -5.851567529006198 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.31366435148484, + -5.8787400187081476, + 39.34078034637437, + -5.851567529006198 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json index f3c60dd14..567f22d8d 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "75cdfa", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31366435148484, - -5.8787400187081476, - 39.34078034637437, - -5.851567529006198 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.31366435148484, + -5.8787400187081476, + 39.34078034637437, + -5.851567529006198 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json index cfc7a0981..2483ad14e 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "9b8638-labels", "properties": { "label:description": "Geojson building labels for scene 9b8638", @@ -113,12 +113,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34076385802326, - -5.878724092981988, - 39.362419204085, - -5.8515539621204695 - ], "links": [ { "rel": "collection", @@ -142,7 +136,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.34076385802326, + -5.878724092981988, + 39.362419204085, + -5.8515539621204695 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json index 810e0332a..421b75f92 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "9b8638", "properties": { "area": "znz", @@ -89,12 +89,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34076385802326, - -5.878724092981988, - 39.362419204085, - -5.8515539621204695 - ], "links": [ { "rel": "collection", @@ -119,5 +113,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.34076385802326, + -5.878724092981988, + 39.362419204085, + -5.8515539621204695 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json index 106a7f1c0..4db538c37 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "aee7fd-labels", "properties": { "label:description": "Geojson building labels for scene aee7fd", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31369530504979, - -5.933017366868187, - 39.340814311389366, - -5.905835679883141 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.31369530504979, + -5.933017366868187, + 39.340814311389366, + -5.905835679883141 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json index d8eda463b..fc130af26 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "aee7fd", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.31369530504979, - -5.933017366868187, - 39.340814311389366, - -5.905835679883141 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.31369530504979, + -5.933017366868187, + 39.340814311389366, + -5.905835679883141 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json index fa4bcd9c8..44efd7d87 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "bc32f1-labels", "properties": { "label:description": "Geojson building labels for scene bc32f1", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34083023028068, - -5.987278166971163, - 39.36795215042373, - -5.960140574104503 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.34083023028068, + -5.987278166971163, + 39.36795215042373, + -5.960140574104503 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json index bd5c2ccb5..1d2709183 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "bc32f1", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34083023028068, - -5.987278166971163, - 39.36795215042373, - -5.960140574104503 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.34083023028068, + -5.987278166971163, + 39.36795215042373, + -5.960140574104503 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json index d1f4ed19a..766476c23 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "bd5c14-labels", "properties": { "label:description": "Geojson building labels for scene bd5c14", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34081295055692, - -5.960157881729879, - 39.367933753210345, - -5.932984029391401 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.34081295055692, + -5.960157881729879, + 39.367933753210345, + -5.932984029391401 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json index 063e13f72..19cad7c97 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "bd5c14", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.34081295055692, - -5.960157881729879, - 39.367933753210345, - -5.932984029391401 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.34081295055692, + -5.960157881729879, + 39.367933753210345, + -5.932984029391401 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json index 963065505..08497f0e7 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "c7415c-labels", "properties": { "label:description": "Geojson building labels for scene c7415c", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.340780340171506, - -5.9058357319970805, - 39.360290846530575, - -5.878712001912929 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.340780340171506, + -5.9058357319970805, + 39.360290846530575, + -5.878712001912929 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json index 8beaebe91..9c7991b75 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "c7415c", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.340780340171506, - -5.9058357319970805, - 39.360290846530575, - -5.878712001912929 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.340780340171506, + -5.9058357319970805, + 39.360290846530575, + -5.878712001912929 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json index 214a95f2d..bd245e151 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json @@ -164,8 +164,5 @@ ] } }, - "license": "CC-BY-4.0", - "stac_extensions": [ - "label" - ] + "license": "CC-BY-4.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json index 8635cddcf..a8437dbc7 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "e52478-labels", "properties": { "label:description": "Geojson building labels for scene e52478", @@ -61,12 +61,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.36791576371174, - -5.9601408243123455, - 39.378914288970435, - -5.9329766232129995 - ], "links": [ { "rel": "collection", @@ -90,7 +84,14 @@ "type": "application/geo+json" } }, + "bbox": [ + 39.36791576371174, + -5.9601408243123455, + 39.378914288970435, + -5.9329766232129995 + ], "stac_extensions": [ - "label" - ] + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json index 8e6863629..1632da701 100644 --- a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json +++ b/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "0.8.1", + "stac_version": "1.0.0-rc.3", "id": "e52478", "properties": { "area": "znz", @@ -37,12 +37,6 @@ ], "type": "Polygon" }, - "bbox": [ - 39.36791576371174, - -5.9601408243123455, - 39.378914288970435, - -5.9329766232129995 - ], "links": [ { "rel": "collection", @@ -67,5 +61,12 @@ "title": "GeoTIFF" } }, + "bbox": [ + 39.36791576371174, + -5.9601408243123455, + 39.378914288970435, + -5.9329766232129995 + ], + "stac_extensions": [], "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json index 6722373f3..eb4923d4c 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json @@ -91,7 +91,7 @@ "temporal": { "interval": [ [ - "2017-08-28T10:00:00-08:00", + "2017-08-28T10:00:00+00:00", null ] ] diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json index d9c9b526c..a1ded9213 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "20170831_162740_ssc1d1", "properties": { "datetime": "2017-08-31T16:27:42.176605Z", @@ -156,8 +156,8 @@ 29.064872627755797 ], "stac_extensions": [ - "eo", - "view" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "collection": "planet-disaster-data" } \ No newline at end of file diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json index e4120efdc..701f76ce0 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "20170831_172754_101c", "properties": { "datetime": "2017-08-31T17:27:54.960530Z", @@ -141,9 +141,9 @@ 29.62330817060518 ], "stac_extensions": [ - "eo", - "view", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "collection": "planet-disaster-data" } \ No newline at end of file diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json index 96e295d5c..91b6fdd7e 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "20170831_195425_SS02", "properties": { "datetime": "2017-08-31T19:54:26.532800Z", @@ -121,8 +121,8 @@ 30.052156732418428 ], "stac_extensions": [ - "eo", - "view" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "collection": "planet-disaster-data" } \ No newline at end of file diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json index 29005fb9a..abee6ece5 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "2017831_195552_SS02", "properties": { "datetime": "2017-08-31T19:55:53.513448Z", @@ -140,8 +140,8 @@ 29.834836581964858 ], "stac_extensions": [ - "eo", - "view" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "collection": "planet-disaster-data" } \ No newline at end of file diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json index 693c4b0b8..a39c7aa6f 100644 --- a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json +++ b/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "Houston-East-20170831-103f-100d-0f4f-RGB", "properties": { "datetime": "2017-08-31T17:24:57.555491Z", @@ -13,7 +13,7 @@ "view:sun_azimuth": 145.5, "view:sun_elevation": 64.9, "view:off_nadir": 0.2, - "proj:epsg_code": 32615, + "proj:epsg": 32615, "pl:ground_control": true }, "geometry": { @@ -84,9 +84,9 @@ 30.157560439570304 ], "stac_extensions": [ - "eo", - "view", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "collection": "planet-disaster-data" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/catalog.json b/tests/data-files/catalogs/test-case-1/catalog.json index 56f92411d..d2d07610d 100644 --- a/tests/data-files/catalogs/test-case-1/catalog.json +++ b/tests/data-files/catalogs/test-case-1/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "test", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test catalog", "links": [ { @@ -18,5 +19,6 @@ "href": "./catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json index c78c81b26..3c43c3581 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json @@ -1,69 +1,70 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-1-1-imagery", - "properties": { - "datetime": "2019-10-04 18:55:37Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 4.275202171119132 - ], - [ - -2.5048828125, - 4.275202171119132 - ], - [ - -2.5048828125, - 3.8916575492899987 - ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-1-1", - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-1-1-imagery", + "properties": { + "datetime": "2019-10-04T18:55:37Z" }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "ortho": { - "href": "http://example.com/area-1-1_ortho.tif", - "type": "image/vnd.stac.geotiff" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "ortho": { + "href": "http://example.com/area-1-1_ortho.tif", + "type": "image/vnd.stac.geotiff" + }, + "dsm": { + "href": "http://example.com/area-1-1_dsm.tif", + "type": "image/vnd.stac.geotiff" + } }, - "dsm": { - "href": "http://example.com/area-1-1_dsm.tif", - "type": "image/vnd.stac.geotiff" - } - } -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [], + "collection": "area-1-1" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json index a2c203a52..d6204f9e6 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json @@ -1,93 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-1-1-labels", - "properties": { - "datetime": "2019-10-04 18:55:37Z", - "label:description": "labels for area-1-1", - "label:type": "vector", - "label:properties": [ - "label" - ], - "label:classes": [ - { - "name": "label", - "classes": [ - "one", - "two" - ] - } - ], - "label:task": [ - "classification" - ], - "label:method": [ - "manual" - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-1-1-labels", + "properties": { + "datetime": "2019-10-04T18:55:37Z", + "label:description": "labels for area-1-1", + "label:type": "vector", + "label:properties": [ + "label" ], - [ - -1.9610595703125, - 4.275202171119132 + "label:classes": [ + { + "name": "label", + "classes": [ + "one", + "two" + ] + } ], - [ - -2.5048828125, - 4.275202171119132 + "label:tasks": [ + "classification" ], - [ - -2.5048828125, - 3.8916575492899987 + "label:methods": [ + "manual" ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-1-1", - "links": [ - { - "rel": "source", - "href": "../area-1-1-imagery/area-1-1-imagery.json", - "type": "application/json" }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "source", + "href": "../area-1-1-imagery/area-1-1-imagery.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "http://example.com/area-1-1-labels.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "http://example.com/area-1-1-labels.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "area-1-1" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json index 8e1c7f953..51e3950e1 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "area-1-1", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test collection country-1", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json index a8535f7ff..7cf5dbfd5 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json @@ -1,69 +1,70 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-1-2-imagery", - "properties": { - "datetime": "2019-10-04 18:55:37Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 4.275202171119132 - ], - [ - -2.5048828125, - 4.275202171119132 - ], - [ - -2.5048828125, - 3.8916575492899987 - ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-1-2", - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-1-2-imagery", + "properties": { + "datetime": "2019-10-04T18:55:37Z" }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "ortho": { - "href": "http://example.com/area-1-2_ortho.tif", - "type": "image/vnd.stac.geotiff" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "ortho": { + "href": "http://example.com/area-1-2_ortho.tif", + "type": "image/vnd.stac.geotiff" + }, + "dsm": { + "href": "http://example.com/area-1-2_dsm.tif", + "type": "image/vnd.stac.geotiff" + } }, - "dsm": { - "href": "http://example.com/area-1-2_dsm.tif", - "type": "image/vnd.stac.geotiff" - } - } -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [], + "collection": "area-1-2" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json index 9e3c4517b..0b3b4ae9f 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json @@ -1,93 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-1-2-labels", - "properties": { - "datetime": "2019-10-04 18:55:37Z", - "label:description": "labels for area-1-2", - "label:type": "vector", - "label:properties": [ - "label" - ], - "label:classes": [ - { - "name": "label", - "classes": [ - "one", - "two" - ] - } - ], - "label:task": [ - "classification" - ], - "label:method": [ - "manual" - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-1-2-labels", + "properties": { + "datetime": "2019-10-04T18:55:37Z", + "label:description": "labels for area-1-2", + "label:type": "vector", + "label:properties": [ + "label" ], - [ - -1.9610595703125, - 4.275202171119132 + "label:classes": [ + { + "name": "label", + "classes": [ + "one", + "two" + ] + } ], - [ - -2.5048828125, - 4.275202171119132 + "label:tasks": [ + "classification" ], - [ - -2.5048828125, - 3.8916575492899987 + "label:methods": [ + "manual" ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-1-2", - "links": [ - { - "rel": "source", - "href": "../area-1-2-imagery/area-1-2-imagery.json", - "type": "application/json" }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "source", + "href": "../area-1-2-imagery/area-1-2-imagery.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "http://example.com/area-1-2-labels.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "http://example.com/area-1-2-labels.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "area-1-2" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json index 0f1743608..580ab0fa0 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json +++ b/tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "area-1-2", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test collection country-1", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ diff --git a/tests/data-files/catalogs/test-case-1/country-1/catalog.json b/tests/data-files/catalogs/test-case-1/country-1/catalog.json index 7fa89aead..f1c0e3108 100644 --- a/tests/data-files/catalogs/test-case-1/country-1/catalog.json +++ b/tests/data-files/catalogs/test-case-1/country-1/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "country-1", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test catalog country-1", "links": [ { @@ -23,5 +24,6 @@ "href": "../catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json index b13143c40..32ed274a0 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json @@ -1,69 +1,70 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-2-1-imagery", - "properties": { - "datetime": "2019-10-04 18:55:37Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 4.275202171119132 - ], - [ - -2.5048828125, - 4.275202171119132 - ], - [ - -2.5048828125, - 3.8916575492899987 - ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-2-1", - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-2-1-imagery", + "properties": { + "datetime": "2019-10-04T18:55:37Z" }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "ortho": { - "href": "http://example.com/area-2-1_ortho.tif", - "type": "image/vnd.stac.geotiff" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "ortho": { + "href": "http://example.com/area-2-1_ortho.tif", + "type": "image/vnd.stac.geotiff" + }, + "dsm": { + "href": "http://example.com/area-2-1_dsm.tif", + "type": "image/vnd.stac.geotiff" + } }, - "dsm": { - "href": "http://example.com/area-2-1_dsm.tif", - "type": "image/vnd.stac.geotiff" - } - } -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [], + "collection": "area-2-1" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json index 267467a6f..d05be47fb 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json @@ -1,93 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-2-1-labels", - "properties": { - "datetime": "2019-10-04 18:55:37Z", - "label:description": "labels for area-2-1", - "label:type": "vector", - "label:properties": [ - "label" - ], - "label:classes": [ - { - "name": "label", - "classes": [ - "one", - "two" - ] - } - ], - "label:task": [ - "classification" - ], - "label:method": [ - "manual" - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-2-1-labels", + "properties": { + "datetime": "2019-10-04T18:55:37Z", + "label:description": "labels for area-2-1", + "label:type": "vector", + "label:properties": [ + "label" ], - [ - -1.9610595703125, - 4.275202171119132 + "label:classes": [ + { + "name": "label", + "classes": [ + "one", + "two" + ] + } ], - [ - -2.5048828125, - 4.275202171119132 + "label:tasks": [ + "classification" ], - [ - -2.5048828125, - 3.8916575492899987 + "label:methods": [ + "manual" ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-2-1", - "links": [ - { - "rel": "source", - "href": "../area-2-1-imagery/area-2-1-imagery.json", - "type": "application/json" }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "source", + "href": "../area-2-1-imagery/area-2-1-imagery.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "http://example.com/area-2-1-labels.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "http://example.com/area-2-1-labels.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "area-2-1" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json index 417afd754..4865aaf50 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "area-2-1", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test collection country-2", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json index 3ed5c71f3..de99f978b 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json @@ -1,69 +1,70 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-2-2-imagery", - "properties": { - "datetime": "2019-10-04 18:55:37Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 4.275202171119132 - ], - [ - -2.5048828125, - 4.275202171119132 - ], - [ - -2.5048828125, - 3.8916575492899987 - ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-2-2", - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-2-2-imagery", + "properties": { + "datetime": "2019-10-04T18:55:37Z" }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "ortho": { - "href": "http://example.com/area-2-2_ortho.tif", - "type": "image/vnd.stac.geotiff" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "ortho": { + "href": "http://example.com/area-2-2_ortho.tif", + "type": "image/vnd.stac.geotiff" + }, + "dsm": { + "href": "http://example.com/area-2-2_dsm.tif", + "type": "image/vnd.stac.geotiff" + } }, - "dsm": { - "href": "http://example.com/area-2-2_dsm.tif", - "type": "image/vnd.stac.geotiff" - } - } -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [], + "collection": "area-2-2" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json index 8ce9a7d5b..50bddf151 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json @@ -1,93 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "area-2-2-labels", - "properties": { - "datetime": "2019-10-04 18:55:37Z", - "label:description": "labels for area-2-2", - "label:type": "vector", - "label:properties": [ - "label" - ], - "label:classes": [ - { - "name": "label", - "classes": [ - "one", - "two" - ] - } - ], - "label:task": [ - "classification" - ], - "label:method": [ - "manual" - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "area-2-2-labels", + "properties": { + "datetime": "2019-10-04T18:55:37Z", + "label:description": "labels for area-2-2", + "label:type": "vector", + "label:properties": [ + "label" ], - [ - -1.9610595703125, - 4.275202171119132 + "label:classes": [ + { + "name": "label", + "classes": [ + "one", + "two" + ] + } ], - [ - -2.5048828125, - 4.275202171119132 + "label:tasks": [ + "classification" ], - [ - -2.5048828125, - 3.8916575492899987 + "label:methods": [ + "manual" ] - ] - ] - }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "area-2-2", - "links": [ - { - "rel": "source", - "href": "../area-2-2-imagery/area-2-2-imagery.json", - "type": "application/json" }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 + ] + ] + ] }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "source", + "href": "../area-2-2-imagery/area-2-2-imagery.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "http://example.com/area-2-2-labels.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "http://example.com/area-2-2-labels.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] -} + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "area-2-2" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json index d0cd0cf5a..6274a9d9d 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json +++ b/tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "area-2-2", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test collection country-2", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ diff --git a/tests/data-files/catalogs/test-case-1/country-2/catalog.json b/tests/data-files/catalogs/test-case-1/country-2/catalog.json index d7696d8fb..7441b2a77 100644 --- a/tests/data-files/catalogs/test-case-1/country-2/catalog.json +++ b/tests/data-files/catalogs/test-case-1/country-2/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "country-2", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test catalog country-2", "links": [ { @@ -23,5 +24,6 @@ "href": "../catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json index 714c0b535..27a7066fa 100644 --- a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json +++ b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json @@ -1,914 +1,914 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "cf73ec1a-d790-4b59-b077-e101738571ed", - "properties": { - "label:description": "Labels in layer", - "label:type": "vector", - "datetime": "2019-08-07 20:37:10Z", - "label:method": [ - "manual" - ], - "label:classes": [ - { - "name": "label", - "classes": [ - "Passenger Vehicle", - "Bus", - "Truck" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "cf73ec1a-d790-4b59-b077-e101738571ed", + "properties": { + "label:description": "Labels in layer", + "label:type": "vector", + "datetime": "2019-08-07T20:37:10Z", + "label:classes": [ + { + "name": "label", + "classes": [ + "Passenger Vehicle", + "Bus", + "Truck" + ] + } + ], + "label:properties": [ + "label" + ], + "label:tasks": [ + "detection" + ], + "label:methods": [ + "manual" ] - } - ], - "label:task": [ - "detection" - ], - "label:properties": [ - "label" - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -86.75155098633836, - 35.686645216009545 - ], - [ - -86.75155102590682, - 35.68664554635556 - ], - [ - -86.75155102590682, - 35.90922661159095 - ], - [ - -86.75155098633836, - 35.90922694104629 - ], - [ - -86.75155058459285, - 35.90922697349481 - ], - [ - -86.62304646828477, - 35.90922697349481 - ], - [ - -86.62304606653927, - 35.90922694104629 - ], - [ - -86.6230460269708, - 35.90922661159095 - ], - [ - -86.6230460269708, - 35.87775813030909 - ], - [ - -86.62304585436203, - 35.87775669258231 - ], - [ - -86.623045343169, - 35.87775531010654 - ], - [ - -86.62304451303658, - 35.87775403600955 - ], - [ - -86.62304339586625, - 35.877752919254185 - ], - [ - -86.62304203459027, - 35.87775200275673 - ], - [ - -86.62304048152171, - 35.87775132173768 - ], - [ - -86.62303879634413, - 35.877750902368206 - ], - [ - -86.62303704381796, - 35.87775076076447 - ], - [ - -86.43439019325102, - 35.87775076076447 - ], - [ - -86.43438979150552, - 35.87775072830352 - ], - [ - -86.43438975193705, - 35.87775039872192 - ], - [ - -86.43438975193705, - 35.810302095824625 - ], - [ - -86.43438979150552, - 35.81030176597282 - ], - [ - -86.43439019325102, - 35.810301733485254 - ], - [ - -86.56561991663378, - 35.810301733485254 - ], - [ - -86.56562166915995, - 35.81030159176542 - ], - [ - -86.56562335433752, - 35.81030117205213 - ], - [ - -86.56562490740609, - 35.81030049047472 - ], - [ - -86.56562626868205, - 35.81029957322583 - ], - [ - -86.56562738585238, - 35.8102984555548 - ], - [ - -86.56562821598482, - 35.810297180413116 - ], - [ - -86.56562872717785, - 35.810295796803736 - ], - [ - -86.56562889978662, - 35.81029435789799 - ], - [ - -86.56562889978662, - 35.80805379492521 - ], - [ - -86.56562893935508, - 35.8080534650644 - ], - [ - -86.56562934110059, - 35.808053432575946 - ], - [ - -86.5929614057691, - 35.808053432575946 - ], - [ - -86.59296315829528, - 35.80805329085224 - ], - [ - -86.59296484347286, - 35.8080528711275 - ], - [ - -86.59296639654143, - 35.808052189531494 - ], - [ - -86.59296775781738, - 35.80805127225758 - ], - [ - -86.5929688749877, - 35.808050154556064 - ], - [ - -86.59296970512014, - 35.80804887937959 - ], - [ - -86.59297021631318, - 35.80804749573245 - ], - [ - -86.59297038892194, - 35.80804605678745 - ], - [ - -86.59297038892194, - 35.72487368215667 - ], - [ - -86.59297021631318, - 35.72487224176095 - ], - [ - -86.59296970512014, - 35.72487085671881 - ], - [ - -86.5929688749877, - 35.72486958025665 - ], - [ - -86.59296775781738, - 35.72486846142819 - ], - [ - -86.59296639654143, - 35.724867543229394 - ], - [ - -86.59296484347286, - 35.72486686094612 - ], - [ - -86.59296315829528, - 35.72486644079816 - ], - [ - -86.5929614057691, - 35.72486629893155 - ], - [ - -86.59023668132238, - 35.72486629893155 - ], - [ - -86.59023627957687, - 35.72486626641034 - ], - [ - -86.59023624000841, - 35.72486593621692 - ], - [ - -86.59023624000841, - 35.68664554635556 - ], - [ - -86.59023627957687, - 35.686645216009545 - ], - [ - -86.59023668132238, - 35.68664518347332 - ], - [ - -86.75155058459285, - 35.68664518347332 - ], - [ - -86.75155098633836, - 35.686645216009545 - ] - ], - [ - [ - -86.57382236337436, - 35.857516052580706 - ], - [ - -86.57382411590054, - 35.857515910942126 - ], - [ - -86.57382580107813, - 35.85751549146944 - ], - [ - -86.57382735414667, - 35.85751481028278 - ], - [ - -86.57382871542264, - 35.857513893559734 - ], - [ - -86.57382983259296, - 35.85751277652946 - ], - [ - -86.5738306627254, - 35.85751150211878 - ], - [ - -86.57383117391844, - 35.85751011930261 - ], - [ - -86.5738313465272, - 35.857508681221766 - ], - [ - -86.5738313465272, - 35.85527512323118 - ], - [ - -86.57383117391844, - 35.855273685111264 - ], - [ - -86.5738306627254, - 35.85527230225746 - ], - [ - -86.57382983259296, - 35.85527102781209 - ], - [ - -86.57382871542264, - 35.85526991075136 - ], - [ - -86.57382735414667, - 35.8552689940033 - ], - [ - -86.57382580107813, - 35.85526831279802 - ], - [ - -86.57382411590054, - 35.855267893313886 - ], - [ - -86.57382236337436, - 35.85526775167143 - ], - [ - -86.57110618076653, - 35.85526775167143 - ], - [ - -86.57110442824035, - 35.855267893313886 - ], - [ - -86.57110274306277, - 35.85526831279802 - ], - [ - -86.57110118999421, - 35.8552689940033 - ], - [ - -86.57109982871825, - 35.85526991075136 - ], - [ - -86.57109871154792, - 35.85527102781209 - ], - [ - -86.57109788141548, - 35.85527230225746 - ], - [ - -86.57109737022245, - 35.855273685111264 - ], - [ - -86.57109719761368, - 35.85527512323118 - ], - [ - -86.57109719761368, - 35.857508681221766 - ], - [ - -86.57109737022245, - 35.85751011930261 - ], - [ - -86.57109788141548, - 35.85751150211878 - ], - [ - -86.57109871154792, - 35.85751277652946 - ], - [ - -86.57109982871825, - 35.857513893559734 - ], - [ - -86.57110118999421, - 35.85751481028278 - ], - [ - -86.57110274306277, - 35.85751549146944 - ], - [ - -86.57110442824035, - 35.857515910942126 - ], - [ - -86.57110618076653, - 35.857516052580706 - ], - [ - -86.57382236337436, - 35.857516052580706 - ] - ], - [ - [ - -86.6312484737114, - 35.742860087824354 - ], - [ - -86.63124830110263, - 35.7428586477421 - ], - [ - -86.6312477899096, - 35.742857263001376 - ], - [ - -86.63124695977717, - 35.742855986817 - ], - [ - -86.63124584260684, - 35.742854868232016 - ], - [ - -86.63124448133088, - 35.742853950233034 - ], - [ - -86.63124292826232, - 35.742853268098244 - ], - [ - -86.63124124308473, - 35.742852848041714 - ], - [ - -86.63123949055856, - 35.74285270620598 - ], - [ - -86.6285233079507, - 35.74285270620598 - ], - [ - -86.62852155542451, - 35.742852848041714 - ], - [ - -86.62851987024693, - 35.742853268098244 - ], - [ - -86.62851831717838, - 35.742853950233034 - ], - [ - -86.62851695590241, - 35.742854868232016 - ], - [ - -86.62851583873208, - 35.742855986817 - ], - [ - -86.62851500859965, - 35.742857263001376 - ], - [ - -86.62851449740661, - 35.7428586477421 - ], - [ - -86.62851432479786, - 35.742860087824354 - ], - [ - -86.62851432479786, - 35.74509362569718 - ], - [ - -86.62851449740661, - 35.74509506574047 - ], - [ - -86.62851500859965, - 35.74509645044369 - ], - [ - -86.62851583873208, - 35.74509772659346 - ], - [ - -86.62851695590241, - 35.745098845148064 - ], - [ - -86.62851831717838, - 35.7450997631221 - ], - [ - -86.62851987024693, - 35.74510044523834 - ], - [ - -86.62852155542451, - 35.74510086528344 - ], - [ - -86.6285233079507, - 35.74510100711532 - ], - [ - -86.63123949055856, - 35.74510100711532 - ], - [ - -86.63124124308473, - 35.74510086528344 - ], - [ - -86.63124292826232, - 35.74510044523834 - ], - [ - -86.63124448133088, - 35.7450997631221 - ], - [ - -86.63124584260684, - 35.745098845148064 - ], - [ - -86.63124695977717, - 35.74509772659346 - ], - [ - -86.6312477899096, - 35.74509645044369 - ], - [ - -86.63124830110263, - 35.74509506574047 - ], - [ - -86.6312484737114, - 35.74509362569718 - ], - [ - -86.6312484737114, - 35.742860087824354 - ] - ], - [ - [ - -86.6640582606738, - 35.84178531898385 - ], - [ - -86.66405808806503, - 35.84178388062817 - ], - [ - -86.664057576872, - 35.84178249754766 - ], - [ - -86.66405674673958, - 35.84178122289334 - ], - [ - -86.66405562956925, - 35.84178010564947 - ], - [ - -86.66405426829328, - 35.84177918875111 - ], - [ - -86.66405271522473, - 35.84177850743416 - ], - [ - -86.66405103004713, - 35.841778087881245 - ], - [ - -86.66404927752096, - 35.84177794621557 - ], - [ - -86.66133309491308, - 35.84177794621557 - ], - [ - -86.6613313423869, - 35.841778087881245 - ], - [ - -86.66132965720934, - 35.84177850743416 - ], - [ - -86.66132810414076, - 35.84177918875111 - ], - [ - -86.6613267428648, - 35.84178010564947 - ], - [ - -86.66132562569447, - 35.84178122289334 - ], - [ - -86.66132479556204, - 35.84178249754766 - ], - [ - -86.661324284369, - 35.84178388062817 - ], - [ - -86.66132411176024, - 35.84178531898385 - ], - [ - -86.66132411176024, - 35.84401887455732 - ], - [ - -86.661324284369, - 35.84402031287395 - ], - [ - -86.66132479556204, - 35.84402169591686 - ], - [ - -86.66132562569447, - 35.84402297053648 - ], - [ - -86.6613267428648, - 35.84402408774991 - ], - [ - -86.66132810414076, - 35.844025004623255 - ], - [ - -86.66132965720934, - 35.84402568592161 - ], - [ - -86.6613313423869, - 35.84402610546306 - ], - [ - -86.66133309491308, - 35.844026247124866 - ], - [ - -86.66404927752096, - 35.844026247124866 - ], - [ - -86.66405103004713, - 35.84402610546306 - ], - [ - -86.66405271522473, - 35.84402568592161 - ], - [ - -86.66405426829328, - 35.844025004623255 - ], - [ - -86.66405562956925, - 35.84402408774991 - ], - [ - -86.66405674673958, - 35.84402297053648 - ], - [ - -86.664057576872, - 35.84402169591686 - ], - [ - -86.66405808806503, - 35.84402031287395 - ], - [ - -86.6640582606738, - 35.84401887455732 - ], - [ - -86.6640582606738, - 35.84178531898385 - ] - ], - [ - [ - -86.67226070741438, - 35.7675913956163 - ], - [ - -86.67226053480562, - 35.76758995596529 - ], - [ - -86.67226002361258, - 35.76758857163924 - ], - [ - -86.67225919348014, - 35.767587295837004 - ], - [ - -86.67225807630982, - 35.767586177586985 - ], - [ - -86.67225671503387, - 35.76758525986291 - ], - [ - -86.6722551619653, - 35.76758457793238 - ], - [ - -86.67225347678772, - 35.767584158001654 - ], - [ - -86.67225172426154, - 35.767584016208396 - ], - [ - -86.66953554165369, - 35.767584016208396 - ], - [ - -86.66953378912751, - 35.767584158001654 - ], - [ - -86.66953210394993, - 35.76758457793238 - ], - [ - -86.66953055088136, - 35.76758525986291 - ], - [ - -86.66952918960541, - 35.767586177586985 - ], - [ - -86.66952807243509, - 35.767587295837004 - ], - [ - -86.66952724230264, - 35.76758857163924 - ], - [ - -86.66952673110961, - 35.76758995596529 - ], - [ - -86.66952655850085, - 35.7675913956163 - ], - [ - -86.66952655850085, - 35.76982493791015 - ], - [ - -86.66952673110961, - 35.76982637752218 - ], - [ - -86.66952724230264, - 35.7698277618107 - ], - [ - -86.66952807243509, - 35.7698290375783 - ], - [ - -86.66952918960541, - 35.76983015579792 - ], - [ - -86.66953055088136, - 35.76983107349703 - ], - [ - -86.66953210394993, - 35.769831755409 - ], - [ - -86.66953378912751, - 35.769832175328304 - ], - [ - -86.66953554165369, - 35.76983231711771 - ], - [ - -86.67225172426154, - 35.76983231711771 - ], - [ - -86.67225347678772, - 35.769832175328304 - ], - [ - -86.6722551619653, - 35.769831755409 - ], - [ - -86.67225671503387, - 35.76983107349703 - ], - [ - -86.67225807630982, - 35.76983015579792 - ], - [ - -86.67225919348014, - 35.7698290375783 - ], - [ - -86.67226002361258, - 35.7698277618107 - ], - [ - -86.67226053480562, - 35.76982637752218 - ], - [ - -86.67226070741438, - 35.76982493791015 - ], - [ - -86.67226070741438, - 35.7675913956163 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -86.75155098633836, + 35.686645216009545 + ], + [ + -86.75155102590682, + 35.68664554635556 + ], + [ + -86.75155102590682, + 35.90922661159095 + ], + [ + -86.75155098633836, + 35.90922694104629 + ], + [ + -86.75155058459285, + 35.90922697349481 + ], + [ + -86.62304646828477, + 35.90922697349481 + ], + [ + -86.62304606653927, + 35.90922694104629 + ], + [ + -86.6230460269708, + 35.90922661159095 + ], + [ + -86.6230460269708, + 35.87775813030909 + ], + [ + -86.62304585436203, + 35.87775669258231 + ], + [ + -86.623045343169, + 35.87775531010654 + ], + [ + -86.62304451303658, + 35.87775403600955 + ], + [ + -86.62304339586625, + 35.877752919254185 + ], + [ + -86.62304203459027, + 35.87775200275673 + ], + [ + -86.62304048152171, + 35.87775132173768 + ], + [ + -86.62303879634413, + 35.877750902368206 + ], + [ + -86.62303704381796, + 35.87775076076447 + ], + [ + -86.43439019325102, + 35.87775076076447 + ], + [ + -86.43438979150552, + 35.87775072830352 + ], + [ + -86.43438975193705, + 35.87775039872192 + ], + [ + -86.43438975193705, + 35.810302095824625 + ], + [ + -86.43438979150552, + 35.81030176597282 + ], + [ + -86.43439019325102, + 35.810301733485254 + ], + [ + -86.56561991663378, + 35.810301733485254 + ], + [ + -86.56562166915995, + 35.81030159176542 + ], + [ + -86.56562335433752, + 35.81030117205213 + ], + [ + -86.56562490740609, + 35.81030049047472 + ], + [ + -86.56562626868205, + 35.81029957322583 + ], + [ + -86.56562738585238, + 35.8102984555548 + ], + [ + -86.56562821598482, + 35.810297180413116 + ], + [ + -86.56562872717785, + 35.810295796803736 + ], + [ + -86.56562889978662, + 35.81029435789799 + ], + [ + -86.56562889978662, + 35.80805379492521 + ], + [ + -86.56562893935508, + 35.8080534650644 + ], + [ + -86.56562934110059, + 35.808053432575946 + ], + [ + -86.5929614057691, + 35.808053432575946 + ], + [ + -86.59296315829528, + 35.80805329085224 + ], + [ + -86.59296484347286, + 35.8080528711275 + ], + [ + -86.59296639654143, + 35.808052189531494 + ], + [ + -86.59296775781738, + 35.80805127225758 + ], + [ + -86.5929688749877, + 35.808050154556064 + ], + [ + -86.59296970512014, + 35.80804887937959 + ], + [ + -86.59297021631318, + 35.80804749573245 + ], + [ + -86.59297038892194, + 35.80804605678745 + ], + [ + -86.59297038892194, + 35.72487368215667 + ], + [ + -86.59297021631318, + 35.72487224176095 + ], + [ + -86.59296970512014, + 35.72487085671881 + ], + [ + -86.5929688749877, + 35.72486958025665 + ], + [ + -86.59296775781738, + 35.72486846142819 + ], + [ + -86.59296639654143, + 35.724867543229394 + ], + [ + -86.59296484347286, + 35.72486686094612 + ], + [ + -86.59296315829528, + 35.72486644079816 + ], + [ + -86.5929614057691, + 35.72486629893155 + ], + [ + -86.59023668132238, + 35.72486629893155 + ], + [ + -86.59023627957687, + 35.72486626641034 + ], + [ + -86.59023624000841, + 35.72486593621692 + ], + [ + -86.59023624000841, + 35.68664554635556 + ], + [ + -86.59023627957687, + 35.686645216009545 + ], + [ + -86.59023668132238, + 35.68664518347332 + ], + [ + -86.75155058459285, + 35.68664518347332 + ], + [ + -86.75155098633836, + 35.686645216009545 + ] + ], + [ + [ + -86.57382236337436, + 35.857516052580706 + ], + [ + -86.57382411590054, + 35.857515910942126 + ], + [ + -86.57382580107813, + 35.85751549146944 + ], + [ + -86.57382735414667, + 35.85751481028278 + ], + [ + -86.57382871542264, + 35.857513893559734 + ], + [ + -86.57382983259296, + 35.85751277652946 + ], + [ + -86.5738306627254, + 35.85751150211878 + ], + [ + -86.57383117391844, + 35.85751011930261 + ], + [ + -86.5738313465272, + 35.857508681221766 + ], + [ + -86.5738313465272, + 35.85527512323118 + ], + [ + -86.57383117391844, + 35.855273685111264 + ], + [ + -86.5738306627254, + 35.85527230225746 + ], + [ + -86.57382983259296, + 35.85527102781209 + ], + [ + -86.57382871542264, + 35.85526991075136 + ], + [ + -86.57382735414667, + 35.8552689940033 + ], + [ + -86.57382580107813, + 35.85526831279802 + ], + [ + -86.57382411590054, + 35.855267893313886 + ], + [ + -86.57382236337436, + 35.85526775167143 + ], + [ + -86.57110618076653, + 35.85526775167143 + ], + [ + -86.57110442824035, + 35.855267893313886 + ], + [ + -86.57110274306277, + 35.85526831279802 + ], + [ + -86.57110118999421, + 35.8552689940033 + ], + [ + -86.57109982871825, + 35.85526991075136 + ], + [ + -86.57109871154792, + 35.85527102781209 + ], + [ + -86.57109788141548, + 35.85527230225746 + ], + [ + -86.57109737022245, + 35.855273685111264 + ], + [ + -86.57109719761368, + 35.85527512323118 + ], + [ + -86.57109719761368, + 35.857508681221766 + ], + [ + -86.57109737022245, + 35.85751011930261 + ], + [ + -86.57109788141548, + 35.85751150211878 + ], + [ + -86.57109871154792, + 35.85751277652946 + ], + [ + -86.57109982871825, + 35.857513893559734 + ], + [ + -86.57110118999421, + 35.85751481028278 + ], + [ + -86.57110274306277, + 35.85751549146944 + ], + [ + -86.57110442824035, + 35.857515910942126 + ], + [ + -86.57110618076653, + 35.857516052580706 + ], + [ + -86.57382236337436, + 35.857516052580706 + ] + ], + [ + [ + -86.6312484737114, + 35.742860087824354 + ], + [ + -86.63124830110263, + 35.7428586477421 + ], + [ + -86.6312477899096, + 35.742857263001376 + ], + [ + -86.63124695977717, + 35.742855986817 + ], + [ + -86.63124584260684, + 35.742854868232016 + ], + [ + -86.63124448133088, + 35.742853950233034 + ], + [ + -86.63124292826232, + 35.742853268098244 + ], + [ + -86.63124124308473, + 35.742852848041714 + ], + [ + -86.63123949055856, + 35.74285270620598 + ], + [ + -86.6285233079507, + 35.74285270620598 + ], + [ + -86.62852155542451, + 35.742852848041714 + ], + [ + -86.62851987024693, + 35.742853268098244 + ], + [ + -86.62851831717838, + 35.742853950233034 + ], + [ + -86.62851695590241, + 35.742854868232016 + ], + [ + -86.62851583873208, + 35.742855986817 + ], + [ + -86.62851500859965, + 35.742857263001376 + ], + [ + -86.62851449740661, + 35.7428586477421 + ], + [ + -86.62851432479786, + 35.742860087824354 + ], + [ + -86.62851432479786, + 35.74509362569718 + ], + [ + -86.62851449740661, + 35.74509506574047 + ], + [ + -86.62851500859965, + 35.74509645044369 + ], + [ + -86.62851583873208, + 35.74509772659346 + ], + [ + -86.62851695590241, + 35.745098845148064 + ], + [ + -86.62851831717838, + 35.7450997631221 + ], + [ + -86.62851987024693, + 35.74510044523834 + ], + [ + -86.62852155542451, + 35.74510086528344 + ], + [ + -86.6285233079507, + 35.74510100711532 + ], + [ + -86.63123949055856, + 35.74510100711532 + ], + [ + -86.63124124308473, + 35.74510086528344 + ], + [ + -86.63124292826232, + 35.74510044523834 + ], + [ + -86.63124448133088, + 35.7450997631221 + ], + [ + -86.63124584260684, + 35.745098845148064 + ], + [ + -86.63124695977717, + 35.74509772659346 + ], + [ + -86.6312477899096, + 35.74509645044369 + ], + [ + -86.63124830110263, + 35.74509506574047 + ], + [ + -86.6312484737114, + 35.74509362569718 + ], + [ + -86.6312484737114, + 35.742860087824354 + ] + ], + [ + [ + -86.6640582606738, + 35.84178531898385 + ], + [ + -86.66405808806503, + 35.84178388062817 + ], + [ + -86.664057576872, + 35.84178249754766 + ], + [ + -86.66405674673958, + 35.84178122289334 + ], + [ + -86.66405562956925, + 35.84178010564947 + ], + [ + -86.66405426829328, + 35.84177918875111 + ], + [ + -86.66405271522473, + 35.84177850743416 + ], + [ + -86.66405103004713, + 35.841778087881245 + ], + [ + -86.66404927752096, + 35.84177794621557 + ], + [ + -86.66133309491308, + 35.84177794621557 + ], + [ + -86.6613313423869, + 35.841778087881245 + ], + [ + -86.66132965720934, + 35.84177850743416 + ], + [ + -86.66132810414076, + 35.84177918875111 + ], + [ + -86.6613267428648, + 35.84178010564947 + ], + [ + -86.66132562569447, + 35.84178122289334 + ], + [ + -86.66132479556204, + 35.84178249754766 + ], + [ + -86.661324284369, + 35.84178388062817 + ], + [ + -86.66132411176024, + 35.84178531898385 + ], + [ + -86.66132411176024, + 35.84401887455732 + ], + [ + -86.661324284369, + 35.84402031287395 + ], + [ + -86.66132479556204, + 35.84402169591686 + ], + [ + -86.66132562569447, + 35.84402297053648 + ], + [ + -86.6613267428648, + 35.84402408774991 + ], + [ + -86.66132810414076, + 35.844025004623255 + ], + [ + -86.66132965720934, + 35.84402568592161 + ], + [ + -86.6613313423869, + 35.84402610546306 + ], + [ + -86.66133309491308, + 35.844026247124866 + ], + [ + -86.66404927752096, + 35.844026247124866 + ], + [ + -86.66405103004713, + 35.84402610546306 + ], + [ + -86.66405271522473, + 35.84402568592161 + ], + [ + -86.66405426829328, + 35.844025004623255 + ], + [ + -86.66405562956925, + 35.84402408774991 + ], + [ + -86.66405674673958, + 35.84402297053648 + ], + [ + -86.664057576872, + 35.84402169591686 + ], + [ + -86.66405808806503, + 35.84402031287395 + ], + [ + -86.6640582606738, + 35.84401887455732 + ], + [ + -86.6640582606738, + 35.84178531898385 + ] + ], + [ + [ + -86.67226070741438, + 35.7675913956163 + ], + [ + -86.67226053480562, + 35.76758995596529 + ], + [ + -86.67226002361258, + 35.76758857163924 + ], + [ + -86.67225919348014, + 35.767587295837004 + ], + [ + -86.67225807630982, + 35.767586177586985 + ], + [ + -86.67225671503387, + 35.76758525986291 + ], + [ + -86.6722551619653, + 35.76758457793238 + ], + [ + -86.67225347678772, + 35.767584158001654 + ], + [ + -86.67225172426154, + 35.767584016208396 + ], + [ + -86.66953554165369, + 35.767584016208396 + ], + [ + -86.66953378912751, + 35.767584158001654 + ], + [ + -86.66953210394993, + 35.76758457793238 + ], + [ + -86.66953055088136, + 35.76758525986291 + ], + [ + -86.66952918960541, + 35.767586177586985 + ], + [ + -86.66952807243509, + 35.767587295837004 + ], + [ + -86.66952724230264, + 35.76758857163924 + ], + [ + -86.66952673110961, + 35.76758995596529 + ], + [ + -86.66952655850085, + 35.7675913956163 + ], + [ + -86.66952655850085, + 35.76982493791015 + ], + [ + -86.66952673110961, + 35.76982637752218 + ], + [ + -86.66952724230264, + 35.7698277618107 + ], + [ + -86.66952807243509, + 35.7698290375783 + ], + [ + -86.66952918960541, + 35.76983015579792 + ], + [ + -86.66953055088136, + 35.76983107349703 + ], + [ + -86.66953210394993, + 35.769831755409 + ], + [ + -86.66953378912751, + 35.769832175328304 + ], + [ + -86.66953554165369, + 35.76983231711771 + ], + [ + -86.67225172426154, + 35.76983231711771 + ], + [ + -86.67225347678772, + 35.769832175328304 + ], + [ + -86.6722551619653, + 35.769831755409 + ], + [ + -86.67225671503387, + 35.76983107349703 + ], + [ + -86.67225807630982, + 35.76983015579792 + ], + [ + -86.67225919348014, + 35.7698290375783 + ], + [ + -86.67226002361258, + 35.7698277618107 + ], + [ + -86.67226053480562, + 35.76982637752218 + ], + [ + -86.67226070741438, + 35.76982493791015 + ], + [ + -86.67226070741438, + 35.7675913956163 + ] + ] ] - ] - ] - }, - "bbox": [ - -86.75155102590682, - 35.68664518347332, - -86.43438975193705, - 35.90922697349481 - ], - "links": [ - { - "rel": "source", - "href": "../../f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "title": "Source image STAC item for the label item" }, - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "source", + "href": "../../f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Source image STAC item for the label item" + }, + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "cf73ec1a-d790-4b59-b077-e101738571ed": { + "href": "./data.geojson", + "type": "application/geo+json", + "title": "Label Data Feature Collection" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "cf73ec1a-d790-4b59-b077-e101738571ed": { - "href": "./data.geojson", - "type": "application/geo+json", - "title": "Label Data Feature Collection" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -86.75155102590682, + 35.68664518347332, + -86.43438975193705, + 35.90922697349481 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json index 263b1f8a2..dbad0e5dc 100644 --- a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json +++ b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "5b922d42-9a77-4f79-a672-86096f7f849e", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Label collection in layer 1a8c1632-fa91-4a62-b33e-3a87c2ebdf16", "links": [ { @@ -20,7 +21,12 @@ "type": "application/json" } ], + "stac_extensions": [], "title": "Label collection", + "keywords": [], + "version": "1", + "providers": [], + "properties": {}, "extent": { "spatial": { "bbox": [ @@ -41,9 +47,5 @@ ] } }, - "license": "proprietary", - "keywords": [], - "version": "1", - "providers": [], - "properties": {} + "license": "proprietary" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json index c8d614c88..1cea4cec1 100644 --- a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json +++ b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "1a8c1632-fa91-4a62-b33e-3a87c2ebdf16", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Project layer collection", "links": [ { @@ -26,7 +27,12 @@ "type": "application/json" } ], + "stac_extensions": [], "title": "Layers", + "keywords": [], + "version": "1", + "providers": [], + "properties": {}, "extent": { "spatial": { "bbox": [ @@ -47,9 +53,5 @@ ] } }, - "license": "proprietary", - "keywords": [], - "version": "1", - "providers": [], - "properties": {} + "license": "proprietary" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json index 6f431f9cc..9f7105872 100644 --- a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json +++ b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "f433578c-f879-414d-8101-83142a0a13c3", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Scene collection in layer 1a8c1632-fa91-4a62-b33e-3a87c2ebdf16", "links": [ { @@ -20,7 +21,12 @@ "type": "application/json" } ], + "stac_extensions": [], "title": "Scene collection", + "keywords": [], + "version": "1", + "providers": [], + "properties": {}, "extent": { "spatial": { "bbox": [ @@ -41,9 +47,5 @@ ] } }, - "license": "proprietary", - "keywords": [], - "version": "1", - "providers": [], - "properties": {} + "license": "proprietary" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json index 9c6ade178..b2e249c36 100644 --- a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json +++ b/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json @@ -1,63 +1,63 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "d43bead8-e3f8-4c51-95d6-e24e750a402b", - "properties": { - "datetime": "2019-08-07 20:37:10Z" - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - -86.75260925292969, - 35.685734110176064 - ], - [ - -86.75260925292969, - 35.9078947564485 - ], - [ - -86.43606567382814, - 35.9078947564485 - ], - [ - -86.43606567382814, - 35.685734110176064 - ], - [ - -86.75260925292969, - 35.685734110176064 - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "d43bead8-e3f8-4c51-95d6-e24e750a402b", + "properties": { + "datetime": "2019-08-07T20:37:10Z" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -86.75260925292969, + 35.685734110176064 + ], + [ + -86.75260925292969, + 35.9078947564485 + ], + [ + -86.43606567382814, + 35.9078947564485 + ], + [ + -86.43606567382814, + 35.685734110176064 + ], + [ + -86.75260925292969, + 35.685734110176064 + ] + ] + ] ] - ] - ] - }, - "bbox": [ - -86.75260925292969, - 35.685734110176064, - -86.43606567382814, - 35.9078947564485 - ], - "links": [ - { - "rel": "root", - "href": "../../../catalog.json", - "type": "application/json" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "d43bead8-e3f8-4c51-95d6-e24e750a402b": { - "href": "s3://somebucket/some/sort/of/COG.tif", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "title": "scene" - } - }, - "stac_extensions": [] + "links": [ + { + "rel": "root", + "href": "../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "d43bead8-e3f8-4c51-95d6-e24e750a402b": { + "href": "s3://somebucket/some/sort/of/COG.tif", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" + } + }, + "bbox": [ + -86.75260925292969, + 35.685734110176064, + -86.43606567382814, + 35.9078947564485 + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-2/catalog.json b/tests/data-files/catalogs/test-case-2/catalog.json index 73cea685d..8d3626f02 100644 --- a/tests/data-files/catalogs/test-case-2/catalog.json +++ b/tests/data-files/catalogs/test-case-2/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "01749366-9e25-4b42-a4d1-5aae9f8c9eec", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Exported from Raster Foundry 2019-10-04 15:55:13.759", "links": [ { @@ -15,5 +16,6 @@ "title": "Layer Collection" } ], + "stac_extensions": [], "title": "Test Case 2" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json b/tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json index 1bb21dcfc..0f825fb88 100644 --- a/tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json +++ b/tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json @@ -1,293 +1,294 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "665946-labels", - "properties": { - "label:description": "Geojson building labels for scene 665946", - "area": "acc", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 7308 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-08-05T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "665946-labels", + "properties": { + "label:description": "Geojson building labels for scene 665946", + "area": "acc", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 7308 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-08-05T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -0.24297113100354567, - 5.639021431491198 - ], - [ - -0.23897453489157597, - 5.641691785417461 - ], - [ - -0.23850428635941076, - 5.642250323405412 - ], - [ - -0.2366780918775795, - 5.646581694873462 - ], - [ - -0.2365678982420534, - 5.647261088925513 - ], - [ - -0.23628962908589524, - 5.6470592605205 - ], - [ - -0.23574709855965523, - 5.646250384519349 - ], - [ - -0.23549999005573774, - 5.646062606522559 - ], - [ - -0.2352479883141164, - 5.645837517588297 - ], - [ - -0.2351489002506633, - 5.645826507803469 - ], - [ - -0.23489078196191707, - 5.645643011389668 - ], - [ - -0.23501342114346913, - 5.645366016963376 - ], - [ - -0.23498470193337662, - 5.645184128632788 - ], - [ - -0.23469272329743152, - 5.644748553946379 - ], - [ - -0.23452040803687635, - 5.644241181234739 - ], - [ - -0.23438159852142515, - 5.644073652509197 - ], - [ - -0.23389815848486362, - 5.643677566736666 - ], - [ - -0.23365883173408977, - 5.643060103719671 - ], - [ - -0.23308923406724827, - 5.642587433386895 - ], - [ - -0.2330078629719845, - 5.642391185451258 - ], - [ - -0.23287862652656693, - 5.641023433070588 - ], - [ - -0.2325962209606543, - 5.640494520951376 - ], - [ - -0.23233774806981916, - 5.639542000483295 - ], - [ - -0.231619767817498, - 5.638628968929094 - ], - [ - -0.2313230026465391, - 5.637736280148708 - ], - [ - -0.23095443945034647, - 5.637243267042115 - ], - [ - -0.23061459546424917, - 5.63661742758884 - ], - [ - -0.2303513360383966, - 5.635274804516999 - ], - [ - -0.23008329007752945, - 5.634768628439113 - ], - [ - -0.2294179617103787, - 5.63434741335775 - ], - [ - -0.22888186978864675, - 5.633672511920566 - ], - [ - -0.22813038379121683, - 5.632798969280243 - ], - [ - -0.22817824914137264, - 5.632402883507711 - ], - [ - -0.22802029348586056, - 5.630963333101811 - ], - [ - -0.2282117548864802, - 5.630476303163984 - ], - [ - -0.22777618020007248, - 5.630079020757702 - ], - [ - -0.2282117548864806, - 5.6293371078303025 - ], - [ - -0.22854202580254826, - 5.628882387003831 - ], - [ - -0.22852628100057304, - 5.628226174025203 - ], - [ - -0.22799593868162446, - 5.627172467575712 - ], - [ - -0.22778055649501683, - 5.626682654237342 - ], - [ - -0.2278709467984373, - 5.626501873630503 - ], - [ - -0.22845649911650084, - 5.62644673598136 - ], - [ - -0.22858210650783073, - 5.625651222502934 - ], - [ - -0.22899381962385806, - 5.62461496152446 - ], - [ - -0.22936723044334495, - 5.623408328310911 - ], - [ - -0.2292520125949269, - 5.621492222212219 - ], - [ - -0.22909151426155971, - 5.620375712067064 - ], - [ - -0.23060578114592858, - 5.620077394512653 - ], - [ - -0.23103842882717712, - 5.620844995237451 - ], - [ - -0.23218982991437007, - 5.622524994096494 - ], - [ - -0.23385761694369905, - 5.62503016373469 - ], - [ - -0.23535094926284753, - 5.627118386615555 - ], - [ - -0.24008913919135902, - 5.634579814569995 - ], - [ - -0.24297113100354567, - 5.639021431491198 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.24297113100354567, - 5.620077394512653, - -0.22777618020007248, - 5.647261088925513 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.24297113100354567, + 5.639021431491198 + ], + [ + -0.23897453489157597, + 5.641691785417461 + ], + [ + -0.23850428635941076, + 5.642250323405412 + ], + [ + -0.2366780918775795, + 5.646581694873462 + ], + [ + -0.2365678982420534, + 5.647261088925513 + ], + [ + -0.23628962908589524, + 5.6470592605205 + ], + [ + -0.23574709855965523, + 5.646250384519349 + ], + [ + -0.23549999005573774, + 5.646062606522559 + ], + [ + -0.2352479883141164, + 5.645837517588297 + ], + [ + -0.2351489002506633, + 5.645826507803469 + ], + [ + -0.23489078196191707, + 5.645643011389668 + ], + [ + -0.23501342114346913, + 5.645366016963376 + ], + [ + -0.23498470193337662, + 5.645184128632788 + ], + [ + -0.23469272329743152, + 5.644748553946379 + ], + [ + -0.23452040803687635, + 5.644241181234739 + ], + [ + -0.23438159852142515, + 5.644073652509197 + ], + [ + -0.23389815848486362, + 5.643677566736666 + ], + [ + -0.23365883173408977, + 5.643060103719671 + ], + [ + -0.23308923406724827, + 5.642587433386895 + ], + [ + -0.2330078629719845, + 5.642391185451258 + ], + [ + -0.23287862652656693, + 5.641023433070588 + ], + [ + -0.2325962209606543, + 5.640494520951376 + ], + [ + -0.23233774806981916, + 5.639542000483295 + ], + [ + -0.231619767817498, + 5.638628968929094 + ], + [ + -0.2313230026465391, + 5.637736280148708 + ], + [ + -0.23095443945034647, + 5.637243267042115 + ], + [ + -0.23061459546424917, + 5.63661742758884 + ], + [ + -0.2303513360383966, + 5.635274804516999 + ], + [ + -0.23008329007752945, + 5.634768628439113 + ], + [ + -0.2294179617103787, + 5.63434741335775 + ], + [ + -0.22888186978864675, + 5.633672511920566 + ], + [ + -0.22813038379121683, + 5.632798969280243 + ], + [ + -0.22817824914137264, + 5.632402883507711 + ], + [ + -0.22802029348586056, + 5.630963333101811 + ], + [ + -0.2282117548864802, + 5.630476303163984 + ], + [ + -0.22777618020007248, + 5.630079020757702 + ], + [ + -0.2282117548864806, + 5.6293371078303025 + ], + [ + -0.22854202580254826, + 5.628882387003831 + ], + [ + -0.22852628100057304, + 5.628226174025203 + ], + [ + -0.22799593868162446, + 5.627172467575712 + ], + [ + -0.22778055649501683, + 5.626682654237342 + ], + [ + -0.2278709467984373, + 5.626501873630503 + ], + [ + -0.22845649911650084, + 5.62644673598136 + ], + [ + -0.22858210650783073, + 5.625651222502934 + ], + [ + -0.22899381962385806, + 5.62461496152446 + ], + [ + -0.22936723044334495, + 5.623408328310911 + ], + [ + -0.2292520125949269, + 5.621492222212219 + ], + [ + -0.22909151426155971, + 5.620375712067064 + ], + [ + -0.23060578114592858, + 5.620077394512653 + ], + [ + -0.23103842882717712, + 5.620844995237451 + ], + [ + -0.23218982991437007, + 5.622524994096494 + ], + [ + -0.23385761694369905, + 5.62503016373469 + ], + [ + -0.23535094926284753, + 5.627118386615555 + ], + [ + -0.24008913919135902, + 5.634579814569995 + ], + [ + -0.24297113100354567, + 5.639021431491198 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/665946-labels/665946.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/665946-labels/665946.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + -0.24297113100354567, + 5.620077394512653, + -0.22777618020007248, + 5.647261088925513 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/665946/665946.json b/tests/data-files/catalogs/test-case-4/acc/665946/665946.json index 6cc411832..bd4c9dd4f 100644 --- a/tests/data-files/catalogs/test-case-4/acc/665946/665946.json +++ b/tests/data-files/catalogs/test-case-4/acc/665946/665946.json @@ -1,268 +1,269 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "665946", - "properties": { - "area": "acc", - "license": "CC-BY-4.0", - "datetime": "2018-08-05T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -0.24297113100354567, - 5.639021431491198 - ], - [ - -0.23897453489157597, - 5.641691785417461 - ], - [ - -0.23850428635941076, - 5.642250323405412 - ], - [ - -0.2366780918775795, - 5.646581694873462 - ], - [ - -0.2365678982420534, - 5.647261088925513 - ], - [ - -0.23628962908589524, - 5.6470592605205 - ], - [ - -0.23574709855965523, - 5.646250384519349 - ], - [ - -0.23549999005573774, - 5.646062606522559 - ], - [ - -0.2352479883141164, - 5.645837517588297 - ], - [ - -0.2351489002506633, - 5.645826507803469 - ], - [ - -0.23489078196191707, - 5.645643011389668 - ], - [ - -0.23501342114346913, - 5.645366016963376 - ], - [ - -0.23498470193337662, - 5.645184128632788 - ], - [ - -0.23469272329743152, - 5.644748553946379 - ], - [ - -0.23452040803687635, - 5.644241181234739 - ], - [ - -0.23438159852142515, - 5.644073652509197 - ], - [ - -0.23389815848486362, - 5.643677566736666 - ], - [ - -0.23365883173408977, - 5.643060103719671 - ], - [ - -0.23308923406724827, - 5.642587433386895 - ], - [ - -0.2330078629719845, - 5.642391185451258 - ], - [ - -0.23287862652656693, - 5.641023433070588 - ], - [ - -0.2325962209606543, - 5.640494520951376 - ], - [ - -0.23233774806981916, - 5.639542000483295 - ], - [ - -0.231619767817498, - 5.638628968929094 - ], - [ - -0.2313230026465391, - 5.637736280148708 - ], - [ - -0.23095443945034647, - 5.637243267042115 - ], - [ - -0.23061459546424917, - 5.63661742758884 - ], - [ - -0.2303513360383966, - 5.635274804516999 - ], - [ - -0.23008329007752945, - 5.634768628439113 - ], - [ - -0.2294179617103787, - 5.63434741335775 - ], - [ - -0.22888186978864675, - 5.633672511920566 - ], - [ - -0.22813038379121683, - 5.632798969280243 - ], - [ - -0.22817824914137264, - 5.632402883507711 - ], - [ - -0.22802029348586056, - 5.630963333101811 - ], - [ - -0.2282117548864802, - 5.630476303163984 - ], - [ - -0.22777618020007248, - 5.630079020757702 - ], - [ - -0.2282117548864806, - 5.6293371078303025 - ], - [ - -0.22854202580254826, - 5.628882387003831 - ], - [ - -0.22852628100057304, - 5.628226174025203 - ], - [ - -0.22799593868162446, - 5.627172467575712 - ], - [ - -0.22778055649501683, - 5.626682654237342 - ], - [ - -0.2278709467984373, - 5.626501873630503 - ], - [ - -0.22845649911650084, - 5.62644673598136 - ], - [ - -0.22858210650783073, - 5.625651222502934 - ], - [ - -0.22899381962385806, - 5.62461496152446 - ], - [ - -0.22936723044334495, - 5.623408328310911 - ], - [ - -0.2292520125949269, - 5.621492222212219 - ], - [ - -0.22909151426155971, - 5.620375712067064 - ], - [ - -0.23060578114592858, - 5.620077394512653 - ], - [ - -0.23103842882717712, - 5.620844995237451 - ], - [ - -0.23218982991437007, - 5.622524994096494 - ], - [ - -0.23385761694369905, - 5.62503016373469 - ], - [ - -0.23535094926284753, - 5.627118386615555 - ], - [ - -0.24008913919135902, - 5.634579814569995 - ], - [ - -0.24297113100354567, - 5.639021431491198 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.24297113100354567, - 5.620077394512653, - -0.22777618020007248, - 5.647261088925513 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "665946", + "properties": { + "area": "acc", + "license": "CC-BY-4.0", + "datetime": "2018-08-05T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.24297113100354567, + 5.639021431491198 + ], + [ + -0.23897453489157597, + 5.641691785417461 + ], + [ + -0.23850428635941076, + 5.642250323405412 + ], + [ + -0.2366780918775795, + 5.646581694873462 + ], + [ + -0.2365678982420534, + 5.647261088925513 + ], + [ + -0.23628962908589524, + 5.6470592605205 + ], + [ + -0.23574709855965523, + 5.646250384519349 + ], + [ + -0.23549999005573774, + 5.646062606522559 + ], + [ + -0.2352479883141164, + 5.645837517588297 + ], + [ + -0.2351489002506633, + 5.645826507803469 + ], + [ + -0.23489078196191707, + 5.645643011389668 + ], + [ + -0.23501342114346913, + 5.645366016963376 + ], + [ + -0.23498470193337662, + 5.645184128632788 + ], + [ + -0.23469272329743152, + 5.644748553946379 + ], + [ + -0.23452040803687635, + 5.644241181234739 + ], + [ + -0.23438159852142515, + 5.644073652509197 + ], + [ + -0.23389815848486362, + 5.643677566736666 + ], + [ + -0.23365883173408977, + 5.643060103719671 + ], + [ + -0.23308923406724827, + 5.642587433386895 + ], + [ + -0.2330078629719845, + 5.642391185451258 + ], + [ + -0.23287862652656693, + 5.641023433070588 + ], + [ + -0.2325962209606543, + 5.640494520951376 + ], + [ + -0.23233774806981916, + 5.639542000483295 + ], + [ + -0.231619767817498, + 5.638628968929094 + ], + [ + -0.2313230026465391, + 5.637736280148708 + ], + [ + -0.23095443945034647, + 5.637243267042115 + ], + [ + -0.23061459546424917, + 5.63661742758884 + ], + [ + -0.2303513360383966, + 5.635274804516999 + ], + [ + -0.23008329007752945, + 5.634768628439113 + ], + [ + -0.2294179617103787, + 5.63434741335775 + ], + [ + -0.22888186978864675, + 5.633672511920566 + ], + [ + -0.22813038379121683, + 5.632798969280243 + ], + [ + -0.22817824914137264, + 5.632402883507711 + ], + [ + -0.22802029348586056, + 5.630963333101811 + ], + [ + -0.2282117548864802, + 5.630476303163984 + ], + [ + -0.22777618020007248, + 5.630079020757702 + ], + [ + -0.2282117548864806, + 5.6293371078303025 + ], + [ + -0.22854202580254826, + 5.628882387003831 + ], + [ + -0.22852628100057304, + 5.628226174025203 + ], + [ + -0.22799593868162446, + 5.627172467575712 + ], + [ + -0.22778055649501683, + 5.626682654237342 + ], + [ + -0.2278709467984373, + 5.626501873630503 + ], + [ + -0.22845649911650084, + 5.62644673598136 + ], + [ + -0.22858210650783073, + 5.625651222502934 + ], + [ + -0.22899381962385806, + 5.62461496152446 + ], + [ + -0.22936723044334495, + 5.623408328310911 + ], + [ + -0.2292520125949269, + 5.621492222212219 + ], + [ + -0.22909151426155971, + 5.620375712067064 + ], + [ + -0.23060578114592858, + 5.620077394512653 + ], + [ + -0.23103842882717712, + 5.620844995237451 + ], + [ + -0.23218982991437007, + 5.622524994096494 + ], + [ + -0.23385761694369905, + 5.62503016373469 + ], + [ + -0.23535094926284753, + 5.627118386615555 + ], + [ + -0.24008913919135902, + 5.634579814569995 + ], + [ + -0.24297113100354567, + 5.639021431491198 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/665946/665946.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "acc" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/665946/665946.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + -0.24297113100354567, + 5.620077394512653, + -0.22777618020007248, + 5.647261088925513 + ], + "stac_extensions": [], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json b/tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json index 955df0cd7..52a1042e2 100644 --- a/tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json +++ b/tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json @@ -1,209 +1,210 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "a42435-labels", - "properties": { - "label:description": "Geojson building labels for scene a42435", - "area": "acc", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 6647 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-10-06T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -0.2498678517428029, - 5.617970541565606 - ], - [ - -0.2483206321371833, - 5.618380099696508 - ], - [ - -0.24612982118300658, - 5.61867914214129 - ], - [ - -0.2449076477130216, - 5.618425606155497 - ], - [ - -0.24292486628565255, - 5.618295587701242 - ], - [ - -0.24057803318637266, - 5.61884816613182 - ], - [ - -0.23918683572585742, - 5.618893672590809 - ], - [ - -0.23845873238203655, - 5.619088700272191 - ], - [ - -0.2367164850950368, - 5.619166711344742 - ], - [ - -0.2367164850950352, - 5.6187116467548535 - ], - [ - -0.23648895280009183, - 5.617144924381095 - ], - [ - -0.23618991035530826, - 5.61604951890401 - ], - [ - -0.23606639282376832, - 5.615178395260507 - ], - [ - -0.2358128568379735, - 5.6150613786516805 - ], - [ - -0.23484421935378336, - 5.614905356506578 - ], - [ - -0.23409011231911103, - 5.614809467896564 - ], - [ - -0.23370005695634877, - 5.614633942983321 - ], - [ - -0.23344002004784176, - 5.614284518387513 - ], - [ - -0.23326449513459932, - 5.613874960256616 - ], - [ - -0.2332904988254504, - 5.612933951693955 - ], - [ - -0.23357958657882866, - 5.610610569931325 - ], - [ - -0.23818182071090357, - 5.609166251772857 - ], - [ - -0.23942120427966462, - 5.608949984373012 - ], - [ - -0.24480177379675747, - 5.608165317985103 - ], - [ - -0.24616292977578505, - 5.607877073189545 - ], - [ - -0.2453887159937594, - 5.610507482291447 - ], - [ - -0.24500926881889307, - 5.612188394844965 - ], - [ - -0.244985658785574, - 5.612914448925815 - ], - [ - -0.24537571414833664, - 5.614219509160388 - ], - [ - -0.2465653830047576, - 5.614698952210449 - ], - [ - -0.24663689315459741, - 5.615028874038114 - ], - [ - -0.24762503340692765, - 5.615730973691088 - ], - [ - -0.24860017181382998, - 5.616101526285711 - ], - [ - -0.2498678517428029, - 5.617970541565606 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "a42435-labels", + "properties": { + "label:description": "Geojson building labels for scene a42435", + "area": "acc", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 6647 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-10-06T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.2498678517428029, - 5.607877073189545, - -0.23326449513459932, - 5.619166711344742 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.2498678517428029, + 5.617970541565606 + ], + [ + -0.2483206321371833, + 5.618380099696508 + ], + [ + -0.24612982118300658, + 5.61867914214129 + ], + [ + -0.2449076477130216, + 5.618425606155497 + ], + [ + -0.24292486628565255, + 5.618295587701242 + ], + [ + -0.24057803318637266, + 5.61884816613182 + ], + [ + -0.23918683572585742, + 5.618893672590809 + ], + [ + -0.23845873238203655, + 5.619088700272191 + ], + [ + -0.2367164850950368, + 5.619166711344742 + ], + [ + -0.2367164850950352, + 5.6187116467548535 + ], + [ + -0.23648895280009183, + 5.617144924381095 + ], + [ + -0.23618991035530826, + 5.61604951890401 + ], + [ + -0.23606639282376832, + 5.615178395260507 + ], + [ + -0.2358128568379735, + 5.6150613786516805 + ], + [ + -0.23484421935378336, + 5.614905356506578 + ], + [ + -0.23409011231911103, + 5.614809467896564 + ], + [ + -0.23370005695634877, + 5.614633942983321 + ], + [ + -0.23344002004784176, + 5.614284518387513 + ], + [ + -0.23326449513459932, + 5.613874960256616 + ], + [ + -0.2332904988254504, + 5.612933951693955 + ], + [ + -0.23357958657882866, + 5.610610569931325 + ], + [ + -0.23818182071090357, + 5.609166251772857 + ], + [ + -0.23942120427966462, + 5.608949984373012 + ], + [ + -0.24480177379675747, + 5.608165317985103 + ], + [ + -0.24616292977578505, + 5.607877073189545 + ], + [ + -0.2453887159937594, + 5.610507482291447 + ], + [ + -0.24500926881889307, + 5.612188394844965 + ], + [ + -0.244985658785574, + 5.612914448925815 + ], + [ + -0.24537571414833664, + 5.614219509160388 + ], + [ + -0.2465653830047576, + 5.614698952210449 + ], + [ + -0.24663689315459741, + 5.615028874038114 + ], + [ + -0.24762503340692765, + 5.615730973691088 + ], + [ + -0.24860017181382998, + 5.616101526285711 + ], + [ + -0.2498678517428029, + 5.617970541565606 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/a42435-labels/a42435.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/a42435-labels/a42435.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -0.2498678517428029, + 5.607877073189545, + -0.23326449513459932, + 5.619166711344742 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json b/tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json index 684a405c7..df352a839 100644 --- a/tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json +++ b/tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json @@ -1,184 +1,185 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "a42435", - "properties": { - "area": "acc", - "license": "CC-BY-4.0", - "datetime": "2018-10-06T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -0.2498678517428029, - 5.617970541565606 - ], - [ - -0.2483206321371833, - 5.618380099696508 - ], - [ - -0.24612982118300658, - 5.61867914214129 - ], - [ - -0.2449076477130216, - 5.618425606155497 - ], - [ - -0.24292486628565255, - 5.618295587701242 - ], - [ - -0.24057803318637266, - 5.61884816613182 - ], - [ - -0.23918683572585742, - 5.618893672590809 - ], - [ - -0.23845873238203655, - 5.619088700272191 - ], - [ - -0.2367164850950368, - 5.619166711344742 - ], - [ - -0.2367164850950352, - 5.6187116467548535 - ], - [ - -0.23648895280009183, - 5.617144924381095 - ], - [ - -0.23618991035530826, - 5.61604951890401 - ], - [ - -0.23606639282376832, - 5.615178395260507 - ], - [ - -0.2358128568379735, - 5.6150613786516805 - ], - [ - -0.23484421935378336, - 5.614905356506578 - ], - [ - -0.23409011231911103, - 5.614809467896564 - ], - [ - -0.23370005695634877, - 5.614633942983321 - ], - [ - -0.23344002004784176, - 5.614284518387513 - ], - [ - -0.23326449513459932, - 5.613874960256616 - ], - [ - -0.2332904988254504, - 5.612933951693955 - ], - [ - -0.23357958657882866, - 5.610610569931325 - ], - [ - -0.23818182071090357, - 5.609166251772857 - ], - [ - -0.23942120427966462, - 5.608949984373012 - ], - [ - -0.24480177379675747, - 5.608165317985103 - ], - [ - -0.24616292977578505, - 5.607877073189545 - ], - [ - -0.2453887159937594, - 5.610507482291447 - ], - [ - -0.24500926881889307, - 5.612188394844965 - ], - [ - -0.244985658785574, - 5.612914448925815 - ], - [ - -0.24537571414833664, - 5.614219509160388 - ], - [ - -0.2465653830047576, - 5.614698952210449 - ], - [ - -0.24663689315459741, - 5.615028874038114 - ], - [ - -0.24762503340692765, - 5.615730973691088 - ], - [ - -0.24860017181382998, - 5.616101526285711 - ], - [ - -0.2498678517428029, - 5.617970541565606 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.2498678517428029, - 5.607877073189545, - -0.23326449513459932, - 5.619166711344742 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "a42435", + "properties": { + "area": "acc", + "license": "CC-BY-4.0", + "datetime": "2018-10-06T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + -0.2498678517428029, + 5.617970541565606 + ], + [ + -0.2483206321371833, + 5.618380099696508 + ], + [ + -0.24612982118300658, + 5.61867914214129 + ], + [ + -0.2449076477130216, + 5.618425606155497 + ], + [ + -0.24292486628565255, + 5.618295587701242 + ], + [ + -0.24057803318637266, + 5.61884816613182 + ], + [ + -0.23918683572585742, + 5.618893672590809 + ], + [ + -0.23845873238203655, + 5.619088700272191 + ], + [ + -0.2367164850950368, + 5.619166711344742 + ], + [ + -0.2367164850950352, + 5.6187116467548535 + ], + [ + -0.23648895280009183, + 5.617144924381095 + ], + [ + -0.23618991035530826, + 5.61604951890401 + ], + [ + -0.23606639282376832, + 5.615178395260507 + ], + [ + -0.2358128568379735, + 5.6150613786516805 + ], + [ + -0.23484421935378336, + 5.614905356506578 + ], + [ + -0.23409011231911103, + 5.614809467896564 + ], + [ + -0.23370005695634877, + 5.614633942983321 + ], + [ + -0.23344002004784176, + 5.614284518387513 + ], + [ + -0.23326449513459932, + 5.613874960256616 + ], + [ + -0.2332904988254504, + 5.612933951693955 + ], + [ + -0.23357958657882866, + 5.610610569931325 + ], + [ + -0.23818182071090357, + 5.609166251772857 + ], + [ + -0.23942120427966462, + 5.608949984373012 + ], + [ + -0.24480177379675747, + 5.608165317985103 + ], + [ + -0.24616292977578505, + 5.607877073189545 + ], + [ + -0.2453887159937594, + 5.610507482291447 + ], + [ + -0.24500926881889307, + 5.612188394844965 + ], + [ + -0.244985658785574, + 5.612914448925815 + ], + [ + -0.24537571414833664, + 5.614219509160388 + ], + [ + -0.2465653830047576, + 5.614698952210449 + ], + [ + -0.24663689315459741, + 5.615028874038114 + ], + [ + -0.24762503340692765, + 5.615730973691088 + ], + [ + -0.24860017181382998, + 5.616101526285711 + ], + [ + -0.2498678517428029, + 5.617970541565606 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/a42435/a42435.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/a42435/a42435.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "acc" + "bbox": [ + -0.2498678517428029, + 5.607877073189545, + -0.23326449513459932, + 5.619166711344742 + ], + "stac_extensions": [], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json b/tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json index ff8bd2dcb..41b4279a4 100644 --- a/tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json +++ b/tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json @@ -1,205 +1,206 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "ca041a-labels", - "properties": { - "label:description": "Geojson building labels for scene ca041a", - "area": "acc", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 10194 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-11-12T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "ca041a-labels", + "properties": { + "label:description": "Geojson building labels for scene ca041a", + "area": "acc", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 10194 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-11-12T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -0.2260939759101167, - 5.607821019807083 - ], - [ - -0.22707525357332697, - 5.609567361411101 - ], - [ - -0.2257626190986551, - 5.610742610987594 - ], - [ - -0.2209214783972656, - 5.60396659440964 - ], - [ - -0.2209297943096631, - 5.603475955578037 - ], - [ - -0.21938590601191368, - 5.601711342600872 - ], - [ - -0.21863322644066166, - 5.601370284670147 - ], - [ - -0.2171984310079642, - 5.60126443910544 - ], - [ - -0.2150344772406196, - 5.602172946869177 - ], - [ - -0.21221192884842804, - 5.603687126475405 - ], - [ - -0.2071666235973881, - 5.60628622311988 - ], - [ - -0.20581415249279408, - 5.604666197948947 - ], - [ - -0.2074253572000044, - 5.603584221065274 - ], - [ - -0.2083544460457665, - 5.6019965375946645 - ], - [ - -0.20906008314381597, - 5.600996885039098 - ], - [ - -0.21070656970592522, - 5.599526807751497 - ], - [ - -0.2114122068039735, - 5.597897962116837 - ], - [ - -0.212416616071534, - 5.595984932725826 - ], - [ - -0.2138692108714978, - 5.593295072530488 - ], - [ - -0.21395275670010877, - 5.593002662130355 - ], - [ - -0.21397782044869207, - 5.590391854986293 - ], - [ - -0.2141449121059085, - 5.590032607923272 - ], - [ - -0.21550670911225214, - 5.588150738133833 - ], - [ - -0.21721104401589492, - 5.585527399115482 - ], - [ - -0.21795907856051402, - 5.590627248068187 - ], - [ - -0.21855973661963968, - 5.59108215821591 - ], - [ - -0.2203440443835102, - 5.593219794249854 - ], - [ - -0.22225201704190803, - 5.59588742268891 - ], - [ - -0.2234356667466541, - 5.598325387752418 - ], - [ - -0.22523764092403067, - 5.601536258406711 - ], - [ - -0.22571463408862905, - 5.6031880680693025 - ], - [ - -0.2259010426101557, - 5.606526622234949 - ], - [ - -0.2260939759101167, - 5.607821019807083 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.22707525357332697, - 5.585527399115482, - -0.20581415249279408, - 5.610742610987594 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.2260939759101167, + 5.607821019807083 + ], + [ + -0.22707525357332697, + 5.609567361411101 + ], + [ + -0.2257626190986551, + 5.610742610987594 + ], + [ + -0.2209214783972656, + 5.60396659440964 + ], + [ + -0.2209297943096631, + 5.603475955578037 + ], + [ + -0.21938590601191368, + 5.601711342600872 + ], + [ + -0.21863322644066166, + 5.601370284670147 + ], + [ + -0.2171984310079642, + 5.60126443910544 + ], + [ + -0.2150344772406196, + 5.602172946869177 + ], + [ + -0.21221192884842804, + 5.603687126475405 + ], + [ + -0.2071666235973881, + 5.60628622311988 + ], + [ + -0.20581415249279408, + 5.604666197948947 + ], + [ + -0.2074253572000044, + 5.603584221065274 + ], + [ + -0.2083544460457665, + 5.6019965375946645 + ], + [ + -0.20906008314381597, + 5.600996885039098 + ], + [ + -0.21070656970592522, + 5.599526807751497 + ], + [ + -0.2114122068039735, + 5.597897962116837 + ], + [ + -0.212416616071534, + 5.595984932725826 + ], + [ + -0.2138692108714978, + 5.593295072530488 + ], + [ + -0.21395275670010877, + 5.593002662130355 + ], + [ + -0.21397782044869207, + 5.590391854986293 + ], + [ + -0.2141449121059085, + 5.590032607923272 + ], + [ + -0.21550670911225214, + 5.588150738133833 + ], + [ + -0.21721104401589492, + 5.585527399115482 + ], + [ + -0.21795907856051402, + 5.590627248068187 + ], + [ + -0.21855973661963968, + 5.59108215821591 + ], + [ + -0.2203440443835102, + 5.593219794249854 + ], + [ + -0.22225201704190803, + 5.59588742268891 + ], + [ + -0.2234356667466541, + 5.598325387752418 + ], + [ + -0.22523764092403067, + 5.601536258406711 + ], + [ + -0.22571463408862905, + 5.6031880680693025 + ], + [ + -0.2259010426101557, + 5.606526622234949 + ], + [ + -0.2260939759101167, + 5.607821019807083 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/ca041a-labels/ca041a.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/ca041a-labels/ca041a.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -0.22707525357332697, + 5.585527399115482, + -0.20581415249279408, + 5.610742610987594 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json b/tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json index 1f0870f42..d2abc4c05 100644 --- a/tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json +++ b/tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json @@ -1,180 +1,181 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "ca041a", - "properties": { - "area": "acc", - "license": "CC-BY-4.0", - "datetime": "2018-11-12T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -0.2260939759101167, - 5.607821019807083 - ], - [ - -0.22707525357332697, - 5.609567361411101 - ], - [ - -0.2257626190986551, - 5.610742610987594 - ], - [ - -0.2209214783972656, - 5.60396659440964 - ], - [ - -0.2209297943096631, - 5.603475955578037 - ], - [ - -0.21938590601191368, - 5.601711342600872 - ], - [ - -0.21863322644066166, - 5.601370284670147 - ], - [ - -0.2171984310079642, - 5.60126443910544 - ], - [ - -0.2150344772406196, - 5.602172946869177 - ], - [ - -0.21221192884842804, - 5.603687126475405 - ], - [ - -0.2071666235973881, - 5.60628622311988 - ], - [ - -0.20581415249279408, - 5.604666197948947 - ], - [ - -0.2074253572000044, - 5.603584221065274 - ], - [ - -0.2083544460457665, - 5.6019965375946645 - ], - [ - -0.20906008314381597, - 5.600996885039098 - ], - [ - -0.21070656970592522, - 5.599526807751497 - ], - [ - -0.2114122068039735, - 5.597897962116837 - ], - [ - -0.212416616071534, - 5.595984932725826 - ], - [ - -0.2138692108714978, - 5.593295072530488 - ], - [ - -0.21395275670010877, - 5.593002662130355 - ], - [ - -0.21397782044869207, - 5.590391854986293 - ], - [ - -0.2141449121059085, - 5.590032607923272 - ], - [ - -0.21550670911225214, - 5.588150738133833 - ], - [ - -0.21721104401589492, - 5.585527399115482 - ], - [ - -0.21795907856051402, - 5.590627248068187 - ], - [ - -0.21855973661963968, - 5.59108215821591 - ], - [ - -0.2203440443835102, - 5.593219794249854 - ], - [ - -0.22225201704190803, - 5.59588742268891 - ], - [ - -0.2234356667466541, - 5.598325387752418 - ], - [ - -0.22523764092403067, - 5.601536258406711 - ], - [ - -0.22571463408862905, - 5.6031880680693025 - ], - [ - -0.2259010426101557, - 5.606526622234949 - ], - [ - -0.2260939759101167, - 5.607821019807083 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.22707525357332697, - 5.585527399115482, - -0.20581415249279408, - 5.610742610987594 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "ca041a", + "properties": { + "area": "acc", + "license": "CC-BY-4.0", + "datetime": "2018-11-12T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + -0.2260939759101167, + 5.607821019807083 + ], + [ + -0.22707525357332697, + 5.609567361411101 + ], + [ + -0.2257626190986551, + 5.610742610987594 + ], + [ + -0.2209214783972656, + 5.60396659440964 + ], + [ + -0.2209297943096631, + 5.603475955578037 + ], + [ + -0.21938590601191368, + 5.601711342600872 + ], + [ + -0.21863322644066166, + 5.601370284670147 + ], + [ + -0.2171984310079642, + 5.60126443910544 + ], + [ + -0.2150344772406196, + 5.602172946869177 + ], + [ + -0.21221192884842804, + 5.603687126475405 + ], + [ + -0.2071666235973881, + 5.60628622311988 + ], + [ + -0.20581415249279408, + 5.604666197948947 + ], + [ + -0.2074253572000044, + 5.603584221065274 + ], + [ + -0.2083544460457665, + 5.6019965375946645 + ], + [ + -0.20906008314381597, + 5.600996885039098 + ], + [ + -0.21070656970592522, + 5.599526807751497 + ], + [ + -0.2114122068039735, + 5.597897962116837 + ], + [ + -0.212416616071534, + 5.595984932725826 + ], + [ + -0.2138692108714978, + 5.593295072530488 + ], + [ + -0.21395275670010877, + 5.593002662130355 + ], + [ + -0.21397782044869207, + 5.590391854986293 + ], + [ + -0.2141449121059085, + 5.590032607923272 + ], + [ + -0.21550670911225214, + 5.588150738133833 + ], + [ + -0.21721104401589492, + 5.585527399115482 + ], + [ + -0.21795907856051402, + 5.590627248068187 + ], + [ + -0.21855973661963968, + 5.59108215821591 + ], + [ + -0.2203440443835102, + 5.593219794249854 + ], + [ + -0.22225201704190803, + 5.59588742268891 + ], + [ + -0.2234356667466541, + 5.598325387752418 + ], + [ + -0.22523764092403067, + 5.601536258406711 + ], + [ + -0.22571463408862905, + 5.6031880680693025 + ], + [ + -0.2259010426101557, + 5.606526622234949 + ], + [ + -0.2260939759101167, + 5.607821019807083 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/ca041a/ca041a.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/ca041a/ca041a.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "acc" + "bbox": [ + -0.22707525357332697, + 5.585527399115482, + -0.20581415249279408, + 5.610742610987594 + ], + "stac_extensions": [], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/collection.json b/tests/data-files/catalogs/test-case-4/acc/collection.json index f9ab1e736..8c720cd37 100644 --- a/tests/data-files/catalogs/test-case-4/acc/collection.json +++ b/tests/data-files/catalogs/test-case-4/acc/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "acc", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from acc", "links": [ { @@ -54,6 +55,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -74,8 +76,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json b/tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json index 842a98353..799c1d6b0 100644 --- a/tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json +++ b/tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json @@ -1,237 +1,238 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "d41d81-labels", - "properties": { - "label:description": "Geojson building labels for scene d41d81", - "area": "acc", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 9436 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2019-07-07T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "d41d81-labels", + "properties": { + "label:description": "Geojson building labels for scene d41d81", + "area": "acc", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 9436 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2019-07-07T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -0.20863145179911316, - 5.582119462388951 - ], - [ - -0.20385702387126087, - 5.579536961528113 - ], - [ - -0.2009950607269171, - 5.577949413736869 - ], - [ - -0.20077283770629886, - 5.577641331821918 - ], - [ - -0.20073243352073175, - 5.573262528211078 - ], - [ - -0.19864488393309448, - 5.573787782623451 - ], - [ - -0.19734521596401766, - 5.57635681542243 - ], - [ - -0.1960320799330845, - 5.578885444035844 - ], - [ - -0.19481995436606828, - 5.58174404016472 - ], - [ - -0.193715573293899, - 5.5842996049018465 - ], - [ - -0.1928132131495656, - 5.586195234608039 - ], - [ - -0.19234183098461477, - 5.587727226644127 - ], - [ - -0.19077280177842343, - 5.590675048682799 - ], - [ - -0.18995125000522423, - 5.5918703391724955 - ], - [ - -0.18948660187120017, - 5.593203677296213 - ], - [ - -0.1896814210311117, - 5.593146040802338 - ], - [ - -0.19064648860775885, - 5.592399387837963 - ], - [ - -0.19145578395016197, - 5.591683735622175 - ], - [ - -0.191767472021221, - 5.591533359798418 - ], - [ - -0.19283650742283956, - 5.590449970340895 - ], - [ - -0.19318542830603494, - 5.590043519585699 - ], - [ - -0.193702305535994, - 5.589716881891764 - ], - [ - -0.19418464068091668, - 5.589560941307724 - ], - [ - -0.19516869974401801, - 5.589357695039237 - ], - [ - -0.1953956305325968, - 5.589357695039239 - ], - [ - -0.1961839592598401, - 5.5896504069411765 - ], - [ - -0.1965797482249876, - 5.588349850542593 - ], - [ - -0.19764163737530274, - 5.5883155603721155 - ], - [ - -0.19867982389932817, - 5.587968505169294 - ], - [ - -0.19978796845742514, - 5.5877988416473 - ], - [ - -0.20026909700296197, - 5.588264378064047 - ], - [ - -0.2011782799777639, - 5.588967803092853 - ], - [ - -0.20205926441255548, - 5.5896240281790135 - ], - [ - -0.20252257279431515, - 5.589941270993319 - ], - [ - -0.20519554383017538, - 5.592316146871745 - ], - [ - -0.20705628845757923, - 5.590243111157246 - ], - [ - -0.20726746402099225, - 5.589590166024398 - ], - [ - -0.2075150491643001, - 5.587866779242541 - ], - [ - -0.20796945601763953, - 5.5852380825945485 - ], - [ - -0.2084801191101917, - 5.582507241723167 - ], - [ - -0.20863145179911316, - 5.582119462388951 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.20863145179911316, - 5.573262528211078, - -0.18948660187120017, - 5.593203677296213 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.20863145179911316, + 5.582119462388951 + ], + [ + -0.20385702387126087, + 5.579536961528113 + ], + [ + -0.2009950607269171, + 5.577949413736869 + ], + [ + -0.20077283770629886, + 5.577641331821918 + ], + [ + -0.20073243352073175, + 5.573262528211078 + ], + [ + -0.19864488393309448, + 5.573787782623451 + ], + [ + -0.19734521596401766, + 5.57635681542243 + ], + [ + -0.1960320799330845, + 5.578885444035844 + ], + [ + -0.19481995436606828, + 5.58174404016472 + ], + [ + -0.193715573293899, + 5.5842996049018465 + ], + [ + -0.1928132131495656, + 5.586195234608039 + ], + [ + -0.19234183098461477, + 5.587727226644127 + ], + [ + -0.19077280177842343, + 5.590675048682799 + ], + [ + -0.18995125000522423, + 5.5918703391724955 + ], + [ + -0.18948660187120017, + 5.593203677296213 + ], + [ + -0.1896814210311117, + 5.593146040802338 + ], + [ + -0.19064648860775885, + 5.592399387837963 + ], + [ + -0.19145578395016197, + 5.591683735622175 + ], + [ + -0.191767472021221, + 5.591533359798418 + ], + [ + -0.19283650742283956, + 5.590449970340895 + ], + [ + -0.19318542830603494, + 5.590043519585699 + ], + [ + -0.193702305535994, + 5.589716881891764 + ], + [ + -0.19418464068091668, + 5.589560941307724 + ], + [ + -0.19516869974401801, + 5.589357695039237 + ], + [ + -0.1953956305325968, + 5.589357695039239 + ], + [ + -0.1961839592598401, + 5.5896504069411765 + ], + [ + -0.1965797482249876, + 5.588349850542593 + ], + [ + -0.19764163737530274, + 5.5883155603721155 + ], + [ + -0.19867982389932817, + 5.587968505169294 + ], + [ + -0.19978796845742514, + 5.5877988416473 + ], + [ + -0.20026909700296197, + 5.588264378064047 + ], + [ + -0.2011782799777639, + 5.588967803092853 + ], + [ + -0.20205926441255548, + 5.5896240281790135 + ], + [ + -0.20252257279431515, + 5.589941270993319 + ], + [ + -0.20519554383017538, + 5.592316146871745 + ], + [ + -0.20705628845757923, + 5.590243111157246 + ], + [ + -0.20726746402099225, + 5.589590166024398 + ], + [ + -0.2075150491643001, + 5.587866779242541 + ], + [ + -0.20796945601763953, + 5.5852380825945485 + ], + [ + -0.2084801191101917, + 5.582507241723167 + ], + [ + -0.20863145179911316, + 5.582119462388951 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/d41d81-labels/d41d81.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/d41d81-labels/d41d81.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -0.20863145179911316, + 5.573262528211078, + -0.18948660187120017, + 5.593203677296213 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json b/tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json index dc1391c92..7649f0f2e 100644 --- a/tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json +++ b/tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json @@ -1,212 +1,213 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "d41d81", - "properties": { - "area": "acc", - "license": "CC-BY-4.0", - "datetime": "2019-07-07T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -0.20863145179911316, - 5.582119462388951 - ], - [ - -0.20385702387126087, - 5.579536961528113 - ], - [ - -0.2009950607269171, - 5.577949413736869 - ], - [ - -0.20077283770629886, - 5.577641331821918 - ], - [ - -0.20073243352073175, - 5.573262528211078 - ], - [ - -0.19864488393309448, - 5.573787782623451 - ], - [ - -0.19734521596401766, - 5.57635681542243 - ], - [ - -0.1960320799330845, - 5.578885444035844 - ], - [ - -0.19481995436606828, - 5.58174404016472 - ], - [ - -0.193715573293899, - 5.5842996049018465 - ], - [ - -0.1928132131495656, - 5.586195234608039 - ], - [ - -0.19234183098461477, - 5.587727226644127 - ], - [ - -0.19077280177842343, - 5.590675048682799 - ], - [ - -0.18995125000522423, - 5.5918703391724955 - ], - [ - -0.18948660187120017, - 5.593203677296213 - ], - [ - -0.1896814210311117, - 5.593146040802338 - ], - [ - -0.19064648860775885, - 5.592399387837963 - ], - [ - -0.19145578395016197, - 5.591683735622175 - ], - [ - -0.191767472021221, - 5.591533359798418 - ], - [ - -0.19283650742283956, - 5.590449970340895 - ], - [ - -0.19318542830603494, - 5.590043519585699 - ], - [ - -0.193702305535994, - 5.589716881891764 - ], - [ - -0.19418464068091668, - 5.589560941307724 - ], - [ - -0.19516869974401801, - 5.589357695039237 - ], - [ - -0.1953956305325968, - 5.589357695039239 - ], - [ - -0.1961839592598401, - 5.5896504069411765 - ], - [ - -0.1965797482249876, - 5.588349850542593 - ], - [ - -0.19764163737530274, - 5.5883155603721155 - ], - [ - -0.19867982389932817, - 5.587968505169294 - ], - [ - -0.19978796845742514, - 5.5877988416473 - ], - [ - -0.20026909700296197, - 5.588264378064047 - ], - [ - -0.2011782799777639, - 5.588967803092853 - ], - [ - -0.20205926441255548, - 5.5896240281790135 - ], - [ - -0.20252257279431515, - 5.589941270993319 - ], - [ - -0.20519554383017538, - 5.592316146871745 - ], - [ - -0.20705628845757923, - 5.590243111157246 - ], - [ - -0.20726746402099225, - 5.589590166024398 - ], - [ - -0.2075150491643001, - 5.587866779242541 - ], - [ - -0.20796945601763953, - 5.5852380825945485 - ], - [ - -0.2084801191101917, - 5.582507241723167 - ], - [ - -0.20863145179911316, - 5.582119462388951 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -0.20863145179911316, - 5.573262528211078, - -0.18948660187120017, - 5.593203677296213 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "d41d81", + "properties": { + "area": "acc", + "license": "CC-BY-4.0", + "datetime": "2019-07-07T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -0.20863145179911316, + 5.582119462388951 + ], + [ + -0.20385702387126087, + 5.579536961528113 + ], + [ + -0.2009950607269171, + 5.577949413736869 + ], + [ + -0.20077283770629886, + 5.577641331821918 + ], + [ + -0.20073243352073175, + 5.573262528211078 + ], + [ + -0.19864488393309448, + 5.573787782623451 + ], + [ + -0.19734521596401766, + 5.57635681542243 + ], + [ + -0.1960320799330845, + 5.578885444035844 + ], + [ + -0.19481995436606828, + 5.58174404016472 + ], + [ + -0.193715573293899, + 5.5842996049018465 + ], + [ + -0.1928132131495656, + 5.586195234608039 + ], + [ + -0.19234183098461477, + 5.587727226644127 + ], + [ + -0.19077280177842343, + 5.590675048682799 + ], + [ + -0.18995125000522423, + 5.5918703391724955 + ], + [ + -0.18948660187120017, + 5.593203677296213 + ], + [ + -0.1896814210311117, + 5.593146040802338 + ], + [ + -0.19064648860775885, + 5.592399387837963 + ], + [ + -0.19145578395016197, + 5.591683735622175 + ], + [ + -0.191767472021221, + 5.591533359798418 + ], + [ + -0.19283650742283956, + 5.590449970340895 + ], + [ + -0.19318542830603494, + 5.590043519585699 + ], + [ + -0.193702305535994, + 5.589716881891764 + ], + [ + -0.19418464068091668, + 5.589560941307724 + ], + [ + -0.19516869974401801, + 5.589357695039237 + ], + [ + -0.1953956305325968, + 5.589357695039239 + ], + [ + -0.1961839592598401, + 5.5896504069411765 + ], + [ + -0.1965797482249876, + 5.588349850542593 + ], + [ + -0.19764163737530274, + 5.5883155603721155 + ], + [ + -0.19867982389932817, + 5.587968505169294 + ], + [ + -0.19978796845742514, + 5.5877988416473 + ], + [ + -0.20026909700296197, + 5.588264378064047 + ], + [ + -0.2011782799777639, + 5.588967803092853 + ], + [ + -0.20205926441255548, + 5.5896240281790135 + ], + [ + -0.20252257279431515, + 5.589941270993319 + ], + [ + -0.20519554383017538, + 5.592316146871745 + ], + [ + -0.20705628845757923, + 5.590243111157246 + ], + [ + -0.20726746402099225, + 5.589590166024398 + ], + [ + -0.2075150491643001, + 5.587866779242541 + ], + [ + -0.20796945601763953, + 5.5852380825945485 + ], + [ + -0.2084801191101917, + 5.582507241723167 + ], + [ + -0.20863145179911316, + 5.582119462388951 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/d41d81/d41d81.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "acc" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/acc/d41d81/d41d81.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + -0.20863145179911316, + 5.573262528211078, + -0.18948660187120017, + 5.593203677296213 + ], + "stac_extensions": [], + "collection": "acc" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/catalog.json b/tests/data-files/catalogs/test-case-4/catalog.json index 98231c5d2..dbb196930 100644 --- a/tests/data-files/catalogs/test-case-4/catalog.json +++ b/tests/data-files/catalogs/test-case-4/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "train_tier_1", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data and labels", "links": [ { @@ -43,5 +44,6 @@ "href": "./catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json b/tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json index a3f1dff59..f69f203cf 100644 --- a/tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json @@ -1,613 +1,614 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "0a4c40-labels", - "properties": { - "label:description": "Geojson building labels for scene 0a4c40", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 8286 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "0a4c40-labels", + "properties": { + "label:description": "Geojson building labels for scene 0a4c40", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 8286 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.276113972191155, - -6.810947356781285 - ], - [ - 39.27608768801011, - -6.810947371883635 - ], - [ - 39.276084515013736, - -6.808826770575084 - ], - [ - 39.27627895492949, - -6.80710456764605 - ], - [ - 39.276594556504065, - -6.806958701930941 - ], - [ - 39.276510741596354, - -6.806687631444213 - ], - [ - 39.27652119569571, - -6.806681745790719 - ], - [ - 39.276375849532066, - -6.806246732800051 - ], - [ - 39.27676148940767, - -6.802859165095991 - ], - [ - 39.27799210415278, - -6.801568197283092 - ], - [ - 39.27889256685514, - -6.801261282769122 - ], - [ - 39.28093888743356, - -6.801172557514979 - ], - [ - 39.28284276122209, - -6.801144663537702 - ], - [ - 39.287098934034255, - -6.801204220933001 - ], - [ - 39.28786172777648, - -6.801307642089444 - ], - [ - 39.29241967438444, - -6.803227561830162 - ], - [ - 39.29242104009511, - -6.804404801451413 - ], - [ - 39.2952098976496, - -6.804403107757896 - ], - [ - 39.29653702336342, - -6.804962171090828 - ], - [ - 39.29661761435555, - -6.805287463245364 - ], - [ - 39.29669874755685, - -6.805431138675158 - ], - [ - 39.29708487226545, - -6.805193101843461 - ], - [ - 39.29805570346486, - -6.805602121377039 - ], - [ - 39.29895841215751, - -6.806823881391469 - ], - [ - 39.298958934014394, - -6.807667286355446 - ], - [ - 39.29958246153687, - -6.807668860358516 - ], - [ - 39.30200149149115, - -6.810944287822992 - ], - [ - 39.30267181005435, - -6.812597360144989 - ], - [ - 39.302944057686055, - -6.814197764053667 - ], - [ - 39.30223097991555, - -6.814198210670114 - ], - [ - 39.30223302578063, - -6.817464687860646 - ], - [ - 39.30350167306014, - -6.817465198761061 - ], - [ - 39.30428300792764, - -6.822048228051391 - ], - [ - 39.30439216680433, - -6.825162418177655 - ], - [ - 39.30421322355657, - -6.825395757497203 - ], - [ - 39.3043884650693, - -6.825510626914298 - ], - [ - 39.304361679630816, - -6.825530895971746 - ], - [ - 39.304406143224945, - -6.825558959610252 - ], - [ - 39.30445672661969, - -6.826996830597941 - ], - [ - 39.30444382230467, - -6.827262729915446 - ], - [ - 39.29243489660985, - -6.827270162213488 - ], - [ - 39.29243291419468, - -6.824003683429136 - ], - [ - 39.29570098395584, - -6.824001691395822 - ], - [ - 39.295698980368996, - -6.820735213131869 - ], - [ - 39.292430932750584, - -6.820737204202933 - ], - [ - 39.29243203391734, - -6.822552713411381 - ], - [ - 39.291886091399185, - -6.822261673892082 - ], - [ - 39.291331712185126, - -6.822069286537019 - ], - [ - 39.29079159396925, - -6.822763412848062 - ], - [ - 39.290916634631486, - -6.823095865220003 - ], - [ - 39.290887286706145, - -6.823203023482961 - ], - [ - 39.29032132310435, - -6.823311811527542 - ], - [ - 39.290350214705896, - -6.823531954953058 - ], - [ - 39.29052497169452, - -6.823933626889247 - ], - [ - 39.2905158640202, - -6.824004841654237 - ], - [ - 39.28916484346245, - -6.824005653325033 - ], - [ - 39.28916680372398, - -6.827272133060951 - ], - [ - 39.27936251403853, - -6.827277912717446 - ], - [ - 39.28589870987786, - -6.827274081760294 - ], - [ - 39.28916680372398, - -6.827272133060967 - ], - [ - 39.29243489660985, - -6.827270162213504 - ], - [ - 39.29243687999612, - -6.830536640555797 - ], - [ - 39.29570499407533, - -6.830534646597951 - ], - [ - 39.29570298852461, - -6.82726816921793 - ], - [ - 39.302239169397396, - -6.827264116782583 - ], - [ - 39.302241357885, - -6.830751405928632 - ], - [ - 39.30042734934312, - -6.831470511704737 - ], - [ - 39.285901405812794, - -6.831815142462718 - ], - [ - 39.28590064893513, - -6.830540561995053 - ], - [ - 39.28590140581279, - -6.831815142462718 - ], - [ - 39.28432030798075, - -6.831852009204463 - ], - [ - 39.28420651086406, - -6.831739709369598 - ], - [ - 39.28417828929836, - -6.831543737123127 - ], - [ - 39.28342419476295, - -6.831864296417407 - ], - [ - 39.283227357093445, - -6.831700434763547 - ], - [ - 39.28316646237391, - -6.831517547569526 - ], - [ - 39.283127252955246, - -6.831531289830293 - ], - [ - 39.28263380917574, - -6.831604748672614 - ], - [ - 39.282631875915335, - -6.831651787146114 - ], - [ - 39.282491354591365, - -6.831665588763534 - ], - [ - 39.28195217439128, - -6.831769125360806 - ], - [ - 39.281322169788, - -6.831923671517303 - ], - [ - 39.27951165146522, - -6.831966536058077 - ], - [ - 39.278917481559084, - -6.831925069351034 - ], - [ - 39.277115771956005, - -6.831374726411127 - ], - [ - 39.276947089372904, - -6.831292507992302 - ], - [ - 39.27656238533688, - -6.830644005256977 - ], - [ - 39.276191770934496, - -6.829487882480712 - ], - [ - 39.27617850435042, - -6.828009479393501 - ], - [ - 39.27629548853805, - -6.827985240388855 - ], - [ - 39.276252256300765, - -6.827822594249342 - ], - [ - 39.276298667078755, - -6.827829100625223 - ], - [ - 39.27627245745106, - -6.827716095291023 - ], - [ - 39.276320740638376, - -6.827568422539262 - ], - [ - 39.27629900556454, - -6.827279677789797 - ], - [ - 39.279327705491625, - -6.827277932772754 - ], - [ - 39.27624148705606, - -6.827279710743532 - ], - [ - 39.27621387584599, - -6.827002075397047 - ], - [ - 39.27616943986283, - -6.827019739862473 - ], - [ - 39.27612683468972, - -6.822247430209742 - ], - [ - 39.27646012316207, - -6.822153164618359 - ], - [ - 39.27643255943666, - -6.821957844611729 - ], - [ - 39.27643188409867, - -6.82191995377355 - ], - [ - 39.276448873893564, - -6.821912757774376 - ], - [ - 39.27641345973164, - -6.821705029618133 - ], - [ - 39.27636576816117, - -6.821743601454265 - ], - [ - 39.276296925889824, - -6.821371914879673 - ], - [ - 39.27622372478111, - -6.821377836454363 - ], - [ - 39.27620996114228, - -6.821311861333559 - ], - [ - 39.27624850961271, - -6.821286360687025 - ], - [ - 39.27621640032443, - -6.821142653721728 - ], - [ - 39.276236665262545, - -6.821147868499644 - ], - [ - 39.2762373181251, - -6.821146561531956 - ], - [ - 39.27614885165009, - -6.820746794378429 - ], - [ - 39.27611290306914, - -6.820746814944531 - ], - [ - 39.27609678846316, - -6.81657356235559 - ], - [ - 39.27641899996001, - -6.816545939565581 - ], - [ - 39.276407184174914, - -6.81645709791839 - ], - [ - 39.276266686088135, - -6.816504215701405 - ], - [ - 39.276095987198005, - -6.816315510439744 - ], - [ - 39.27609576029834, - -6.815918959194581 - ], - [ - 39.27610621422462, - -6.815912420247079 - ], - [ - 39.276095021844235, - -6.815770661160172 - ], - [ - 39.276088593360576, - -6.811387693741025 - ], - [ - 39.276564395401294, - -6.811357370035534 - ], - [ - 39.27650879806149, - -6.811284885849981 - ], - [ - 39.27645123321342, - -6.811200643431646 - ], - [ - 39.27644727422708, - -6.811135315968837 - ], - [ - 39.27646231033641, - -6.811141187043987 - ], - [ - 39.27646032560643, - -6.81109937715377 - ], - [ - 39.27641589593634, - -6.811125534455874 - ], - [ - 39.27643093129704, - -6.8111300989379 - ], - [ - 39.276088566839796, - -6.811341309648862 - ], - [ - 39.27608768801011, - -6.810947371883652 - ], - [ - 39.276113972191155, - -6.810947356781285 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.276113972191155, + -6.810947356781285 + ], + [ + 39.27608768801011, + -6.810947371883635 + ], + [ + 39.276084515013736, + -6.808826770575084 + ], + [ + 39.27627895492949, + -6.80710456764605 + ], + [ + 39.276594556504065, + -6.806958701930941 + ], + [ + 39.276510741596354, + -6.806687631444213 + ], + [ + 39.27652119569571, + -6.806681745790719 + ], + [ + 39.276375849532066, + -6.806246732800051 + ], + [ + 39.27676148940767, + -6.802859165095991 + ], + [ + 39.27799210415278, + -6.801568197283092 + ], + [ + 39.27889256685514, + -6.801261282769122 + ], + [ + 39.28093888743356, + -6.801172557514979 + ], + [ + 39.28284276122209, + -6.801144663537702 + ], + [ + 39.287098934034255, + -6.801204220933001 + ], + [ + 39.28786172777648, + -6.801307642089444 + ], + [ + 39.29241967438444, + -6.803227561830162 + ], + [ + 39.29242104009511, + -6.804404801451413 + ], + [ + 39.2952098976496, + -6.804403107757896 + ], + [ + 39.29653702336342, + -6.804962171090828 + ], + [ + 39.29661761435555, + -6.805287463245364 + ], + [ + 39.29669874755685, + -6.805431138675158 + ], + [ + 39.29708487226545, + -6.805193101843461 + ], + [ + 39.29805570346486, + -6.805602121377039 + ], + [ + 39.29895841215751, + -6.806823881391469 + ], + [ + 39.298958934014394, + -6.807667286355446 + ], + [ + 39.29958246153687, + -6.807668860358516 + ], + [ + 39.30200149149115, + -6.810944287822992 + ], + [ + 39.30267181005435, + -6.812597360144989 + ], + [ + 39.302944057686055, + -6.814197764053667 + ], + [ + 39.30223097991555, + -6.814198210670114 + ], + [ + 39.30223302578063, + -6.817464687860646 + ], + [ + 39.30350167306014, + -6.817465198761061 + ], + [ + 39.30428300792764, + -6.822048228051391 + ], + [ + 39.30439216680433, + -6.825162418177655 + ], + [ + 39.30421322355657, + -6.825395757497203 + ], + [ + 39.3043884650693, + -6.825510626914298 + ], + [ + 39.304361679630816, + -6.825530895971746 + ], + [ + 39.304406143224945, + -6.825558959610252 + ], + [ + 39.30445672661969, + -6.826996830597941 + ], + [ + 39.30444382230467, + -6.827262729915446 + ], + [ + 39.29243489660985, + -6.827270162213488 + ], + [ + 39.29243291419468, + -6.824003683429136 + ], + [ + 39.29570098395584, + -6.824001691395822 + ], + [ + 39.295698980368996, + -6.820735213131869 + ], + [ + 39.292430932750584, + -6.820737204202933 + ], + [ + 39.29243203391734, + -6.822552713411381 + ], + [ + 39.291886091399185, + -6.822261673892082 + ], + [ + 39.291331712185126, + -6.822069286537019 + ], + [ + 39.29079159396925, + -6.822763412848062 + ], + [ + 39.290916634631486, + -6.823095865220003 + ], + [ + 39.290887286706145, + -6.823203023482961 + ], + [ + 39.29032132310435, + -6.823311811527542 + ], + [ + 39.290350214705896, + -6.823531954953058 + ], + [ + 39.29052497169452, + -6.823933626889247 + ], + [ + 39.2905158640202, + -6.824004841654237 + ], + [ + 39.28916484346245, + -6.824005653325033 + ], + [ + 39.28916680372398, + -6.827272133060951 + ], + [ + 39.27936251403853, + -6.827277912717446 + ], + [ + 39.28589870987786, + -6.827274081760294 + ], + [ + 39.28916680372398, + -6.827272133060967 + ], + [ + 39.29243489660985, + -6.827270162213504 + ], + [ + 39.29243687999612, + -6.830536640555797 + ], + [ + 39.29570499407533, + -6.830534646597951 + ], + [ + 39.29570298852461, + -6.82726816921793 + ], + [ + 39.302239169397396, + -6.827264116782583 + ], + [ + 39.302241357885, + -6.830751405928632 + ], + [ + 39.30042734934312, + -6.831470511704737 + ], + [ + 39.285901405812794, + -6.831815142462718 + ], + [ + 39.28590064893513, + -6.830540561995053 + ], + [ + 39.28590140581279, + -6.831815142462718 + ], + [ + 39.28432030798075, + -6.831852009204463 + ], + [ + 39.28420651086406, + -6.831739709369598 + ], + [ + 39.28417828929836, + -6.831543737123127 + ], + [ + 39.28342419476295, + -6.831864296417407 + ], + [ + 39.283227357093445, + -6.831700434763547 + ], + [ + 39.28316646237391, + -6.831517547569526 + ], + [ + 39.283127252955246, + -6.831531289830293 + ], + [ + 39.28263380917574, + -6.831604748672614 + ], + [ + 39.282631875915335, + -6.831651787146114 + ], + [ + 39.282491354591365, + -6.831665588763534 + ], + [ + 39.28195217439128, + -6.831769125360806 + ], + [ + 39.281322169788, + -6.831923671517303 + ], + [ + 39.27951165146522, + -6.831966536058077 + ], + [ + 39.278917481559084, + -6.831925069351034 + ], + [ + 39.277115771956005, + -6.831374726411127 + ], + [ + 39.276947089372904, + -6.831292507992302 + ], + [ + 39.27656238533688, + -6.830644005256977 + ], + [ + 39.276191770934496, + -6.829487882480712 + ], + [ + 39.27617850435042, + -6.828009479393501 + ], + [ + 39.27629548853805, + -6.827985240388855 + ], + [ + 39.276252256300765, + -6.827822594249342 + ], + [ + 39.276298667078755, + -6.827829100625223 + ], + [ + 39.27627245745106, + -6.827716095291023 + ], + [ + 39.276320740638376, + -6.827568422539262 + ], + [ + 39.27629900556454, + -6.827279677789797 + ], + [ + 39.279327705491625, + -6.827277932772754 + ], + [ + 39.27624148705606, + -6.827279710743532 + ], + [ + 39.27621387584599, + -6.827002075397047 + ], + [ + 39.27616943986283, + -6.827019739862473 + ], + [ + 39.27612683468972, + -6.822247430209742 + ], + [ + 39.27646012316207, + -6.822153164618359 + ], + [ + 39.27643255943666, + -6.821957844611729 + ], + [ + 39.27643188409867, + -6.82191995377355 + ], + [ + 39.276448873893564, + -6.821912757774376 + ], + [ + 39.27641345973164, + -6.821705029618133 + ], + [ + 39.27636576816117, + -6.821743601454265 + ], + [ + 39.276296925889824, + -6.821371914879673 + ], + [ + 39.27622372478111, + -6.821377836454363 + ], + [ + 39.27620996114228, + -6.821311861333559 + ], + [ + 39.27624850961271, + -6.821286360687025 + ], + [ + 39.27621640032443, + -6.821142653721728 + ], + [ + 39.276236665262545, + -6.821147868499644 + ], + [ + 39.2762373181251, + -6.821146561531956 + ], + [ + 39.27614885165009, + -6.820746794378429 + ], + [ + 39.27611290306914, + -6.820746814944531 + ], + [ + 39.27609678846316, + -6.81657356235559 + ], + [ + 39.27641899996001, + -6.816545939565581 + ], + [ + 39.276407184174914, + -6.81645709791839 + ], + [ + 39.276266686088135, + -6.816504215701405 + ], + [ + 39.276095987198005, + -6.816315510439744 + ], + [ + 39.27609576029834, + -6.815918959194581 + ], + [ + 39.27610621422462, + -6.815912420247079 + ], + [ + 39.276095021844235, + -6.815770661160172 + ], + [ + 39.276088593360576, + -6.811387693741025 + ], + [ + 39.276564395401294, + -6.811357370035534 + ], + [ + 39.27650879806149, + -6.811284885849981 + ], + [ + 39.27645123321342, + -6.811200643431646 + ], + [ + 39.27644727422708, + -6.811135315968837 + ], + [ + 39.27646231033641, + -6.811141187043987 + ], + [ + 39.27646032560643, + -6.81109937715377 + ], + [ + 39.27641589593634, + -6.811125534455874 + ], + [ + 39.27643093129704, + -6.8111300989379 + ], + [ + 39.276088566839796, + -6.811341309648862 + ], + [ + 39.27608768801011, + -6.810947371883652 + ], + [ + 39.276113972191155, + -6.810947356781285 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.276084515013736, - -6.831966577377629, - 39.304456754694705, - -6.801144663537702 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/0a4c40-labels/0a4c40.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/0a4c40-labels/0a4c40.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.276084515013736, + -6.831966577377629, + 39.304456754694705, + -6.801144663537702 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json b/tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json index af75d448b..20eb50e69 100644 --- a/tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json +++ b/tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json @@ -1,588 +1,589 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "0a4c40", - "properties": { - "area": "dar", - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.276113972191155, - -6.810947356781285 - ], - [ - 39.27608768801011, - -6.810947371883635 - ], - [ - 39.276084515013736, - -6.808826770575084 - ], - [ - 39.27627895492949, - -6.80710456764605 - ], - [ - 39.276594556504065, - -6.806958701930941 - ], - [ - 39.276510741596354, - -6.806687631444213 - ], - [ - 39.27652119569571, - -6.806681745790719 - ], - [ - 39.276375849532066, - -6.806246732800051 - ], - [ - 39.27676148940767, - -6.802859165095991 - ], - [ - 39.27799210415278, - -6.801568197283092 - ], - [ - 39.27889256685514, - -6.801261282769122 - ], - [ - 39.28093888743356, - -6.801172557514979 - ], - [ - 39.28284276122209, - -6.801144663537702 - ], - [ - 39.287098934034255, - -6.801204220933001 - ], - [ - 39.28786172777648, - -6.801307642089444 - ], - [ - 39.29241967438444, - -6.803227561830162 - ], - [ - 39.29242104009511, - -6.804404801451413 - ], - [ - 39.2952098976496, - -6.804403107757896 - ], - [ - 39.29653702336342, - -6.804962171090828 - ], - [ - 39.29661761435555, - -6.805287463245364 - ], - [ - 39.29669874755685, - -6.805431138675158 - ], - [ - 39.29708487226545, - -6.805193101843461 - ], - [ - 39.29805570346486, - -6.805602121377039 - ], - [ - 39.29895841215751, - -6.806823881391469 - ], - [ - 39.298958934014394, - -6.807667286355446 - ], - [ - 39.29958246153687, - -6.807668860358516 - ], - [ - 39.30200149149115, - -6.810944287822992 - ], - [ - 39.30267181005435, - -6.812597360144989 - ], - [ - 39.302944057686055, - -6.814197764053667 - ], - [ - 39.30223097991555, - -6.814198210670114 - ], - [ - 39.30223302578063, - -6.817464687860646 - ], - [ - 39.30350167306014, - -6.817465198761061 - ], - [ - 39.30428300792764, - -6.822048228051391 - ], - [ - 39.30439216680433, - -6.825162418177655 - ], - [ - 39.30421322355657, - -6.825395757497203 - ], - [ - 39.3043884650693, - -6.825510626914298 - ], - [ - 39.304361679630816, - -6.825530895971746 - ], - [ - 39.304406143224945, - -6.825558959610252 - ], - [ - 39.30445672661969, - -6.826996830597941 - ], - [ - 39.30444382230467, - -6.827262729915446 - ], - [ - 39.29243489660985, - -6.827270162213488 - ], - [ - 39.29243291419468, - -6.824003683429136 - ], - [ - 39.29570098395584, - -6.824001691395822 - ], - [ - 39.295698980368996, - -6.820735213131869 - ], - [ - 39.292430932750584, - -6.820737204202933 - ], - [ - 39.29243203391734, - -6.822552713411381 - ], - [ - 39.291886091399185, - -6.822261673892082 - ], - [ - 39.291331712185126, - -6.822069286537019 - ], - [ - 39.29079159396925, - -6.822763412848062 - ], - [ - 39.290916634631486, - -6.823095865220003 - ], - [ - 39.290887286706145, - -6.823203023482961 - ], - [ - 39.29032132310435, - -6.823311811527542 - ], - [ - 39.290350214705896, - -6.823531954953058 - ], - [ - 39.29052497169452, - -6.823933626889247 - ], - [ - 39.2905158640202, - -6.824004841654237 - ], - [ - 39.28916484346245, - -6.824005653325033 - ], - [ - 39.28916680372398, - -6.827272133060951 - ], - [ - 39.27936251403853, - -6.827277912717446 - ], - [ - 39.28589870987786, - -6.827274081760294 - ], - [ - 39.28916680372398, - -6.827272133060967 - ], - [ - 39.29243489660985, - -6.827270162213504 - ], - [ - 39.29243687999612, - -6.830536640555797 - ], - [ - 39.29570499407533, - -6.830534646597951 - ], - [ - 39.29570298852461, - -6.82726816921793 - ], - [ - 39.302239169397396, - -6.827264116782583 - ], - [ - 39.302241357885, - -6.830751405928632 - ], - [ - 39.30042734934312, - -6.831470511704737 - ], - [ - 39.285901405812794, - -6.831815142462718 - ], - [ - 39.28590064893513, - -6.830540561995053 - ], - [ - 39.28590140581279, - -6.831815142462718 - ], - [ - 39.28432030798075, - -6.831852009204463 - ], - [ - 39.28420651086406, - -6.831739709369598 - ], - [ - 39.28417828929836, - -6.831543737123127 - ], - [ - 39.28342419476295, - -6.831864296417407 - ], - [ - 39.283227357093445, - -6.831700434763547 - ], - [ - 39.28316646237391, - -6.831517547569526 - ], - [ - 39.283127252955246, - -6.831531289830293 - ], - [ - 39.28263380917574, - -6.831604748672614 - ], - [ - 39.282631875915335, - -6.831651787146114 - ], - [ - 39.282491354591365, - -6.831665588763534 - ], - [ - 39.28195217439128, - -6.831769125360806 - ], - [ - 39.281322169788, - -6.831923671517303 - ], - [ - 39.27951165146522, - -6.831966536058077 - ], - [ - 39.278917481559084, - -6.831925069351034 - ], - [ - 39.277115771956005, - -6.831374726411127 - ], - [ - 39.276947089372904, - -6.831292507992302 - ], - [ - 39.27656238533688, - -6.830644005256977 - ], - [ - 39.276191770934496, - -6.829487882480712 - ], - [ - 39.27617850435042, - -6.828009479393501 - ], - [ - 39.27629548853805, - -6.827985240388855 - ], - [ - 39.276252256300765, - -6.827822594249342 - ], - [ - 39.276298667078755, - -6.827829100625223 - ], - [ - 39.27627245745106, - -6.827716095291023 - ], - [ - 39.276320740638376, - -6.827568422539262 - ], - [ - 39.27629900556454, - -6.827279677789797 - ], - [ - 39.279327705491625, - -6.827277932772754 - ], - [ - 39.27624148705606, - -6.827279710743532 - ], - [ - 39.27621387584599, - -6.827002075397047 - ], - [ - 39.27616943986283, - -6.827019739862473 - ], - [ - 39.27612683468972, - -6.822247430209742 - ], - [ - 39.27646012316207, - -6.822153164618359 - ], - [ - 39.27643255943666, - -6.821957844611729 - ], - [ - 39.27643188409867, - -6.82191995377355 - ], - [ - 39.276448873893564, - -6.821912757774376 - ], - [ - 39.27641345973164, - -6.821705029618133 - ], - [ - 39.27636576816117, - -6.821743601454265 - ], - [ - 39.276296925889824, - -6.821371914879673 - ], - [ - 39.27622372478111, - -6.821377836454363 - ], - [ - 39.27620996114228, - -6.821311861333559 - ], - [ - 39.27624850961271, - -6.821286360687025 - ], - [ - 39.27621640032443, - -6.821142653721728 - ], - [ - 39.276236665262545, - -6.821147868499644 - ], - [ - 39.2762373181251, - -6.821146561531956 - ], - [ - 39.27614885165009, - -6.820746794378429 - ], - [ - 39.27611290306914, - -6.820746814944531 - ], - [ - 39.27609678846316, - -6.81657356235559 - ], - [ - 39.27641899996001, - -6.816545939565581 - ], - [ - 39.276407184174914, - -6.81645709791839 - ], - [ - 39.276266686088135, - -6.816504215701405 - ], - [ - 39.276095987198005, - -6.816315510439744 - ], - [ - 39.27609576029834, - -6.815918959194581 - ], - [ - 39.27610621422462, - -6.815912420247079 - ], - [ - 39.276095021844235, - -6.815770661160172 - ], - [ - 39.276088593360576, - -6.811387693741025 - ], - [ - 39.276564395401294, - -6.811357370035534 - ], - [ - 39.27650879806149, - -6.811284885849981 - ], - [ - 39.27645123321342, - -6.811200643431646 - ], - [ - 39.27644727422708, - -6.811135315968837 - ], - [ - 39.27646231033641, - -6.811141187043987 - ], - [ - 39.27646032560643, - -6.81109937715377 - ], - [ - 39.27641589593634, - -6.811125534455874 - ], - [ - 39.27643093129704, - -6.8111300989379 - ], - [ - 39.276088566839796, - -6.811341309648862 - ], - [ - 39.27608768801011, - -6.810947371883652 - ], - [ - 39.276113972191155, - -6.810947356781285 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "0a4c40", + "properties": { + "area": "dar", + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.276113972191155, + -6.810947356781285 + ], + [ + 39.27608768801011, + -6.810947371883635 + ], + [ + 39.276084515013736, + -6.808826770575084 + ], + [ + 39.27627895492949, + -6.80710456764605 + ], + [ + 39.276594556504065, + -6.806958701930941 + ], + [ + 39.276510741596354, + -6.806687631444213 + ], + [ + 39.27652119569571, + -6.806681745790719 + ], + [ + 39.276375849532066, + -6.806246732800051 + ], + [ + 39.27676148940767, + -6.802859165095991 + ], + [ + 39.27799210415278, + -6.801568197283092 + ], + [ + 39.27889256685514, + -6.801261282769122 + ], + [ + 39.28093888743356, + -6.801172557514979 + ], + [ + 39.28284276122209, + -6.801144663537702 + ], + [ + 39.287098934034255, + -6.801204220933001 + ], + [ + 39.28786172777648, + -6.801307642089444 + ], + [ + 39.29241967438444, + -6.803227561830162 + ], + [ + 39.29242104009511, + -6.804404801451413 + ], + [ + 39.2952098976496, + -6.804403107757896 + ], + [ + 39.29653702336342, + -6.804962171090828 + ], + [ + 39.29661761435555, + -6.805287463245364 + ], + [ + 39.29669874755685, + -6.805431138675158 + ], + [ + 39.29708487226545, + -6.805193101843461 + ], + [ + 39.29805570346486, + -6.805602121377039 + ], + [ + 39.29895841215751, + -6.806823881391469 + ], + [ + 39.298958934014394, + -6.807667286355446 + ], + [ + 39.29958246153687, + -6.807668860358516 + ], + [ + 39.30200149149115, + -6.810944287822992 + ], + [ + 39.30267181005435, + -6.812597360144989 + ], + [ + 39.302944057686055, + -6.814197764053667 + ], + [ + 39.30223097991555, + -6.814198210670114 + ], + [ + 39.30223302578063, + -6.817464687860646 + ], + [ + 39.30350167306014, + -6.817465198761061 + ], + [ + 39.30428300792764, + -6.822048228051391 + ], + [ + 39.30439216680433, + -6.825162418177655 + ], + [ + 39.30421322355657, + -6.825395757497203 + ], + [ + 39.3043884650693, + -6.825510626914298 + ], + [ + 39.304361679630816, + -6.825530895971746 + ], + [ + 39.304406143224945, + -6.825558959610252 + ], + [ + 39.30445672661969, + -6.826996830597941 + ], + [ + 39.30444382230467, + -6.827262729915446 + ], + [ + 39.29243489660985, + -6.827270162213488 + ], + [ + 39.29243291419468, + -6.824003683429136 + ], + [ + 39.29570098395584, + -6.824001691395822 + ], + [ + 39.295698980368996, + -6.820735213131869 + ], + [ + 39.292430932750584, + -6.820737204202933 + ], + [ + 39.29243203391734, + -6.822552713411381 + ], + [ + 39.291886091399185, + -6.822261673892082 + ], + [ + 39.291331712185126, + -6.822069286537019 + ], + [ + 39.29079159396925, + -6.822763412848062 + ], + [ + 39.290916634631486, + -6.823095865220003 + ], + [ + 39.290887286706145, + -6.823203023482961 + ], + [ + 39.29032132310435, + -6.823311811527542 + ], + [ + 39.290350214705896, + -6.823531954953058 + ], + [ + 39.29052497169452, + -6.823933626889247 + ], + [ + 39.2905158640202, + -6.824004841654237 + ], + [ + 39.28916484346245, + -6.824005653325033 + ], + [ + 39.28916680372398, + -6.827272133060951 + ], + [ + 39.27936251403853, + -6.827277912717446 + ], + [ + 39.28589870987786, + -6.827274081760294 + ], + [ + 39.28916680372398, + -6.827272133060967 + ], + [ + 39.29243489660985, + -6.827270162213504 + ], + [ + 39.29243687999612, + -6.830536640555797 + ], + [ + 39.29570499407533, + -6.830534646597951 + ], + [ + 39.29570298852461, + -6.82726816921793 + ], + [ + 39.302239169397396, + -6.827264116782583 + ], + [ + 39.302241357885, + -6.830751405928632 + ], + [ + 39.30042734934312, + -6.831470511704737 + ], + [ + 39.285901405812794, + -6.831815142462718 + ], + [ + 39.28590064893513, + -6.830540561995053 + ], + [ + 39.28590140581279, + -6.831815142462718 + ], + [ + 39.28432030798075, + -6.831852009204463 + ], + [ + 39.28420651086406, + -6.831739709369598 + ], + [ + 39.28417828929836, + -6.831543737123127 + ], + [ + 39.28342419476295, + -6.831864296417407 + ], + [ + 39.283227357093445, + -6.831700434763547 + ], + [ + 39.28316646237391, + -6.831517547569526 + ], + [ + 39.283127252955246, + -6.831531289830293 + ], + [ + 39.28263380917574, + -6.831604748672614 + ], + [ + 39.282631875915335, + -6.831651787146114 + ], + [ + 39.282491354591365, + -6.831665588763534 + ], + [ + 39.28195217439128, + -6.831769125360806 + ], + [ + 39.281322169788, + -6.831923671517303 + ], + [ + 39.27951165146522, + -6.831966536058077 + ], + [ + 39.278917481559084, + -6.831925069351034 + ], + [ + 39.277115771956005, + -6.831374726411127 + ], + [ + 39.276947089372904, + -6.831292507992302 + ], + [ + 39.27656238533688, + -6.830644005256977 + ], + [ + 39.276191770934496, + -6.829487882480712 + ], + [ + 39.27617850435042, + -6.828009479393501 + ], + [ + 39.27629548853805, + -6.827985240388855 + ], + [ + 39.276252256300765, + -6.827822594249342 + ], + [ + 39.276298667078755, + -6.827829100625223 + ], + [ + 39.27627245745106, + -6.827716095291023 + ], + [ + 39.276320740638376, + -6.827568422539262 + ], + [ + 39.27629900556454, + -6.827279677789797 + ], + [ + 39.279327705491625, + -6.827277932772754 + ], + [ + 39.27624148705606, + -6.827279710743532 + ], + [ + 39.27621387584599, + -6.827002075397047 + ], + [ + 39.27616943986283, + -6.827019739862473 + ], + [ + 39.27612683468972, + -6.822247430209742 + ], + [ + 39.27646012316207, + -6.822153164618359 + ], + [ + 39.27643255943666, + -6.821957844611729 + ], + [ + 39.27643188409867, + -6.82191995377355 + ], + [ + 39.276448873893564, + -6.821912757774376 + ], + [ + 39.27641345973164, + -6.821705029618133 + ], + [ + 39.27636576816117, + -6.821743601454265 + ], + [ + 39.276296925889824, + -6.821371914879673 + ], + [ + 39.27622372478111, + -6.821377836454363 + ], + [ + 39.27620996114228, + -6.821311861333559 + ], + [ + 39.27624850961271, + -6.821286360687025 + ], + [ + 39.27621640032443, + -6.821142653721728 + ], + [ + 39.276236665262545, + -6.821147868499644 + ], + [ + 39.2762373181251, + -6.821146561531956 + ], + [ + 39.27614885165009, + -6.820746794378429 + ], + [ + 39.27611290306914, + -6.820746814944531 + ], + [ + 39.27609678846316, + -6.81657356235559 + ], + [ + 39.27641899996001, + -6.816545939565581 + ], + [ + 39.276407184174914, + -6.81645709791839 + ], + [ + 39.276266686088135, + -6.816504215701405 + ], + [ + 39.276095987198005, + -6.816315510439744 + ], + [ + 39.27609576029834, + -6.815918959194581 + ], + [ + 39.27610621422462, + -6.815912420247079 + ], + [ + 39.276095021844235, + -6.815770661160172 + ], + [ + 39.276088593360576, + -6.811387693741025 + ], + [ + 39.276564395401294, + -6.811357370035534 + ], + [ + 39.27650879806149, + -6.811284885849981 + ], + [ + 39.27645123321342, + -6.811200643431646 + ], + [ + 39.27644727422708, + -6.811135315968837 + ], + [ + 39.27646231033641, + -6.811141187043987 + ], + [ + 39.27646032560643, + -6.81109937715377 + ], + [ + 39.27641589593634, + -6.811125534455874 + ], + [ + 39.27643093129704, + -6.8111300989379 + ], + [ + 39.276088566839796, + -6.811341309648862 + ], + [ + 39.27608768801011, + -6.810947371883652 + ], + [ + 39.276113972191155, + -6.810947356781285 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.276084515013736, - -6.831966577377629, - 39.304456754694705, - -6.801144663537702 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/0a4c40/0a4c40.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/0a4c40/0a4c40.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "bbox": [ + 39.276084515013736, + -6.831966577377629, + 39.304456754694705, + -6.801144663537702 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json b/tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json index 6750ec422..63e7bcb9c 100644 --- a/tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json @@ -1,229 +1,230 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "353093-labels", - "properties": { - "label:description": "Geojson building labels for scene 353093", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 6998 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2015-04-20T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "353093-labels", + "properties": { + "label:description": "Geojson building labels for scene 353093", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 6998 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2015-04-20T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.21152919667973, - -6.808359253694025 - ], - [ - 39.21101071619515, - -6.80780969783032 - ], - [ - 39.2108730885634, - -6.806774452292519 - ], - [ - 39.20942360688795, - -6.80495789295139 - ], - [ - 39.20849433161743, - -6.804654136577633 - ], - [ - 39.20787596866212, - -6.803293387986344 - ], - [ - 39.20635028282986, - -6.80279870007748 - ], - [ - 39.2046107249861, - -6.795381424410041 - ], - [ - 39.20391600733412, - -6.79149776634182 - ], - [ - 39.20390908985627, - -6.791383173329291 - ], - [ - 39.20496051556537, - -6.7866740661368175 - ], - [ - 39.20505620172756, - -6.786303447012387 - ], - [ - 39.20621306671285, - -6.785058576896908 - ], - [ - 39.20929283360648, - -6.783684457665884 - ], - [ - 39.209550980241275, - -6.783726455597161 - ], - [ - 39.213536684353556, - -6.784551417830786 - ], - [ - 39.21258418889021, - -6.789322183261566 - ], - [ - 39.2163274162412, - -6.790171293774371 - ], - [ - 39.21759522944233, - -6.791810729353808 - ], - [ - 39.22088166017734, - -6.7926145242067575 - ], - [ - 39.22094061195484, - -6.792658437353369 - ], - [ - 39.22238742900873, - -6.794159377815079 - ], - [ - 39.22616044436698, - -6.795158821931178 - ], - [ - 39.22707373283919, - -6.796048913257977 - ], - [ - 39.22708687129574, - -6.796117385555728 - ], - [ - 39.227083424855664, - -6.796231104782896 - ], - [ - 39.22701001344086, - -6.79668679086265 - ], - [ - 39.22681107485247, - -6.797855302295221 - ], - [ - 39.22679477506837, - -6.797883580670773 - ], - [ - 39.21886584724193, - -6.796354991252584 - ], - [ - 39.21789760196279, - -6.800821422455203 - ], - [ - 39.21585912856685, - -6.802696774914448 - ], - [ - 39.2146774574239, - -6.808974568223004 - ], - [ - 39.21453136175979, - -6.809102537362755 - ], - [ - 39.21433909072671, - -6.809236085323197 - ], - [ - 39.213911464788005, - -6.809502598801659 - ], - [ - 39.21384943878973, - -6.809510517136742 - ], - [ - 39.212327864627525, - -6.809201572001297 - ], - [ - 39.21152919667973, - -6.808359253694025 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.20390891675323, - -6.809511989589884, - 39.2270869431941, - -6.783684392169557 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.21152919667973, + -6.808359253694025 + ], + [ + 39.21101071619515, + -6.80780969783032 + ], + [ + 39.2108730885634, + -6.806774452292519 + ], + [ + 39.20942360688795, + -6.80495789295139 + ], + [ + 39.20849433161743, + -6.804654136577633 + ], + [ + 39.20787596866212, + -6.803293387986344 + ], + [ + 39.20635028282986, + -6.80279870007748 + ], + [ + 39.2046107249861, + -6.795381424410041 + ], + [ + 39.20391600733412, + -6.79149776634182 + ], + [ + 39.20390908985627, + -6.791383173329291 + ], + [ + 39.20496051556537, + -6.7866740661368175 + ], + [ + 39.20505620172756, + -6.786303447012387 + ], + [ + 39.20621306671285, + -6.785058576896908 + ], + [ + 39.20929283360648, + -6.783684457665884 + ], + [ + 39.209550980241275, + -6.783726455597161 + ], + [ + 39.213536684353556, + -6.784551417830786 + ], + [ + 39.21258418889021, + -6.789322183261566 + ], + [ + 39.2163274162412, + -6.790171293774371 + ], + [ + 39.21759522944233, + -6.791810729353808 + ], + [ + 39.22088166017734, + -6.7926145242067575 + ], + [ + 39.22094061195484, + -6.792658437353369 + ], + [ + 39.22238742900873, + -6.794159377815079 + ], + [ + 39.22616044436698, + -6.795158821931178 + ], + [ + 39.22707373283919, + -6.796048913257977 + ], + [ + 39.22708687129574, + -6.796117385555728 + ], + [ + 39.227083424855664, + -6.796231104782896 + ], + [ + 39.22701001344086, + -6.79668679086265 + ], + [ + 39.22681107485247, + -6.797855302295221 + ], + [ + 39.22679477506837, + -6.797883580670773 + ], + [ + 39.21886584724193, + -6.796354991252584 + ], + [ + 39.21789760196279, + -6.800821422455203 + ], + [ + 39.21585912856685, + -6.802696774914448 + ], + [ + 39.2146774574239, + -6.808974568223004 + ], + [ + 39.21453136175979, + -6.809102537362755 + ], + [ + 39.21433909072671, + -6.809236085323197 + ], + [ + 39.213911464788005, + -6.809502598801659 + ], + [ + 39.21384943878973, + -6.809510517136742 + ], + [ + 39.212327864627525, + -6.809201572001297 + ], + [ + 39.21152919667973, + -6.808359253694025 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/353093-labels/353093.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/353093-labels/353093.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.20390891675323, + -6.809511989589884, + 39.2270869431941, + -6.783684392169557 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/353093/353093.json b/tests/data-files/catalogs/test-case-4/dar/353093/353093.json index 5e1785da7..b24d05651 100644 --- a/tests/data-files/catalogs/test-case-4/dar/353093/353093.json +++ b/tests/data-files/catalogs/test-case-4/dar/353093/353093.json @@ -1,204 +1,205 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "353093", - "properties": { - "area": "dar", - "license": "CC-BY-4.0", - "datetime": "2015-04-20T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.21152919667973, - -6.808359253694025 - ], - [ - 39.21101071619515, - -6.80780969783032 - ], - [ - 39.2108730885634, - -6.806774452292519 - ], - [ - 39.20942360688795, - -6.80495789295139 - ], - [ - 39.20849433161743, - -6.804654136577633 - ], - [ - 39.20787596866212, - -6.803293387986344 - ], - [ - 39.20635028282986, - -6.80279870007748 - ], - [ - 39.2046107249861, - -6.795381424410041 - ], - [ - 39.20391600733412, - -6.79149776634182 - ], - [ - 39.20390908985627, - -6.791383173329291 - ], - [ - 39.20496051556537, - -6.7866740661368175 - ], - [ - 39.20505620172756, - -6.786303447012387 - ], - [ - 39.20621306671285, - -6.785058576896908 - ], - [ - 39.20929283360648, - -6.783684457665884 - ], - [ - 39.209550980241275, - -6.783726455597161 - ], - [ - 39.213536684353556, - -6.784551417830786 - ], - [ - 39.21258418889021, - -6.789322183261566 - ], - [ - 39.2163274162412, - -6.790171293774371 - ], - [ - 39.21759522944233, - -6.791810729353808 - ], - [ - 39.22088166017734, - -6.7926145242067575 - ], - [ - 39.22094061195484, - -6.792658437353369 - ], - [ - 39.22238742900873, - -6.794159377815079 - ], - [ - 39.22616044436698, - -6.795158821931178 - ], - [ - 39.22707373283919, - -6.796048913257977 - ], - [ - 39.22708687129574, - -6.796117385555728 - ], - [ - 39.227083424855664, - -6.796231104782896 - ], - [ - 39.22701001344086, - -6.79668679086265 - ], - [ - 39.22681107485247, - -6.797855302295221 - ], - [ - 39.22679477506837, - -6.797883580670773 - ], - [ - 39.21886584724193, - -6.796354991252584 - ], - [ - 39.21789760196279, - -6.800821422455203 - ], - [ - 39.21585912856685, - -6.802696774914448 - ], - [ - 39.2146774574239, - -6.808974568223004 - ], - [ - 39.21453136175979, - -6.809102537362755 - ], - [ - 39.21433909072671, - -6.809236085323197 - ], - [ - 39.213911464788005, - -6.809502598801659 - ], - [ - 39.21384943878973, - -6.809510517136742 - ], - [ - 39.212327864627525, - -6.809201572001297 - ], - [ - 39.21152919667973, - -6.808359253694025 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.20390891675323, - -6.809511989589884, - 39.2270869431941, - -6.783684392169557 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "353093", + "properties": { + "area": "dar", + "license": "CC-BY-4.0", + "datetime": "2015-04-20T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.21152919667973, + -6.808359253694025 + ], + [ + 39.21101071619515, + -6.80780969783032 + ], + [ + 39.2108730885634, + -6.806774452292519 + ], + [ + 39.20942360688795, + -6.80495789295139 + ], + [ + 39.20849433161743, + -6.804654136577633 + ], + [ + 39.20787596866212, + -6.803293387986344 + ], + [ + 39.20635028282986, + -6.80279870007748 + ], + [ + 39.2046107249861, + -6.795381424410041 + ], + [ + 39.20391600733412, + -6.79149776634182 + ], + [ + 39.20390908985627, + -6.791383173329291 + ], + [ + 39.20496051556537, + -6.7866740661368175 + ], + [ + 39.20505620172756, + -6.786303447012387 + ], + [ + 39.20621306671285, + -6.785058576896908 + ], + [ + 39.20929283360648, + -6.783684457665884 + ], + [ + 39.209550980241275, + -6.783726455597161 + ], + [ + 39.213536684353556, + -6.784551417830786 + ], + [ + 39.21258418889021, + -6.789322183261566 + ], + [ + 39.2163274162412, + -6.790171293774371 + ], + [ + 39.21759522944233, + -6.791810729353808 + ], + [ + 39.22088166017734, + -6.7926145242067575 + ], + [ + 39.22094061195484, + -6.792658437353369 + ], + [ + 39.22238742900873, + -6.794159377815079 + ], + [ + 39.22616044436698, + -6.795158821931178 + ], + [ + 39.22707373283919, + -6.796048913257977 + ], + [ + 39.22708687129574, + -6.796117385555728 + ], + [ + 39.227083424855664, + -6.796231104782896 + ], + [ + 39.22701001344086, + -6.79668679086265 + ], + [ + 39.22681107485247, + -6.797855302295221 + ], + [ + 39.22679477506837, + -6.797883580670773 + ], + [ + 39.21886584724193, + -6.796354991252584 + ], + [ + 39.21789760196279, + -6.800821422455203 + ], + [ + 39.21585912856685, + -6.802696774914448 + ], + [ + 39.2146774574239, + -6.808974568223004 + ], + [ + 39.21453136175979, + -6.809102537362755 + ], + [ + 39.21433909072671, + -6.809236085323197 + ], + [ + 39.213911464788005, + -6.809502598801659 + ], + [ + 39.21384943878973, + -6.809510517136742 + ], + [ + 39.212327864627525, + -6.809201572001297 + ], + [ + 39.21152919667973, + -6.808359253694025 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/353093/353093.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/353093/353093.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + 39.20390891675323, + -6.809511989589884, + 39.2270869431941, + -6.783684392169557 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json b/tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json index 7ea037593..670590586 100644 --- a/tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json @@ -1,855 +1,856 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "42f235-labels", - "properties": { - "label:description": "Geojson building labels for scene 42f235", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 38011 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - 39.249113885000924, - -6.830281768837318 - ], - [ - 39.24884161123377, - -6.828887292275093 - ], - [ - 39.24871852862772, - -6.826210688475486 - ], - [ - 39.24873780887967, - -6.826104043462706 - ], - [ - 39.24871518124407, - -6.826095666463732 - ], - [ - 39.24876108061556, - -6.825838559040912 - ], - [ - 39.248745334613886, - -6.825824622762926 - ], - [ - 39.24883372959313, - -6.825108372989363 - ], - [ - 39.24881466651106, - -6.825100724860026 - ], - [ - 39.248823648081135, - -6.825021263999036 - ], - [ - 39.24878829196981, - -6.824964024555612 - ], - [ - 39.24875880383777, - -6.823975764750708 - ], - [ - 39.248614822687124, - -6.823955191386981 - ], - [ - 39.24852566077328, - -6.822015378803604 - ], - [ - 39.24847267302938, - -6.817448633758215 - ], - [ - 39.24852805358732, - -6.817447536777786 - ], - [ - 39.24852785202801, - -6.817334983971412 - ], - [ - 39.24851981536032, - -6.817052344829385 - ], - [ - 39.24850867395349, - -6.817040674531265 - ], - [ - 39.248462246290046, - -6.816355584534947 - ], - [ - 39.2484489815814, - -6.814858967526715 - ], - [ - 39.24857555480819, - -6.812582295545642 - ], - [ - 39.249003427800226, - -6.81250520642257 - ], - [ - 39.24898887783278, - -6.81234399560838 - ], - [ - 39.24896866449714, - -6.812339800430215 - ], - [ - 39.248924971349844, - -6.812068805181685 - ], - [ - 39.24880864683033, - -6.812098011765998 - ], - [ - 39.248778859104355, - -6.812074785411178 - ], - [ - 39.248719320462236, - -6.812093089498146 - ], - [ - 39.24861449191581, - -6.811914273108745 - ], - [ - 39.24889679656208, - -6.807117076678235 - ], - [ - 39.24890514165142, - -6.807103387095611 - ], - [ - 39.24895350334043, - -6.807098209917111 - ], - [ - 39.24890327231322, - -6.80700753713003 - ], - [ - 39.24907475678098, - -6.804094158740673 - ], - [ - 39.24930614048839, - -6.802705018401825 - ], - [ - 39.25081787969558, - -6.801411369325037 - ], - [ - 39.251008675447466, - -6.801297890493637 - ], - [ - 39.25320499806219, - -6.800774456159943 - ], - [ - 39.25874764808896, - -6.800811619113268 - ], - [ - 39.258786073608775, - -6.800825135486226 - ], - [ - 39.25879893597007, - -6.800813295767783 - ], - [ - 39.25895579718065, - -6.800866120225621 - ], - [ - 39.258974085499354, - -6.800814994369178 - ], - [ - 39.25902908759272, - -6.800813440425365 - ], - [ - 39.260730218645, - -6.800825015408699 - ], - [ - 39.260776088828045, - -6.800843742362178 - ], - [ - 39.26079119762641, - -6.800826328419392 - ], - [ - 39.26081964802796, - -6.80082562450859 - ], - [ - 39.26279103974784, - -6.800839018382041 - ], - [ - 39.26282408195466, - -6.800853484934448 - ], - [ - 39.26283135889312, - -6.800839827584873 - ], - [ - 39.262880469121384, - -6.800839627101004 - ], - [ - 39.2674255850371, - -6.800870658566417 - ], - [ - 39.27802431978168, - -6.801019804497031 - ], - [ - 39.28109921288827, - -6.801626688409457 - ], - [ - 39.28154897125957, - -6.802640253929808 - ], - [ - 39.281838163359225, - -6.803552989812799 - ], - [ - 39.28188220517211, - -6.804654362572848 - ], - [ - 39.28174842361446, - -6.804677677457344 - ], - [ - 39.2818422399688, - -6.804884685465061 - ], - [ - 39.28184158489463, - -6.804910067132295 - ], - [ - 39.281821768102056, - -6.805041729452175 - ], - [ - 39.28176132590204, - -6.805084810716516 - ], - [ - 39.28170218244199, - -6.805103501195034 - ], - [ - 39.28169704386786, - -6.805132894893024 - ], - [ - 39.28180979330479, - -6.805157268210907 - ], - [ - 39.2818006803067, - -6.805121275486576 - ], - [ - 39.28181210517936, - -6.80511328337132 - ], - [ - 39.28183808958605, - -6.805147777380223 - ], - [ - 39.28182612681898, - -6.805160810113123 - ], - [ - 39.281869847721794, - -6.805173622049742 - ], - [ - 39.28190633249704, - -6.805434077918824 - ], - [ - 39.281867601400805, - -6.805514017050015 - ], - [ - 39.28192556234148, - -6.805708808637539 - ], - [ - 39.282005671645834, - -6.807691339680273 - ], - [ - 39.28199943605757, - -6.807706273438956 - ], - [ - 39.28195533204774, - -6.807710185868851 - ], - [ - 39.28197796547748, - -6.807757364213221 - ], - [ - 39.28200883331969, - -6.807773047276546 - ], - [ - 39.282023019900635, - -6.808121846172164 - ], - [ - 39.282018245286956, - -6.808137714081887 - ], - [ - 39.28195634628146, - -6.808155853630331 - ], - [ - 39.28199944963618, - -6.808297352946989 - ], - [ - 39.28195181634511, - -6.80831680607628 - ], - [ - 39.28198882234564, - -6.808428329895218 - ], - [ - 39.282024499646646, - -6.808439547675201 - ], - [ - 39.2820369889843, - -6.808470622842016 - ], - [ - 39.282085070245415, - -6.80965589504642 - ], - [ - 39.28206930768178, - -6.809724680899688 - ], - [ - 39.28197604437582, - -6.809748107963618 - ], - [ - 39.28203658454973, - -6.809955641744993 - ], - [ - 39.28196678950842, - -6.809989174710164 - ], - [ - 39.28200076660283, - -6.809985427370294 - ], - [ - 39.282019456410715, - -6.810031452338205 - ], - [ - 39.28204972047887, - -6.810016433350921 - ], - [ - 39.28210025147508, - -6.810051116848933 - ], - [ - 39.28208152179033, - -6.810068511252877 - ], - [ - 39.28205676485107, - -6.810065686630296 - ], - [ - 39.28204318497603, - -6.810045394638353 - ], - [ - 39.28202652462141, - -6.810054127516999 - ], - [ - 39.28211105481988, - -6.810310955878061 - ], - [ - 39.28213177566899, - -6.810811361103773 - ], - [ - 39.2821320509615, - -6.811580168174703 - ], - [ - 39.28196212984445, - -6.81161380520041 - ], - [ - 39.28196324177067, - -6.811640766913107 - ], - [ - 39.28192745818485, - -6.811648558462478 - ], - [ - 39.28212985550575, - -6.812080389087699 - ], - [ - 39.282133235128924, - -6.812183754967635 - ], - [ - 39.28213536630495, - -6.814779570405246 - ], - [ - 39.28197188899595, - -6.814791111511188 - ], - [ - 39.28197562893217, - -6.814835126510185 - ], - [ - 39.28191559123218, - -6.814843893517796 - ], - [ - 39.28193291303407, - -6.814921201695186 - ], - [ - 39.2819237543314, - -6.81493645195515 - ], - [ - 39.28204752072457, - -6.815465796642168 - ], - [ - 39.28203191583694, - -6.81547995693839 - ], - [ - 39.28213668715259, - -6.815839528151637 - ], - [ - 39.282139961134924, - -6.82031295339354 - ], - [ - 39.28212156668364, - -6.820323535094702 - ], - [ - 39.282083452011165, - -6.820429480387951 - ], - [ - 39.2820979897894, - -6.820427505393686 - ], - [ - 39.28210159597546, - -6.820388271297486 - ], - [ - 39.28213848063579, - -6.820389430950556 - ], - [ - 39.28214024571535, - -6.820799338145754 - ], - [ - 39.28210969828819, - -6.82171079200005 - ], - [ - 39.28195130955397, - -6.821742318527214 - ], - [ - 39.2819621025051, - -6.821764513094872 - ], - [ - 39.2820301392614, - -6.821789494080819 - ], - [ - 39.28210146084555, - -6.821945651724311 - ], - [ - 39.28207040706704, - -6.822893035225293 - ], - [ - 39.28202470254904, - -6.822934919676698 - ], - [ - 39.28202109729184, - -6.822974817458986 - ], - [ - 39.28200660408125, - -6.822974998241254 - ], - [ - 39.28206129036098, - -6.823007414800348 - ], - [ - 39.28206592657398, - -6.82302820749804 - ], - [ - 39.28202064253523, - -6.824380481803342 - ], - [ - 39.281981544474924, - -6.824398479223031 - ], - [ - 39.28198457446479, - -6.824409215900801 - ], - [ - 39.282007489634466, - -6.824391763586223 - ], - [ - 39.28202004299919, - -6.824405229294226 - ], - [ - 39.28201771588613, - -6.824465696907729 - ], - [ - 39.2819989849681, - -6.824470675281921 - ], - [ - 39.28201551904975, - -6.82454063096336 - ], - [ - 39.281970927484814, - -6.82587821531825 - ], - [ - 39.28195816743914, - -6.825889196972958 - ], - [ - 39.281549414229346, - -6.825907201573767 - ], - [ - 39.281536280695086, - -6.825977236091125 - ], - [ - 39.28151348717576, - -6.825989987664138 - ], - [ - 39.281654833408055, - -6.826537198414719 - ], - [ - 39.281616883627095, - -6.826665413771542 - ], - [ - 39.28166851162509, - -6.826681587515675 - ], - [ - 39.28170780397164, - -6.826934383460437 - ], - [ - 39.28164411603734, - -6.827064614531757 - ], - [ - 39.28157087941187, - -6.827067306789635 - ], - [ - 39.28158180421901, - -6.827250179303952 - ], - [ - 39.281652062880944, - -6.827212345943851 - ], - [ - 39.281661122188204, - -6.827254722874545 - ], - [ - 39.28186998371223, - -6.827299194884757 - ], - [ - 39.281879687390074, - -6.827327728734536 - ], - [ - 39.28192180185399, - -6.827334112070436 - ], - [ - 39.28182547653564, - -6.830240875372596 - ], - [ - 39.281807154293304, - -6.830374916457267 - ], - [ - 39.28171581577605, - -6.830474463144125 - ], - [ - 39.280358440001955, - -6.831172974478872 - ], - [ - 39.27736178031955, - -6.832006509610587 - ], - [ - 39.27676315251181, - -6.832050277630079 - ], - [ - 39.26584625859982, - -6.832170132981521 - ], - [ - 39.249536580127696, - -6.832021721810229 - ], - [ - 39.24950165016739, - -6.831993443914228 - ], - [ - 39.24941999314367, - -6.831844309519701 - ], - [ - 39.249113885000924, - -6.830281768837318 - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "42f235-labels", + "properties": { + "label:description": "Geojson building labels for scene 42f235", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - [ - 39.282098666051674, - -6.82047613815803 - ], - [ - 39.28209567183957, - -6.820469231737571 - ], - [ - 39.2820671143494, - -6.820462273624464 - ], - [ - 39.28207043911302, - -6.820476154654143 - ], - [ - 39.282098666051674, - -6.82047613815803 - ] + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 38011 + } + ] + } ], - [ - [ - 39.28167616089308, - -6.805130308068077 - ], - [ - 39.28167391746429, - -6.805111703716632 - ], - [ - 39.28164656952175, - -6.805120400817218 - ], - [ - 39.28164943969814, - -6.805125077344592 - ], - [ - 39.28167616089308, - -6.805130308068077 - ] - ] - ], - [ - [ - [ - 39.281860334465605, - -6.805146602602473 - ], - [ - 39.281870004608614, - -6.80515718683268 - ], - [ - 39.28185880151722, - -6.805166623631171 - ], - [ - 39.28185067602336, - -6.805156082059399 - ], - [ - 39.281860334465605, - -6.805146602602473 - ] + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ], - [ - [ - [ - 39.281777446400945, - -6.805088153206213 - ], - [ - 39.281790280725005, - -6.805110130150037 - ], - [ - 39.28177175352243, - -6.805119220116509 - ], - [ - 39.28176463641958, - -6.805107930094916 - ], - [ - 39.281777446400945, - -6.805088153206213 - ] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 39.249113885000924, + -6.830281768837318 + ], + [ + 39.24884161123377, + -6.828887292275093 + ], + [ + 39.24871852862772, + -6.826210688475486 + ], + [ + 39.24873780887967, + -6.826104043462706 + ], + [ + 39.24871518124407, + -6.826095666463732 + ], + [ + 39.24876108061556, + -6.825838559040912 + ], + [ + 39.248745334613886, + -6.825824622762926 + ], + [ + 39.24883372959313, + -6.825108372989363 + ], + [ + 39.24881466651106, + -6.825100724860026 + ], + [ + 39.248823648081135, + -6.825021263999036 + ], + [ + 39.24878829196981, + -6.824964024555612 + ], + [ + 39.24875880383777, + -6.823975764750708 + ], + [ + 39.248614822687124, + -6.823955191386981 + ], + [ + 39.24852566077328, + -6.822015378803604 + ], + [ + 39.24847267302938, + -6.817448633758215 + ], + [ + 39.24852805358732, + -6.817447536777786 + ], + [ + 39.24852785202801, + -6.817334983971412 + ], + [ + 39.24851981536032, + -6.817052344829385 + ], + [ + 39.24850867395349, + -6.817040674531265 + ], + [ + 39.248462246290046, + -6.816355584534947 + ], + [ + 39.2484489815814, + -6.814858967526715 + ], + [ + 39.24857555480819, + -6.812582295545642 + ], + [ + 39.249003427800226, + -6.81250520642257 + ], + [ + 39.24898887783278, + -6.81234399560838 + ], + [ + 39.24896866449714, + -6.812339800430215 + ], + [ + 39.248924971349844, + -6.812068805181685 + ], + [ + 39.24880864683033, + -6.812098011765998 + ], + [ + 39.248778859104355, + -6.812074785411178 + ], + [ + 39.248719320462236, + -6.812093089498146 + ], + [ + 39.24861449191581, + -6.811914273108745 + ], + [ + 39.24889679656208, + -6.807117076678235 + ], + [ + 39.24890514165142, + -6.807103387095611 + ], + [ + 39.24895350334043, + -6.807098209917111 + ], + [ + 39.24890327231322, + -6.80700753713003 + ], + [ + 39.24907475678098, + -6.804094158740673 + ], + [ + 39.24930614048839, + -6.802705018401825 + ], + [ + 39.25081787969558, + -6.801411369325037 + ], + [ + 39.251008675447466, + -6.801297890493637 + ], + [ + 39.25320499806219, + -6.800774456159943 + ], + [ + 39.25874764808896, + -6.800811619113268 + ], + [ + 39.258786073608775, + -6.800825135486226 + ], + [ + 39.25879893597007, + -6.800813295767783 + ], + [ + 39.25895579718065, + -6.800866120225621 + ], + [ + 39.258974085499354, + -6.800814994369178 + ], + [ + 39.25902908759272, + -6.800813440425365 + ], + [ + 39.260730218645, + -6.800825015408699 + ], + [ + 39.260776088828045, + -6.800843742362178 + ], + [ + 39.26079119762641, + -6.800826328419392 + ], + [ + 39.26081964802796, + -6.80082562450859 + ], + [ + 39.26279103974784, + -6.800839018382041 + ], + [ + 39.26282408195466, + -6.800853484934448 + ], + [ + 39.26283135889312, + -6.800839827584873 + ], + [ + 39.262880469121384, + -6.800839627101004 + ], + [ + 39.2674255850371, + -6.800870658566417 + ], + [ + 39.27802431978168, + -6.801019804497031 + ], + [ + 39.28109921288827, + -6.801626688409457 + ], + [ + 39.28154897125957, + -6.802640253929808 + ], + [ + 39.281838163359225, + -6.803552989812799 + ], + [ + 39.28188220517211, + -6.804654362572848 + ], + [ + 39.28174842361446, + -6.804677677457344 + ], + [ + 39.2818422399688, + -6.804884685465061 + ], + [ + 39.28184158489463, + -6.804910067132295 + ], + [ + 39.281821768102056, + -6.805041729452175 + ], + [ + 39.28176132590204, + -6.805084810716516 + ], + [ + 39.28170218244199, + -6.805103501195034 + ], + [ + 39.28169704386786, + -6.805132894893024 + ], + [ + 39.28180979330479, + -6.805157268210907 + ], + [ + 39.2818006803067, + -6.805121275486576 + ], + [ + 39.28181210517936, + -6.80511328337132 + ], + [ + 39.28183808958605, + -6.805147777380223 + ], + [ + 39.28182612681898, + -6.805160810113123 + ], + [ + 39.281869847721794, + -6.805173622049742 + ], + [ + 39.28190633249704, + -6.805434077918824 + ], + [ + 39.281867601400805, + -6.805514017050015 + ], + [ + 39.28192556234148, + -6.805708808637539 + ], + [ + 39.282005671645834, + -6.807691339680273 + ], + [ + 39.28199943605757, + -6.807706273438956 + ], + [ + 39.28195533204774, + -6.807710185868851 + ], + [ + 39.28197796547748, + -6.807757364213221 + ], + [ + 39.28200883331969, + -6.807773047276546 + ], + [ + 39.282023019900635, + -6.808121846172164 + ], + [ + 39.282018245286956, + -6.808137714081887 + ], + [ + 39.28195634628146, + -6.808155853630331 + ], + [ + 39.28199944963618, + -6.808297352946989 + ], + [ + 39.28195181634511, + -6.80831680607628 + ], + [ + 39.28198882234564, + -6.808428329895218 + ], + [ + 39.282024499646646, + -6.808439547675201 + ], + [ + 39.2820369889843, + -6.808470622842016 + ], + [ + 39.282085070245415, + -6.80965589504642 + ], + [ + 39.28206930768178, + -6.809724680899688 + ], + [ + 39.28197604437582, + -6.809748107963618 + ], + [ + 39.28203658454973, + -6.809955641744993 + ], + [ + 39.28196678950842, + -6.809989174710164 + ], + [ + 39.28200076660283, + -6.809985427370294 + ], + [ + 39.282019456410715, + -6.810031452338205 + ], + [ + 39.28204972047887, + -6.810016433350921 + ], + [ + 39.28210025147508, + -6.810051116848933 + ], + [ + 39.28208152179033, + -6.810068511252877 + ], + [ + 39.28205676485107, + -6.810065686630296 + ], + [ + 39.28204318497603, + -6.810045394638353 + ], + [ + 39.28202652462141, + -6.810054127516999 + ], + [ + 39.28211105481988, + -6.810310955878061 + ], + [ + 39.28213177566899, + -6.810811361103773 + ], + [ + 39.2821320509615, + -6.811580168174703 + ], + [ + 39.28196212984445, + -6.81161380520041 + ], + [ + 39.28196324177067, + -6.811640766913107 + ], + [ + 39.28192745818485, + -6.811648558462478 + ], + [ + 39.28212985550575, + -6.812080389087699 + ], + [ + 39.282133235128924, + -6.812183754967635 + ], + [ + 39.28213536630495, + -6.814779570405246 + ], + [ + 39.28197188899595, + -6.814791111511188 + ], + [ + 39.28197562893217, + -6.814835126510185 + ], + [ + 39.28191559123218, + -6.814843893517796 + ], + [ + 39.28193291303407, + -6.814921201695186 + ], + [ + 39.2819237543314, + -6.81493645195515 + ], + [ + 39.28204752072457, + -6.815465796642168 + ], + [ + 39.28203191583694, + -6.81547995693839 + ], + [ + 39.28213668715259, + -6.815839528151637 + ], + [ + 39.282139961134924, + -6.82031295339354 + ], + [ + 39.28212156668364, + -6.820323535094702 + ], + [ + 39.282083452011165, + -6.820429480387951 + ], + [ + 39.2820979897894, + -6.820427505393686 + ], + [ + 39.28210159597546, + -6.820388271297486 + ], + [ + 39.28213848063579, + -6.820389430950556 + ], + [ + 39.28214024571535, + -6.820799338145754 + ], + [ + 39.28210969828819, + -6.82171079200005 + ], + [ + 39.28195130955397, + -6.821742318527214 + ], + [ + 39.2819621025051, + -6.821764513094872 + ], + [ + 39.2820301392614, + -6.821789494080819 + ], + [ + 39.28210146084555, + -6.821945651724311 + ], + [ + 39.28207040706704, + -6.822893035225293 + ], + [ + 39.28202470254904, + -6.822934919676698 + ], + [ + 39.28202109729184, + -6.822974817458986 + ], + [ + 39.28200660408125, + -6.822974998241254 + ], + [ + 39.28206129036098, + -6.823007414800348 + ], + [ + 39.28206592657398, + -6.82302820749804 + ], + [ + 39.28202064253523, + -6.824380481803342 + ], + [ + 39.281981544474924, + -6.824398479223031 + ], + [ + 39.28198457446479, + -6.824409215900801 + ], + [ + 39.282007489634466, + -6.824391763586223 + ], + [ + 39.28202004299919, + -6.824405229294226 + ], + [ + 39.28201771588613, + -6.824465696907729 + ], + [ + 39.2819989849681, + -6.824470675281921 + ], + [ + 39.28201551904975, + -6.82454063096336 + ], + [ + 39.281970927484814, + -6.82587821531825 + ], + [ + 39.28195816743914, + -6.825889196972958 + ], + [ + 39.281549414229346, + -6.825907201573767 + ], + [ + 39.281536280695086, + -6.825977236091125 + ], + [ + 39.28151348717576, + -6.825989987664138 + ], + [ + 39.281654833408055, + -6.826537198414719 + ], + [ + 39.281616883627095, + -6.826665413771542 + ], + [ + 39.28166851162509, + -6.826681587515675 + ], + [ + 39.28170780397164, + -6.826934383460437 + ], + [ + 39.28164411603734, + -6.827064614531757 + ], + [ + 39.28157087941187, + -6.827067306789635 + ], + [ + 39.28158180421901, + -6.827250179303952 + ], + [ + 39.281652062880944, + -6.827212345943851 + ], + [ + 39.281661122188204, + -6.827254722874545 + ], + [ + 39.28186998371223, + -6.827299194884757 + ], + [ + 39.281879687390074, + -6.827327728734536 + ], + [ + 39.28192180185399, + -6.827334112070436 + ], + [ + 39.28182547653564, + -6.830240875372596 + ], + [ + 39.281807154293304, + -6.830374916457267 + ], + [ + 39.28171581577605, + -6.830474463144125 + ], + [ + 39.280358440001955, + -6.831172974478872 + ], + [ + 39.27736178031955, + -6.832006509610587 + ], + [ + 39.27676315251181, + -6.832050277630079 + ], + [ + 39.26584625859982, + -6.832170132981521 + ], + [ + 39.249536580127696, + -6.832021721810229 + ], + [ + 39.24950165016739, + -6.831993443914228 + ], + [ + 39.24941999314367, + -6.831844309519701 + ], + [ + 39.249113885000924, + -6.830281768837318 + ] + ], + [ + [ + 39.282098666051674, + -6.82047613815803 + ], + [ + 39.28209567183957, + -6.820469231737571 + ], + [ + 39.2820671143494, + -6.820462273624464 + ], + [ + 39.28207043911302, + -6.820476154654143 + ], + [ + 39.282098666051674, + -6.82047613815803 + ] + ], + [ + [ + 39.28167616089308, + -6.805130308068077 + ], + [ + 39.28167391746429, + -6.805111703716632 + ], + [ + 39.28164656952175, + -6.805120400817218 + ], + [ + 39.28164943969814, + -6.805125077344592 + ], + [ + 39.28167616089308, + -6.805130308068077 + ] + ] + ], + [ + [ + [ + 39.281860334465605, + -6.805146602602473 + ], + [ + 39.281870004608614, + -6.80515718683268 + ], + [ + 39.28185880151722, + -6.805166623631171 + ], + [ + 39.28185067602336, + -6.805156082059399 + ], + [ + 39.281860334465605, + -6.805146602602473 + ] + ] + ], + [ + [ + [ + 39.281777446400945, + -6.805088153206213 + ], + [ + 39.281790280725005, + -6.805110130150037 + ], + [ + 39.28177175352243, + -6.805119220116509 + ], + [ + 39.28176463641958, + -6.805107930094916 + ], + [ + 39.281777446400945, + -6.805088153206213 + ] + ] + ] ] - ] - ] - }, - "bbox": [ - 39.2484489815814, - -6.832170132981521, - 39.28214024571535, - -6.800774407651622 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/42f235-labels/42f235.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/42f235-labels/42f235.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.2484489815814, + -6.832170132981521, + 39.28214024571535, + -6.800774407651622 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json b/tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json index 6c2e4997b..18ffed606 100644 --- a/tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json +++ b/tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json @@ -1,830 +1,831 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "42f235", - "properties": { - "area": "dar", - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z" - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - 39.249113885000924, - -6.830281768837318 - ], - [ - 39.24884161123377, - -6.828887292275093 - ], - [ - 39.24871852862772, - -6.826210688475486 - ], - [ - 39.24873780887967, - -6.826104043462706 - ], - [ - 39.24871518124407, - -6.826095666463732 - ], - [ - 39.24876108061556, - -6.825838559040912 - ], - [ - 39.248745334613886, - -6.825824622762926 - ], - [ - 39.24883372959313, - -6.825108372989363 - ], - [ - 39.24881466651106, - -6.825100724860026 - ], - [ - 39.248823648081135, - -6.825021263999036 - ], - [ - 39.24878829196981, - -6.824964024555612 - ], - [ - 39.24875880383777, - -6.823975764750708 - ], - [ - 39.248614822687124, - -6.823955191386981 - ], - [ - 39.24852566077328, - -6.822015378803604 - ], - [ - 39.24847267302938, - -6.817448633758215 - ], - [ - 39.24852805358732, - -6.817447536777786 - ], - [ - 39.24852785202801, - -6.817334983971412 - ], - [ - 39.24851981536032, - -6.817052344829385 - ], - [ - 39.24850867395349, - -6.817040674531265 - ], - [ - 39.248462246290046, - -6.816355584534947 - ], - [ - 39.2484489815814, - -6.814858967526715 - ], - [ - 39.24857555480819, - -6.812582295545642 - ], - [ - 39.249003427800226, - -6.81250520642257 - ], - [ - 39.24898887783278, - -6.81234399560838 - ], - [ - 39.24896866449714, - -6.812339800430215 - ], - [ - 39.248924971349844, - -6.812068805181685 - ], - [ - 39.24880864683033, - -6.812098011765998 - ], - [ - 39.248778859104355, - -6.812074785411178 - ], - [ - 39.248719320462236, - -6.812093089498146 - ], - [ - 39.24861449191581, - -6.811914273108745 - ], - [ - 39.24889679656208, - -6.807117076678235 - ], - [ - 39.24890514165142, - -6.807103387095611 - ], - [ - 39.24895350334043, - -6.807098209917111 - ], - [ - 39.24890327231322, - -6.80700753713003 - ], - [ - 39.24907475678098, - -6.804094158740673 - ], - [ - 39.24930614048839, - -6.802705018401825 - ], - [ - 39.25081787969558, - -6.801411369325037 - ], - [ - 39.251008675447466, - -6.801297890493637 - ], - [ - 39.25320499806219, - -6.800774456159943 - ], - [ - 39.25874764808896, - -6.800811619113268 - ], - [ - 39.258786073608775, - -6.800825135486226 - ], - [ - 39.25879893597007, - -6.800813295767783 - ], - [ - 39.25895579718065, - -6.800866120225621 - ], - [ - 39.258974085499354, - -6.800814994369178 - ], - [ - 39.25902908759272, - -6.800813440425365 - ], - [ - 39.260730218645, - -6.800825015408699 - ], - [ - 39.260776088828045, - -6.800843742362178 - ], - [ - 39.26079119762641, - -6.800826328419392 - ], - [ - 39.26081964802796, - -6.80082562450859 - ], - [ - 39.26279103974784, - -6.800839018382041 - ], - [ - 39.26282408195466, - -6.800853484934448 - ], - [ - 39.26283135889312, - -6.800839827584873 - ], - [ - 39.262880469121384, - -6.800839627101004 - ], - [ - 39.2674255850371, - -6.800870658566417 - ], - [ - 39.27802431978168, - -6.801019804497031 - ], - [ - 39.28109921288827, - -6.801626688409457 - ], - [ - 39.28154897125957, - -6.802640253929808 - ], - [ - 39.281838163359225, - -6.803552989812799 - ], - [ - 39.28188220517211, - -6.804654362572848 - ], - [ - 39.28174842361446, - -6.804677677457344 - ], - [ - 39.2818422399688, - -6.804884685465061 - ], - [ - 39.28184158489463, - -6.804910067132295 - ], - [ - 39.281821768102056, - -6.805041729452175 - ], - [ - 39.28176132590204, - -6.805084810716516 - ], - [ - 39.28170218244199, - -6.805103501195034 - ], - [ - 39.28169704386786, - -6.805132894893024 - ], - [ - 39.28180979330479, - -6.805157268210907 - ], - [ - 39.2818006803067, - -6.805121275486576 - ], - [ - 39.28181210517936, - -6.80511328337132 - ], - [ - 39.28183808958605, - -6.805147777380223 - ], - [ - 39.28182612681898, - -6.805160810113123 - ], - [ - 39.281869847721794, - -6.805173622049742 - ], - [ - 39.28190633249704, - -6.805434077918824 - ], - [ - 39.281867601400805, - -6.805514017050015 - ], - [ - 39.28192556234148, - -6.805708808637539 - ], - [ - 39.282005671645834, - -6.807691339680273 - ], - [ - 39.28199943605757, - -6.807706273438956 - ], - [ - 39.28195533204774, - -6.807710185868851 - ], - [ - 39.28197796547748, - -6.807757364213221 - ], - [ - 39.28200883331969, - -6.807773047276546 - ], - [ - 39.282023019900635, - -6.808121846172164 - ], - [ - 39.282018245286956, - -6.808137714081887 - ], - [ - 39.28195634628146, - -6.808155853630331 - ], - [ - 39.28199944963618, - -6.808297352946989 - ], - [ - 39.28195181634511, - -6.80831680607628 - ], - [ - 39.28198882234564, - -6.808428329895218 - ], - [ - 39.282024499646646, - -6.808439547675201 - ], - [ - 39.2820369889843, - -6.808470622842016 - ], - [ - 39.282085070245415, - -6.80965589504642 - ], - [ - 39.28206930768178, - -6.809724680899688 - ], - [ - 39.28197604437582, - -6.809748107963618 - ], - [ - 39.28203658454973, - -6.809955641744993 - ], - [ - 39.28196678950842, - -6.809989174710164 - ], - [ - 39.28200076660283, - -6.809985427370294 - ], - [ - 39.282019456410715, - -6.810031452338205 - ], - [ - 39.28204972047887, - -6.810016433350921 - ], - [ - 39.28210025147508, - -6.810051116848933 - ], - [ - 39.28208152179033, - -6.810068511252877 - ], - [ - 39.28205676485107, - -6.810065686630296 - ], - [ - 39.28204318497603, - -6.810045394638353 - ], - [ - 39.28202652462141, - -6.810054127516999 - ], - [ - 39.28211105481988, - -6.810310955878061 - ], - [ - 39.28213177566899, - -6.810811361103773 - ], - [ - 39.2821320509615, - -6.811580168174703 - ], - [ - 39.28196212984445, - -6.81161380520041 - ], - [ - 39.28196324177067, - -6.811640766913107 - ], - [ - 39.28192745818485, - -6.811648558462478 - ], - [ - 39.28212985550575, - -6.812080389087699 - ], - [ - 39.282133235128924, - -6.812183754967635 - ], - [ - 39.28213536630495, - -6.814779570405246 - ], - [ - 39.28197188899595, - -6.814791111511188 - ], - [ - 39.28197562893217, - -6.814835126510185 - ], - [ - 39.28191559123218, - -6.814843893517796 - ], - [ - 39.28193291303407, - -6.814921201695186 - ], - [ - 39.2819237543314, - -6.81493645195515 - ], - [ - 39.28204752072457, - -6.815465796642168 - ], - [ - 39.28203191583694, - -6.81547995693839 - ], - [ - 39.28213668715259, - -6.815839528151637 - ], - [ - 39.282139961134924, - -6.82031295339354 - ], - [ - 39.28212156668364, - -6.820323535094702 - ], - [ - 39.282083452011165, - -6.820429480387951 - ], - [ - 39.2820979897894, - -6.820427505393686 - ], - [ - 39.28210159597546, - -6.820388271297486 - ], - [ - 39.28213848063579, - -6.820389430950556 - ], - [ - 39.28214024571535, - -6.820799338145754 - ], - [ - 39.28210969828819, - -6.82171079200005 - ], - [ - 39.28195130955397, - -6.821742318527214 - ], - [ - 39.2819621025051, - -6.821764513094872 - ], - [ - 39.2820301392614, - -6.821789494080819 - ], - [ - 39.28210146084555, - -6.821945651724311 - ], - [ - 39.28207040706704, - -6.822893035225293 - ], - [ - 39.28202470254904, - -6.822934919676698 - ], - [ - 39.28202109729184, - -6.822974817458986 - ], - [ - 39.28200660408125, - -6.822974998241254 - ], - [ - 39.28206129036098, - -6.823007414800348 - ], - [ - 39.28206592657398, - -6.82302820749804 - ], - [ - 39.28202064253523, - -6.824380481803342 - ], - [ - 39.281981544474924, - -6.824398479223031 - ], - [ - 39.28198457446479, - -6.824409215900801 - ], - [ - 39.282007489634466, - -6.824391763586223 - ], - [ - 39.28202004299919, - -6.824405229294226 - ], - [ - 39.28201771588613, - -6.824465696907729 - ], - [ - 39.2819989849681, - -6.824470675281921 - ], - [ - 39.28201551904975, - -6.82454063096336 - ], - [ - 39.281970927484814, - -6.82587821531825 - ], - [ - 39.28195816743914, - -6.825889196972958 - ], - [ - 39.281549414229346, - -6.825907201573767 - ], - [ - 39.281536280695086, - -6.825977236091125 - ], - [ - 39.28151348717576, - -6.825989987664138 - ], - [ - 39.281654833408055, - -6.826537198414719 - ], - [ - 39.281616883627095, - -6.826665413771542 - ], - [ - 39.28166851162509, - -6.826681587515675 - ], - [ - 39.28170780397164, - -6.826934383460437 - ], - [ - 39.28164411603734, - -6.827064614531757 - ], - [ - 39.28157087941187, - -6.827067306789635 - ], - [ - 39.28158180421901, - -6.827250179303952 - ], - [ - 39.281652062880944, - -6.827212345943851 - ], - [ - 39.281661122188204, - -6.827254722874545 - ], - [ - 39.28186998371223, - -6.827299194884757 - ], - [ - 39.281879687390074, - -6.827327728734536 - ], - [ - 39.28192180185399, - -6.827334112070436 - ], - [ - 39.28182547653564, - -6.830240875372596 - ], - [ - 39.281807154293304, - -6.830374916457267 - ], - [ - 39.28171581577605, - -6.830474463144125 - ], - [ - 39.280358440001955, - -6.831172974478872 - ], - [ - 39.27736178031955, - -6.832006509610587 - ], - [ - 39.27676315251181, - -6.832050277630079 - ], - [ - 39.26584625859982, - -6.832170132981521 - ], - [ - 39.249536580127696, - -6.832021721810229 - ], - [ - 39.24950165016739, - -6.831993443914228 - ], - [ - 39.24941999314367, - -6.831844309519701 - ], - [ - 39.249113885000924, - -6.830281768837318 - ] - ], - [ - [ - 39.282098666051674, - -6.82047613815803 - ], - [ - 39.28209567183957, - -6.820469231737571 - ], - [ - 39.2820671143494, - -6.820462273624464 - ], - [ - 39.28207043911302, - -6.820476154654143 - ], - [ - 39.282098666051674, - -6.82047613815803 - ] - ], - [ - [ - 39.28167616089308, - -6.805130308068077 - ], - [ - 39.28167391746429, - -6.805111703716632 - ], - [ - 39.28164656952175, - -6.805120400817218 - ], - [ - 39.28164943969814, - -6.805125077344592 - ], - [ - 39.28167616089308, - -6.805130308068077 - ] - ] - ], - [ - [ - [ - 39.281860334465605, - -6.805146602602473 - ], - [ - 39.281870004608614, - -6.80515718683268 - ], - [ - 39.28185880151722, - -6.805166623631171 - ], - [ - 39.28185067602336, - -6.805156082059399 - ], - [ - 39.281860334465605, - -6.805146602602473 - ] - ] - ], - [ - [ - [ - 39.281777446400945, - -6.805088153206213 - ], - [ - 39.281790280725005, - -6.805110130150037 - ], - [ - 39.28177175352243, - -6.805119220116509 - ], - [ - 39.28176463641958, - -6.805107930094916 - ], - [ - 39.281777446400945, - -6.805088153206213 - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "42f235", + "properties": { + "area": "dar", + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 39.249113885000924, + -6.830281768837318 + ], + [ + 39.24884161123377, + -6.828887292275093 + ], + [ + 39.24871852862772, + -6.826210688475486 + ], + [ + 39.24873780887967, + -6.826104043462706 + ], + [ + 39.24871518124407, + -6.826095666463732 + ], + [ + 39.24876108061556, + -6.825838559040912 + ], + [ + 39.248745334613886, + -6.825824622762926 + ], + [ + 39.24883372959313, + -6.825108372989363 + ], + [ + 39.24881466651106, + -6.825100724860026 + ], + [ + 39.248823648081135, + -6.825021263999036 + ], + [ + 39.24878829196981, + -6.824964024555612 + ], + [ + 39.24875880383777, + -6.823975764750708 + ], + [ + 39.248614822687124, + -6.823955191386981 + ], + [ + 39.24852566077328, + -6.822015378803604 + ], + [ + 39.24847267302938, + -6.817448633758215 + ], + [ + 39.24852805358732, + -6.817447536777786 + ], + [ + 39.24852785202801, + -6.817334983971412 + ], + [ + 39.24851981536032, + -6.817052344829385 + ], + [ + 39.24850867395349, + -6.817040674531265 + ], + [ + 39.248462246290046, + -6.816355584534947 + ], + [ + 39.2484489815814, + -6.814858967526715 + ], + [ + 39.24857555480819, + -6.812582295545642 + ], + [ + 39.249003427800226, + -6.81250520642257 + ], + [ + 39.24898887783278, + -6.81234399560838 + ], + [ + 39.24896866449714, + -6.812339800430215 + ], + [ + 39.248924971349844, + -6.812068805181685 + ], + [ + 39.24880864683033, + -6.812098011765998 + ], + [ + 39.248778859104355, + -6.812074785411178 + ], + [ + 39.248719320462236, + -6.812093089498146 + ], + [ + 39.24861449191581, + -6.811914273108745 + ], + [ + 39.24889679656208, + -6.807117076678235 + ], + [ + 39.24890514165142, + -6.807103387095611 + ], + [ + 39.24895350334043, + -6.807098209917111 + ], + [ + 39.24890327231322, + -6.80700753713003 + ], + [ + 39.24907475678098, + -6.804094158740673 + ], + [ + 39.24930614048839, + -6.802705018401825 + ], + [ + 39.25081787969558, + -6.801411369325037 + ], + [ + 39.251008675447466, + -6.801297890493637 + ], + [ + 39.25320499806219, + -6.800774456159943 + ], + [ + 39.25874764808896, + -6.800811619113268 + ], + [ + 39.258786073608775, + -6.800825135486226 + ], + [ + 39.25879893597007, + -6.800813295767783 + ], + [ + 39.25895579718065, + -6.800866120225621 + ], + [ + 39.258974085499354, + -6.800814994369178 + ], + [ + 39.25902908759272, + -6.800813440425365 + ], + [ + 39.260730218645, + -6.800825015408699 + ], + [ + 39.260776088828045, + -6.800843742362178 + ], + [ + 39.26079119762641, + -6.800826328419392 + ], + [ + 39.26081964802796, + -6.80082562450859 + ], + [ + 39.26279103974784, + -6.800839018382041 + ], + [ + 39.26282408195466, + -6.800853484934448 + ], + [ + 39.26283135889312, + -6.800839827584873 + ], + [ + 39.262880469121384, + -6.800839627101004 + ], + [ + 39.2674255850371, + -6.800870658566417 + ], + [ + 39.27802431978168, + -6.801019804497031 + ], + [ + 39.28109921288827, + -6.801626688409457 + ], + [ + 39.28154897125957, + -6.802640253929808 + ], + [ + 39.281838163359225, + -6.803552989812799 + ], + [ + 39.28188220517211, + -6.804654362572848 + ], + [ + 39.28174842361446, + -6.804677677457344 + ], + [ + 39.2818422399688, + -6.804884685465061 + ], + [ + 39.28184158489463, + -6.804910067132295 + ], + [ + 39.281821768102056, + -6.805041729452175 + ], + [ + 39.28176132590204, + -6.805084810716516 + ], + [ + 39.28170218244199, + -6.805103501195034 + ], + [ + 39.28169704386786, + -6.805132894893024 + ], + [ + 39.28180979330479, + -6.805157268210907 + ], + [ + 39.2818006803067, + -6.805121275486576 + ], + [ + 39.28181210517936, + -6.80511328337132 + ], + [ + 39.28183808958605, + -6.805147777380223 + ], + [ + 39.28182612681898, + -6.805160810113123 + ], + [ + 39.281869847721794, + -6.805173622049742 + ], + [ + 39.28190633249704, + -6.805434077918824 + ], + [ + 39.281867601400805, + -6.805514017050015 + ], + [ + 39.28192556234148, + -6.805708808637539 + ], + [ + 39.282005671645834, + -6.807691339680273 + ], + [ + 39.28199943605757, + -6.807706273438956 + ], + [ + 39.28195533204774, + -6.807710185868851 + ], + [ + 39.28197796547748, + -6.807757364213221 + ], + [ + 39.28200883331969, + -6.807773047276546 + ], + [ + 39.282023019900635, + -6.808121846172164 + ], + [ + 39.282018245286956, + -6.808137714081887 + ], + [ + 39.28195634628146, + -6.808155853630331 + ], + [ + 39.28199944963618, + -6.808297352946989 + ], + [ + 39.28195181634511, + -6.80831680607628 + ], + [ + 39.28198882234564, + -6.808428329895218 + ], + [ + 39.282024499646646, + -6.808439547675201 + ], + [ + 39.2820369889843, + -6.808470622842016 + ], + [ + 39.282085070245415, + -6.80965589504642 + ], + [ + 39.28206930768178, + -6.809724680899688 + ], + [ + 39.28197604437582, + -6.809748107963618 + ], + [ + 39.28203658454973, + -6.809955641744993 + ], + [ + 39.28196678950842, + -6.809989174710164 + ], + [ + 39.28200076660283, + -6.809985427370294 + ], + [ + 39.282019456410715, + -6.810031452338205 + ], + [ + 39.28204972047887, + -6.810016433350921 + ], + [ + 39.28210025147508, + -6.810051116848933 + ], + [ + 39.28208152179033, + -6.810068511252877 + ], + [ + 39.28205676485107, + -6.810065686630296 + ], + [ + 39.28204318497603, + -6.810045394638353 + ], + [ + 39.28202652462141, + -6.810054127516999 + ], + [ + 39.28211105481988, + -6.810310955878061 + ], + [ + 39.28213177566899, + -6.810811361103773 + ], + [ + 39.2821320509615, + -6.811580168174703 + ], + [ + 39.28196212984445, + -6.81161380520041 + ], + [ + 39.28196324177067, + -6.811640766913107 + ], + [ + 39.28192745818485, + -6.811648558462478 + ], + [ + 39.28212985550575, + -6.812080389087699 + ], + [ + 39.282133235128924, + -6.812183754967635 + ], + [ + 39.28213536630495, + -6.814779570405246 + ], + [ + 39.28197188899595, + -6.814791111511188 + ], + [ + 39.28197562893217, + -6.814835126510185 + ], + [ + 39.28191559123218, + -6.814843893517796 + ], + [ + 39.28193291303407, + -6.814921201695186 + ], + [ + 39.2819237543314, + -6.81493645195515 + ], + [ + 39.28204752072457, + -6.815465796642168 + ], + [ + 39.28203191583694, + -6.81547995693839 + ], + [ + 39.28213668715259, + -6.815839528151637 + ], + [ + 39.282139961134924, + -6.82031295339354 + ], + [ + 39.28212156668364, + -6.820323535094702 + ], + [ + 39.282083452011165, + -6.820429480387951 + ], + [ + 39.2820979897894, + -6.820427505393686 + ], + [ + 39.28210159597546, + -6.820388271297486 + ], + [ + 39.28213848063579, + -6.820389430950556 + ], + [ + 39.28214024571535, + -6.820799338145754 + ], + [ + 39.28210969828819, + -6.82171079200005 + ], + [ + 39.28195130955397, + -6.821742318527214 + ], + [ + 39.2819621025051, + -6.821764513094872 + ], + [ + 39.2820301392614, + -6.821789494080819 + ], + [ + 39.28210146084555, + -6.821945651724311 + ], + [ + 39.28207040706704, + -6.822893035225293 + ], + [ + 39.28202470254904, + -6.822934919676698 + ], + [ + 39.28202109729184, + -6.822974817458986 + ], + [ + 39.28200660408125, + -6.822974998241254 + ], + [ + 39.28206129036098, + -6.823007414800348 + ], + [ + 39.28206592657398, + -6.82302820749804 + ], + [ + 39.28202064253523, + -6.824380481803342 + ], + [ + 39.281981544474924, + -6.824398479223031 + ], + [ + 39.28198457446479, + -6.824409215900801 + ], + [ + 39.282007489634466, + -6.824391763586223 + ], + [ + 39.28202004299919, + -6.824405229294226 + ], + [ + 39.28201771588613, + -6.824465696907729 + ], + [ + 39.2819989849681, + -6.824470675281921 + ], + [ + 39.28201551904975, + -6.82454063096336 + ], + [ + 39.281970927484814, + -6.82587821531825 + ], + [ + 39.28195816743914, + -6.825889196972958 + ], + [ + 39.281549414229346, + -6.825907201573767 + ], + [ + 39.281536280695086, + -6.825977236091125 + ], + [ + 39.28151348717576, + -6.825989987664138 + ], + [ + 39.281654833408055, + -6.826537198414719 + ], + [ + 39.281616883627095, + -6.826665413771542 + ], + [ + 39.28166851162509, + -6.826681587515675 + ], + [ + 39.28170780397164, + -6.826934383460437 + ], + [ + 39.28164411603734, + -6.827064614531757 + ], + [ + 39.28157087941187, + -6.827067306789635 + ], + [ + 39.28158180421901, + -6.827250179303952 + ], + [ + 39.281652062880944, + -6.827212345943851 + ], + [ + 39.281661122188204, + -6.827254722874545 + ], + [ + 39.28186998371223, + -6.827299194884757 + ], + [ + 39.281879687390074, + -6.827327728734536 + ], + [ + 39.28192180185399, + -6.827334112070436 + ], + [ + 39.28182547653564, + -6.830240875372596 + ], + [ + 39.281807154293304, + -6.830374916457267 + ], + [ + 39.28171581577605, + -6.830474463144125 + ], + [ + 39.280358440001955, + -6.831172974478872 + ], + [ + 39.27736178031955, + -6.832006509610587 + ], + [ + 39.27676315251181, + -6.832050277630079 + ], + [ + 39.26584625859982, + -6.832170132981521 + ], + [ + 39.249536580127696, + -6.832021721810229 + ], + [ + 39.24950165016739, + -6.831993443914228 + ], + [ + 39.24941999314367, + -6.831844309519701 + ], + [ + 39.249113885000924, + -6.830281768837318 + ] + ], + [ + [ + 39.282098666051674, + -6.82047613815803 + ], + [ + 39.28209567183957, + -6.820469231737571 + ], + [ + 39.2820671143494, + -6.820462273624464 + ], + [ + 39.28207043911302, + -6.820476154654143 + ], + [ + 39.282098666051674, + -6.82047613815803 + ] + ], + [ + [ + 39.28167616089308, + -6.805130308068077 + ], + [ + 39.28167391746429, + -6.805111703716632 + ], + [ + 39.28164656952175, + -6.805120400817218 + ], + [ + 39.28164943969814, + -6.805125077344592 + ], + [ + 39.28167616089308, + -6.805130308068077 + ] + ] + ], + [ + [ + [ + 39.281860334465605, + -6.805146602602473 + ], + [ + 39.281870004608614, + -6.80515718683268 + ], + [ + 39.28185880151722, + -6.805166623631171 + ], + [ + 39.28185067602336, + -6.805156082059399 + ], + [ + 39.281860334465605, + -6.805146602602473 + ] + ] + ], + [ + [ + [ + 39.281777446400945, + -6.805088153206213 + ], + [ + 39.281790280725005, + -6.805110130150037 + ], + [ + 39.28177175352243, + -6.805119220116509 + ], + [ + 39.28176463641958, + -6.805107930094916 + ], + [ + 39.281777446400945, + -6.805088153206213 + ] + ] + ] ] - ] - ] - }, - "bbox": [ - 39.2484489815814, - -6.832170132981521, - 39.28214024571535, - -6.800774407651622 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/42f235/42f235.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/42f235/42f235.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "bbox": [ + 39.2484489815814, + -6.832170132981521, + 39.28214024571535, + -6.800774407651622 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json b/tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json index 6e9feefe1..1025e7bf8 100644 --- a/tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json @@ -1,825 +1,826 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "a017f9-labels", - "properties": { - "label:description": "Geojson building labels for scene a017f9", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 8466 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2015-04-17T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.26926014396197, - -6.778408889724252 - ], - [ - 39.26917573821576, - -6.778059433830696 - ], - [ - 39.269135899723494, - -6.777817111177557 - ], - [ - 39.26903939444476, - -6.777807075160049 - ], - [ - 39.26903081931803, - -6.777772501737724 - ], - [ - 39.26906382558145, - -6.777602227096167 - ], - [ - 39.26904873507929, - -6.777478560312792 - ], - [ - 39.26906977566508, - -6.777464896790929 - ], - [ - 39.26907206971568, - -6.777431243033367 - ], - [ - 39.26905482925254, - -6.777366237073936 - ], - [ - 39.26907664234658, - -6.777355931873772 - ], - [ - 39.26908175247129, - -6.777328474644556 - ], - [ - 39.26911153777823, - -6.7767048297691925 - ], - [ - 39.268882527014895, - -6.776678363032119 - ], - [ - 39.26893474099891, - -6.776264192380035 - ], - [ - 39.26888836161254, - -6.776253809942015 - ], - [ - 39.26890017221041, - -6.775895949943107 - ], - [ - 39.268876747767386, - -6.775880651901013 - ], - [ - 39.26887301554542, - -6.775825989281363 - ], - [ - 39.268905857415454, - -6.775201612825259 - ], - [ - 39.26894490739935, - -6.775063227577604 - ], - [ - 39.268941730886425, - -6.7746895631725605 - ], - [ - 39.26914364260038, - -6.774688448799986 - ], - [ - 39.269122361922676, - -6.774489250907138 - ], - [ - 39.26911632867811, - -6.774476515539691 - ], - [ - 39.268713642568706, - -6.7744760981296235 - ], - [ - 39.268737012604355, - -6.774398791342552 - ], - [ - 39.268732532666085, - -6.773790005571118 - ], - [ - 39.26843497013009, - -6.773735427452071 - ], - [ - 39.26795336212609, - -6.77391654782026 - ], - [ - 39.26791971568522, - -6.773724191695024 - ], - [ - 39.2673415071351, - -6.773740528442519 - ], - [ - 39.267350063052604, - -6.773399215834541 - ], - [ - 39.26723521167855, - -6.773414701179516 - ], - [ - 39.26720466086805, - -6.773248698765951 - ], - [ - 39.26678014120764, - -6.773242894087743 - ], - [ - 39.266762284676794, - -6.772986892804697 - ], - [ - 39.26675892922693, - -6.772350856460975 - ], - [ - 39.26674681765211, - -6.772255700716951 - ], - [ - 39.266689201437146, - -6.772246368011811 - ], - [ - 39.26668350788613, - -6.772191783623815 - ], - [ - 39.26672244519504, - -6.771767088705046 - ], - [ - 39.26626283690872, - -6.771780875871251 - ], - [ - 39.26625879082112, - -6.771454101013188 - ], - [ - 39.266132977817925, - -6.771472067783455 - ], - [ - 39.26611967798805, - -6.771424057777588 - ], - [ - 39.26608419604109, - -6.771424077200688 - ], - [ - 39.26577319095754, - -6.7715025578108285 - ], - [ - 39.265752052763695, - -6.771435782627501 - ], - [ - 39.265127479719204, - -6.77145467575174 - ], - [ - 39.26511907017294, - -6.771328213140658 - ], - [ - 39.26493015593419, - -6.771364468688907 - ], - [ - 39.264912911500495, - -6.771432744389225 - ], - [ - 39.26488039779026, - -6.771436738799636 - ], - [ - 39.26475100488943, - -6.771406733473938 - ], - [ - 39.2646926955789, - -6.771412615337635 - ], - [ - 39.2646606028275, - -6.771388711051411 - ], - [ - 39.26392674348407, - -6.771256584601807 - ], - [ - 39.26394389453707, - -6.771551356063654 - ], - [ - 39.263667091483114, - -6.771557919685502 - ], - [ - 39.26366183135522, - -6.771600089723924 - ], - [ - 39.263278347477964, - -6.77157318737759 - ], - [ - 39.26283023251785, - -6.771600245021459 - ], - [ - 39.262838493462255, - -6.771634543496247 - ], - [ - 39.262821747451255, - -6.771719954194481 - ], - [ - 39.26265424567296, - -6.771690993457386 - ], - [ - 39.26268995014445, - -6.771860885107266 - ], - [ - 39.26252831334852, - -6.771895873157618 - ], - [ - 39.26252880337638, - -6.7719554235439245 - ], - [ - 39.26210122775403, - -6.771977406295051 - ], - [ - 39.26143116288968, - -6.772125372485002 - ], - [ - 39.2614075982154, - -6.772141135811242 - ], - [ - 39.2613973736888, - -6.772275813955157 - ], - [ - 39.260536340561174, - -6.772228258989134 - ], - [ - 39.26053980618854, - -6.772319083364469 - ], - [ - 39.26056969675572, - -6.772373190295851 - ], - [ - 39.26060315386137, - -6.772554749985303 - ], - [ - 39.26059568903891, - -6.772595510376657 - ], - [ - 39.26001518300299, - -6.772559851743274 - ], - [ - 39.25890041510944, - -6.7725543793501695 - ], - [ - 39.258876087403834, - -6.772494197053711 - ], - [ - 39.258514479088554, - -6.772422102891361 - ], - [ - 39.258340434530666, - -6.772428219316269 - ], - [ - 39.25828032895121, - -6.77272075666584 - ], - [ - 39.25826082874752, - -6.772915188024547 - ], - [ - 39.25823949031402, - -6.772922057829271 - ], - [ - 39.2578520090327, - -6.772838102931263 - ], - [ - 39.257888798127965, - -6.77312332117879 - ], - [ - 39.25787979922622, - -6.773136270542293 - ], - [ - 39.25688475867173, - -6.773097642255739 - ], - [ - 39.25682520466523, - -6.773109721503685 - ], - [ - 39.25660478951343, - -6.773091077694639 - ], - [ - 39.255055254694156, - -6.772380238732794 - ], - [ - 39.25421275712266, - -6.771620622438099 - ], - [ - 39.25404556903243, - -6.77004589995448 - ], - [ - 39.25403029430106, - -6.769728767128998 - ], - [ - 39.25411994186844, - -6.768221861425687 - ], - [ - 39.254234107717096, - -6.767341435882211 - ], - [ - 39.25545106018141, - -6.764159936003817 - ], - [ - 39.257116110067585, - -6.76294877364334 - ], - [ - 39.25718438415638, - -6.762917093607382 - ], - [ - 39.25719972988617, - -6.762889019397921 - ], - [ - 39.257442153683776, - -6.76271321325156 - ], - [ - 39.25823278499986, - -6.762669559070356 - ], - [ - 39.25834142312088, - -6.762242194863409 - ], - [ - 39.25939647121495, - -6.762572558084997 - ], - [ - 39.25940845544633, - -6.762487777603404 - ], - [ - 39.259435774764846, - -6.762476198533605 - ], - [ - 39.26006839728735, - -6.76261436674474 - ], - [ - 39.260386403814906, - -6.762668411712047 - ], - [ - 39.260437484245585, - -6.762439496926193 - ], - [ - 39.260004647501674, - -6.762312837170598 - ], - [ - 39.2598716936977, - -6.762259959251641 - ], - [ - 39.2600401439903, - -6.761820065451443 - ], - [ - 39.26029388247496, - -6.761028721580892 - ], - [ - 39.26032420174658, - -6.761018967933076 - ], - [ - 39.26053391798681, - -6.76114659187534 - ], - [ - 39.260536816410294, - -6.760851252611107 - ], - [ - 39.260557853080336, - -6.760831034771786 - ], - [ - 39.260566844200056, - -6.76074069093421 - ], - [ - 39.26036239395784, - -6.760812697920982 - ], - [ - 39.26027824156397, - -6.760662075341912 - ], - [ - 39.261001400117976, - -6.760133550248436 - ], - [ - 39.261527087791784, - -6.759873405077786 - ], - [ - 39.26858580547449, - -6.759308696494632 - ], - [ - 39.268890992058246, - -6.759302984619273 - ], - [ - 39.268903430340835, - -6.759284771586644 - ], - [ - 39.26896542655929, - -6.759278367546962 - ], - [ - 39.271286224101196, - -6.759102343973786 - ], - [ - 39.27211963371433, - -6.759204458764863 - ], - [ - 39.27236321137411, - -6.759271479674409 - ], - [ - 39.273154184137965, - -6.7601369678318965 - ], - [ - 39.27316422844033, - -6.760700644939782 - ], - [ - 39.27314084480545, - -6.7619837448331745 - ], - [ - 39.27305675463236, - -6.762505234243227 - ], - [ - 39.273056789844496, - -6.762534967504247 - ], - [ - 39.27312507291691, - -6.762521393506317 - ], - [ - 39.27313212710874, - -6.76255601845254 - ], - [ - 39.27310309297801, - -6.764507772203796 - ], - [ - 39.27308849603215, - -6.764558304932267 - ], - [ - 39.27300138154325, - -6.764546994635472 - ], - [ - 39.273024316262344, - -6.7646614152403 - ], - [ - 39.27304589268655, - -6.7646695624275575 - ], - [ - 39.27304617892309, - -6.765101677547554 - ], - [ - 39.27308204747948, - -6.7651152272390975 - ], - [ - 39.27309139442161, - -6.765137274306364 - ], - [ - 39.273051905630446, - -6.765239264368901 - ], - [ - 39.27306448349893, - -6.765504735996792 - ], - [ - 39.27308187939778, - -6.765516334329867 - ], - [ - 39.27308560012812, - -6.765549913380341 - ], - [ - 39.27304752697193, - -6.767501671978607 - ], - [ - 39.273038935703134, - -6.767516058055352 - ], - [ - 39.27299835422545, - -6.7675217280475755 - ], - [ - 39.27299933800813, - -6.767537832177631 - ], - [ - 39.273045495033486, - -6.767541113558746 - ], - [ - 39.273050590782745, - -6.767592028459917 - ], - [ - 39.27302400267546, - -6.768534259451833 - ], - [ - 39.272968932976305, - -6.772411177910341 - ], - [ - 39.272959627433714, - -6.772630668635193 - ], - [ - 39.272905788858104, - -6.772643489838023 - ], - [ - 39.27288173837768, - -6.772739885404247 - ], - [ - 39.272882307015124, - -6.7727514232524175 - ], - [ - 39.272956057298394, - -6.772741000926849 - ], - [ - 39.272963123503956, - -6.772796709421321 - ], - [ - 39.27286982682731, - -6.778350777504887 - ], - [ - 39.27283086855745, - -6.7790374037343115 - ], - [ - 39.272701979976624, - -6.779284945399111 - ], - [ - 39.272261619559565, - -6.779665292928737 - ], - [ - 39.27210271074345, - -6.7797571049359115 - ], - [ - 39.271704543803665, - -6.779784747451681 - ], - [ - 39.27144452806939, - -6.779784849288132 - ], - [ - 39.27115738781928, - -6.779758148606385 - ], - [ - 39.27049709925575, - -6.779461996132346 - ], - [ - 39.270493082234935, - -6.779326099696981 - ], - [ - 39.2702131801902, - -6.779330763890751 - ], - [ - 39.26995991242604, - -6.779214787256136 - ], - [ - 39.2699510390659, - -6.77909635634638 - ], - [ - 39.26977275112402, - -6.779102490914896 - ], - [ - 39.26968410885647, - -6.779086412311449 - ], - [ - 39.26966426752008, - -6.77902210533535 - ], - [ - 39.269673217618006, - -6.778939901472921 - ], - [ - 39.26934578546989, - -6.778920487053642 - ], - [ - 39.26932971998377, - -6.778857872932586 - ], - [ - 39.26936566177434, - -6.778547988756392 - ], - [ - 39.2693481357915, - -6.778494457194883 - ], - [ - 39.269311412694705, - -6.778506382971034 - ], - [ - 39.2692772941224, - -6.778497070722168 - ], - [ - 39.26926014396197, - -6.778408889724252 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "a017f9-labels", + "properties": { + "label:description": "Geojson building labels for scene a017f9", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 8466 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2015-04-17T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.25403029430106, - -6.779784892393674, - 39.2731659202684, - -6.759102343973786 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.26926014396197, + -6.778408889724252 + ], + [ + 39.26917573821576, + -6.778059433830696 + ], + [ + 39.269135899723494, + -6.777817111177557 + ], + [ + 39.26903939444476, + -6.777807075160049 + ], + [ + 39.26903081931803, + -6.777772501737724 + ], + [ + 39.26906382558145, + -6.777602227096167 + ], + [ + 39.26904873507929, + -6.777478560312792 + ], + [ + 39.26906977566508, + -6.777464896790929 + ], + [ + 39.26907206971568, + -6.777431243033367 + ], + [ + 39.26905482925254, + -6.777366237073936 + ], + [ + 39.26907664234658, + -6.777355931873772 + ], + [ + 39.26908175247129, + -6.777328474644556 + ], + [ + 39.26911153777823, + -6.7767048297691925 + ], + [ + 39.268882527014895, + -6.776678363032119 + ], + [ + 39.26893474099891, + -6.776264192380035 + ], + [ + 39.26888836161254, + -6.776253809942015 + ], + [ + 39.26890017221041, + -6.775895949943107 + ], + [ + 39.268876747767386, + -6.775880651901013 + ], + [ + 39.26887301554542, + -6.775825989281363 + ], + [ + 39.268905857415454, + -6.775201612825259 + ], + [ + 39.26894490739935, + -6.775063227577604 + ], + [ + 39.268941730886425, + -6.7746895631725605 + ], + [ + 39.26914364260038, + -6.774688448799986 + ], + [ + 39.269122361922676, + -6.774489250907138 + ], + [ + 39.26911632867811, + -6.774476515539691 + ], + [ + 39.268713642568706, + -6.7744760981296235 + ], + [ + 39.268737012604355, + -6.774398791342552 + ], + [ + 39.268732532666085, + -6.773790005571118 + ], + [ + 39.26843497013009, + -6.773735427452071 + ], + [ + 39.26795336212609, + -6.77391654782026 + ], + [ + 39.26791971568522, + -6.773724191695024 + ], + [ + 39.2673415071351, + -6.773740528442519 + ], + [ + 39.267350063052604, + -6.773399215834541 + ], + [ + 39.26723521167855, + -6.773414701179516 + ], + [ + 39.26720466086805, + -6.773248698765951 + ], + [ + 39.26678014120764, + -6.773242894087743 + ], + [ + 39.266762284676794, + -6.772986892804697 + ], + [ + 39.26675892922693, + -6.772350856460975 + ], + [ + 39.26674681765211, + -6.772255700716951 + ], + [ + 39.266689201437146, + -6.772246368011811 + ], + [ + 39.26668350788613, + -6.772191783623815 + ], + [ + 39.26672244519504, + -6.771767088705046 + ], + [ + 39.26626283690872, + -6.771780875871251 + ], + [ + 39.26625879082112, + -6.771454101013188 + ], + [ + 39.266132977817925, + -6.771472067783455 + ], + [ + 39.26611967798805, + -6.771424057777588 + ], + [ + 39.26608419604109, + -6.771424077200688 + ], + [ + 39.26577319095754, + -6.7715025578108285 + ], + [ + 39.265752052763695, + -6.771435782627501 + ], + [ + 39.265127479719204, + -6.77145467575174 + ], + [ + 39.26511907017294, + -6.771328213140658 + ], + [ + 39.26493015593419, + -6.771364468688907 + ], + [ + 39.264912911500495, + -6.771432744389225 + ], + [ + 39.26488039779026, + -6.771436738799636 + ], + [ + 39.26475100488943, + -6.771406733473938 + ], + [ + 39.2646926955789, + -6.771412615337635 + ], + [ + 39.2646606028275, + -6.771388711051411 + ], + [ + 39.26392674348407, + -6.771256584601807 + ], + [ + 39.26394389453707, + -6.771551356063654 + ], + [ + 39.263667091483114, + -6.771557919685502 + ], + [ + 39.26366183135522, + -6.771600089723924 + ], + [ + 39.263278347477964, + -6.77157318737759 + ], + [ + 39.26283023251785, + -6.771600245021459 + ], + [ + 39.262838493462255, + -6.771634543496247 + ], + [ + 39.262821747451255, + -6.771719954194481 + ], + [ + 39.26265424567296, + -6.771690993457386 + ], + [ + 39.26268995014445, + -6.771860885107266 + ], + [ + 39.26252831334852, + -6.771895873157618 + ], + [ + 39.26252880337638, + -6.7719554235439245 + ], + [ + 39.26210122775403, + -6.771977406295051 + ], + [ + 39.26143116288968, + -6.772125372485002 + ], + [ + 39.2614075982154, + -6.772141135811242 + ], + [ + 39.2613973736888, + -6.772275813955157 + ], + [ + 39.260536340561174, + -6.772228258989134 + ], + [ + 39.26053980618854, + -6.772319083364469 + ], + [ + 39.26056969675572, + -6.772373190295851 + ], + [ + 39.26060315386137, + -6.772554749985303 + ], + [ + 39.26059568903891, + -6.772595510376657 + ], + [ + 39.26001518300299, + -6.772559851743274 + ], + [ + 39.25890041510944, + -6.7725543793501695 + ], + [ + 39.258876087403834, + -6.772494197053711 + ], + [ + 39.258514479088554, + -6.772422102891361 + ], + [ + 39.258340434530666, + -6.772428219316269 + ], + [ + 39.25828032895121, + -6.77272075666584 + ], + [ + 39.25826082874752, + -6.772915188024547 + ], + [ + 39.25823949031402, + -6.772922057829271 + ], + [ + 39.2578520090327, + -6.772838102931263 + ], + [ + 39.257888798127965, + -6.77312332117879 + ], + [ + 39.25787979922622, + -6.773136270542293 + ], + [ + 39.25688475867173, + -6.773097642255739 + ], + [ + 39.25682520466523, + -6.773109721503685 + ], + [ + 39.25660478951343, + -6.773091077694639 + ], + [ + 39.255055254694156, + -6.772380238732794 + ], + [ + 39.25421275712266, + -6.771620622438099 + ], + [ + 39.25404556903243, + -6.77004589995448 + ], + [ + 39.25403029430106, + -6.769728767128998 + ], + [ + 39.25411994186844, + -6.768221861425687 + ], + [ + 39.254234107717096, + -6.767341435882211 + ], + [ + 39.25545106018141, + -6.764159936003817 + ], + [ + 39.257116110067585, + -6.76294877364334 + ], + [ + 39.25718438415638, + -6.762917093607382 + ], + [ + 39.25719972988617, + -6.762889019397921 + ], + [ + 39.257442153683776, + -6.76271321325156 + ], + [ + 39.25823278499986, + -6.762669559070356 + ], + [ + 39.25834142312088, + -6.762242194863409 + ], + [ + 39.25939647121495, + -6.762572558084997 + ], + [ + 39.25940845544633, + -6.762487777603404 + ], + [ + 39.259435774764846, + -6.762476198533605 + ], + [ + 39.26006839728735, + -6.76261436674474 + ], + [ + 39.260386403814906, + -6.762668411712047 + ], + [ + 39.260437484245585, + -6.762439496926193 + ], + [ + 39.260004647501674, + -6.762312837170598 + ], + [ + 39.2598716936977, + -6.762259959251641 + ], + [ + 39.2600401439903, + -6.761820065451443 + ], + [ + 39.26029388247496, + -6.761028721580892 + ], + [ + 39.26032420174658, + -6.761018967933076 + ], + [ + 39.26053391798681, + -6.76114659187534 + ], + [ + 39.260536816410294, + -6.760851252611107 + ], + [ + 39.260557853080336, + -6.760831034771786 + ], + [ + 39.260566844200056, + -6.76074069093421 + ], + [ + 39.26036239395784, + -6.760812697920982 + ], + [ + 39.26027824156397, + -6.760662075341912 + ], + [ + 39.261001400117976, + -6.760133550248436 + ], + [ + 39.261527087791784, + -6.759873405077786 + ], + [ + 39.26858580547449, + -6.759308696494632 + ], + [ + 39.268890992058246, + -6.759302984619273 + ], + [ + 39.268903430340835, + -6.759284771586644 + ], + [ + 39.26896542655929, + -6.759278367546962 + ], + [ + 39.271286224101196, + -6.759102343973786 + ], + [ + 39.27211963371433, + -6.759204458764863 + ], + [ + 39.27236321137411, + -6.759271479674409 + ], + [ + 39.273154184137965, + -6.7601369678318965 + ], + [ + 39.27316422844033, + -6.760700644939782 + ], + [ + 39.27314084480545, + -6.7619837448331745 + ], + [ + 39.27305675463236, + -6.762505234243227 + ], + [ + 39.273056789844496, + -6.762534967504247 + ], + [ + 39.27312507291691, + -6.762521393506317 + ], + [ + 39.27313212710874, + -6.76255601845254 + ], + [ + 39.27310309297801, + -6.764507772203796 + ], + [ + 39.27308849603215, + -6.764558304932267 + ], + [ + 39.27300138154325, + -6.764546994635472 + ], + [ + 39.273024316262344, + -6.7646614152403 + ], + [ + 39.27304589268655, + -6.7646695624275575 + ], + [ + 39.27304617892309, + -6.765101677547554 + ], + [ + 39.27308204747948, + -6.7651152272390975 + ], + [ + 39.27309139442161, + -6.765137274306364 + ], + [ + 39.273051905630446, + -6.765239264368901 + ], + [ + 39.27306448349893, + -6.765504735996792 + ], + [ + 39.27308187939778, + -6.765516334329867 + ], + [ + 39.27308560012812, + -6.765549913380341 + ], + [ + 39.27304752697193, + -6.767501671978607 + ], + [ + 39.273038935703134, + -6.767516058055352 + ], + [ + 39.27299835422545, + -6.7675217280475755 + ], + [ + 39.27299933800813, + -6.767537832177631 + ], + [ + 39.273045495033486, + -6.767541113558746 + ], + [ + 39.273050590782745, + -6.767592028459917 + ], + [ + 39.27302400267546, + -6.768534259451833 + ], + [ + 39.272968932976305, + -6.772411177910341 + ], + [ + 39.272959627433714, + -6.772630668635193 + ], + [ + 39.272905788858104, + -6.772643489838023 + ], + [ + 39.27288173837768, + -6.772739885404247 + ], + [ + 39.272882307015124, + -6.7727514232524175 + ], + [ + 39.272956057298394, + -6.772741000926849 + ], + [ + 39.272963123503956, + -6.772796709421321 + ], + [ + 39.27286982682731, + -6.778350777504887 + ], + [ + 39.27283086855745, + -6.7790374037343115 + ], + [ + 39.272701979976624, + -6.779284945399111 + ], + [ + 39.272261619559565, + -6.779665292928737 + ], + [ + 39.27210271074345, + -6.7797571049359115 + ], + [ + 39.271704543803665, + -6.779784747451681 + ], + [ + 39.27144452806939, + -6.779784849288132 + ], + [ + 39.27115738781928, + -6.779758148606385 + ], + [ + 39.27049709925575, + -6.779461996132346 + ], + [ + 39.270493082234935, + -6.779326099696981 + ], + [ + 39.2702131801902, + -6.779330763890751 + ], + [ + 39.26995991242604, + -6.779214787256136 + ], + [ + 39.2699510390659, + -6.77909635634638 + ], + [ + 39.26977275112402, + -6.779102490914896 + ], + [ + 39.26968410885647, + -6.779086412311449 + ], + [ + 39.26966426752008, + -6.77902210533535 + ], + [ + 39.269673217618006, + -6.778939901472921 + ], + [ + 39.26934578546989, + -6.778920487053642 + ], + [ + 39.26932971998377, + -6.778857872932586 + ], + [ + 39.26936566177434, + -6.778547988756392 + ], + [ + 39.2693481357915, + -6.778494457194883 + ], + [ + 39.269311412694705, + -6.778506382971034 + ], + [ + 39.2692772941224, + -6.778497070722168 + ], + [ + 39.26926014396197, + -6.778408889724252 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/a017f9-labels/a017f9.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/a017f9-labels/a017f9.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.25403029430106, + -6.779784892393674, + 39.2731659202684, + -6.759102343973786 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json b/tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json index 9f5b115a0..be22c5b66 100644 --- a/tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json +++ b/tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json @@ -1,800 +1,801 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "a017f9", - "properties": { - "area": "dar", - "license": "CC-BY-4.0", - "datetime": "2015-04-17T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.26926014396197, - -6.778408889724252 - ], - [ - 39.26917573821576, - -6.778059433830696 - ], - [ - 39.269135899723494, - -6.777817111177557 - ], - [ - 39.26903939444476, - -6.777807075160049 - ], - [ - 39.26903081931803, - -6.777772501737724 - ], - [ - 39.26906382558145, - -6.777602227096167 - ], - [ - 39.26904873507929, - -6.777478560312792 - ], - [ - 39.26906977566508, - -6.777464896790929 - ], - [ - 39.26907206971568, - -6.777431243033367 - ], - [ - 39.26905482925254, - -6.777366237073936 - ], - [ - 39.26907664234658, - -6.777355931873772 - ], - [ - 39.26908175247129, - -6.777328474644556 - ], - [ - 39.26911153777823, - -6.7767048297691925 - ], - [ - 39.268882527014895, - -6.776678363032119 - ], - [ - 39.26893474099891, - -6.776264192380035 - ], - [ - 39.26888836161254, - -6.776253809942015 - ], - [ - 39.26890017221041, - -6.775895949943107 - ], - [ - 39.268876747767386, - -6.775880651901013 - ], - [ - 39.26887301554542, - -6.775825989281363 - ], - [ - 39.268905857415454, - -6.775201612825259 - ], - [ - 39.26894490739935, - -6.775063227577604 - ], - [ - 39.268941730886425, - -6.7746895631725605 - ], - [ - 39.26914364260038, - -6.774688448799986 - ], - [ - 39.269122361922676, - -6.774489250907138 - ], - [ - 39.26911632867811, - -6.774476515539691 - ], - [ - 39.268713642568706, - -6.7744760981296235 - ], - [ - 39.268737012604355, - -6.774398791342552 - ], - [ - 39.268732532666085, - -6.773790005571118 - ], - [ - 39.26843497013009, - -6.773735427452071 - ], - [ - 39.26795336212609, - -6.77391654782026 - ], - [ - 39.26791971568522, - -6.773724191695024 - ], - [ - 39.2673415071351, - -6.773740528442519 - ], - [ - 39.267350063052604, - -6.773399215834541 - ], - [ - 39.26723521167855, - -6.773414701179516 - ], - [ - 39.26720466086805, - -6.773248698765951 - ], - [ - 39.26678014120764, - -6.773242894087743 - ], - [ - 39.266762284676794, - -6.772986892804697 - ], - [ - 39.26675892922693, - -6.772350856460975 - ], - [ - 39.26674681765211, - -6.772255700716951 - ], - [ - 39.266689201437146, - -6.772246368011811 - ], - [ - 39.26668350788613, - -6.772191783623815 - ], - [ - 39.26672244519504, - -6.771767088705046 - ], - [ - 39.26626283690872, - -6.771780875871251 - ], - [ - 39.26625879082112, - -6.771454101013188 - ], - [ - 39.266132977817925, - -6.771472067783455 - ], - [ - 39.26611967798805, - -6.771424057777588 - ], - [ - 39.26608419604109, - -6.771424077200688 - ], - [ - 39.26577319095754, - -6.7715025578108285 - ], - [ - 39.265752052763695, - -6.771435782627501 - ], - [ - 39.265127479719204, - -6.77145467575174 - ], - [ - 39.26511907017294, - -6.771328213140658 - ], - [ - 39.26493015593419, - -6.771364468688907 - ], - [ - 39.264912911500495, - -6.771432744389225 - ], - [ - 39.26488039779026, - -6.771436738799636 - ], - [ - 39.26475100488943, - -6.771406733473938 - ], - [ - 39.2646926955789, - -6.771412615337635 - ], - [ - 39.2646606028275, - -6.771388711051411 - ], - [ - 39.26392674348407, - -6.771256584601807 - ], - [ - 39.26394389453707, - -6.771551356063654 - ], - [ - 39.263667091483114, - -6.771557919685502 - ], - [ - 39.26366183135522, - -6.771600089723924 - ], - [ - 39.263278347477964, - -6.77157318737759 - ], - [ - 39.26283023251785, - -6.771600245021459 - ], - [ - 39.262838493462255, - -6.771634543496247 - ], - [ - 39.262821747451255, - -6.771719954194481 - ], - [ - 39.26265424567296, - -6.771690993457386 - ], - [ - 39.26268995014445, - -6.771860885107266 - ], - [ - 39.26252831334852, - -6.771895873157618 - ], - [ - 39.26252880337638, - -6.7719554235439245 - ], - [ - 39.26210122775403, - -6.771977406295051 - ], - [ - 39.26143116288968, - -6.772125372485002 - ], - [ - 39.2614075982154, - -6.772141135811242 - ], - [ - 39.2613973736888, - -6.772275813955157 - ], - [ - 39.260536340561174, - -6.772228258989134 - ], - [ - 39.26053980618854, - -6.772319083364469 - ], - [ - 39.26056969675572, - -6.772373190295851 - ], - [ - 39.26060315386137, - -6.772554749985303 - ], - [ - 39.26059568903891, - -6.772595510376657 - ], - [ - 39.26001518300299, - -6.772559851743274 - ], - [ - 39.25890041510944, - -6.7725543793501695 - ], - [ - 39.258876087403834, - -6.772494197053711 - ], - [ - 39.258514479088554, - -6.772422102891361 - ], - [ - 39.258340434530666, - -6.772428219316269 - ], - [ - 39.25828032895121, - -6.77272075666584 - ], - [ - 39.25826082874752, - -6.772915188024547 - ], - [ - 39.25823949031402, - -6.772922057829271 - ], - [ - 39.2578520090327, - -6.772838102931263 - ], - [ - 39.257888798127965, - -6.77312332117879 - ], - [ - 39.25787979922622, - -6.773136270542293 - ], - [ - 39.25688475867173, - -6.773097642255739 - ], - [ - 39.25682520466523, - -6.773109721503685 - ], - [ - 39.25660478951343, - -6.773091077694639 - ], - [ - 39.255055254694156, - -6.772380238732794 - ], - [ - 39.25421275712266, - -6.771620622438099 - ], - [ - 39.25404556903243, - -6.77004589995448 - ], - [ - 39.25403029430106, - -6.769728767128998 - ], - [ - 39.25411994186844, - -6.768221861425687 - ], - [ - 39.254234107717096, - -6.767341435882211 - ], - [ - 39.25545106018141, - -6.764159936003817 - ], - [ - 39.257116110067585, - -6.76294877364334 - ], - [ - 39.25718438415638, - -6.762917093607382 - ], - [ - 39.25719972988617, - -6.762889019397921 - ], - [ - 39.257442153683776, - -6.76271321325156 - ], - [ - 39.25823278499986, - -6.762669559070356 - ], - [ - 39.25834142312088, - -6.762242194863409 - ], - [ - 39.25939647121495, - -6.762572558084997 - ], - [ - 39.25940845544633, - -6.762487777603404 - ], - [ - 39.259435774764846, - -6.762476198533605 - ], - [ - 39.26006839728735, - -6.76261436674474 - ], - [ - 39.260386403814906, - -6.762668411712047 - ], - [ - 39.260437484245585, - -6.762439496926193 - ], - [ - 39.260004647501674, - -6.762312837170598 - ], - [ - 39.2598716936977, - -6.762259959251641 - ], - [ - 39.2600401439903, - -6.761820065451443 - ], - [ - 39.26029388247496, - -6.761028721580892 - ], - [ - 39.26032420174658, - -6.761018967933076 - ], - [ - 39.26053391798681, - -6.76114659187534 - ], - [ - 39.260536816410294, - -6.760851252611107 - ], - [ - 39.260557853080336, - -6.760831034771786 - ], - [ - 39.260566844200056, - -6.76074069093421 - ], - [ - 39.26036239395784, - -6.760812697920982 - ], - [ - 39.26027824156397, - -6.760662075341912 - ], - [ - 39.261001400117976, - -6.760133550248436 - ], - [ - 39.261527087791784, - -6.759873405077786 - ], - [ - 39.26858580547449, - -6.759308696494632 - ], - [ - 39.268890992058246, - -6.759302984619273 - ], - [ - 39.268903430340835, - -6.759284771586644 - ], - [ - 39.26896542655929, - -6.759278367546962 - ], - [ - 39.271286224101196, - -6.759102343973786 - ], - [ - 39.27211963371433, - -6.759204458764863 - ], - [ - 39.27236321137411, - -6.759271479674409 - ], - [ - 39.273154184137965, - -6.7601369678318965 - ], - [ - 39.27316422844033, - -6.760700644939782 - ], - [ - 39.27314084480545, - -6.7619837448331745 - ], - [ - 39.27305675463236, - -6.762505234243227 - ], - [ - 39.273056789844496, - -6.762534967504247 - ], - [ - 39.27312507291691, - -6.762521393506317 - ], - [ - 39.27313212710874, - -6.76255601845254 - ], - [ - 39.27310309297801, - -6.764507772203796 - ], - [ - 39.27308849603215, - -6.764558304932267 - ], - [ - 39.27300138154325, - -6.764546994635472 - ], - [ - 39.273024316262344, - -6.7646614152403 - ], - [ - 39.27304589268655, - -6.7646695624275575 - ], - [ - 39.27304617892309, - -6.765101677547554 - ], - [ - 39.27308204747948, - -6.7651152272390975 - ], - [ - 39.27309139442161, - -6.765137274306364 - ], - [ - 39.273051905630446, - -6.765239264368901 - ], - [ - 39.27306448349893, - -6.765504735996792 - ], - [ - 39.27308187939778, - -6.765516334329867 - ], - [ - 39.27308560012812, - -6.765549913380341 - ], - [ - 39.27304752697193, - -6.767501671978607 - ], - [ - 39.273038935703134, - -6.767516058055352 - ], - [ - 39.27299835422545, - -6.7675217280475755 - ], - [ - 39.27299933800813, - -6.767537832177631 - ], - [ - 39.273045495033486, - -6.767541113558746 - ], - [ - 39.273050590782745, - -6.767592028459917 - ], - [ - 39.27302400267546, - -6.768534259451833 - ], - [ - 39.272968932976305, - -6.772411177910341 - ], - [ - 39.272959627433714, - -6.772630668635193 - ], - [ - 39.272905788858104, - -6.772643489838023 - ], - [ - 39.27288173837768, - -6.772739885404247 - ], - [ - 39.272882307015124, - -6.7727514232524175 - ], - [ - 39.272956057298394, - -6.772741000926849 - ], - [ - 39.272963123503956, - -6.772796709421321 - ], - [ - 39.27286982682731, - -6.778350777504887 - ], - [ - 39.27283086855745, - -6.7790374037343115 - ], - [ - 39.272701979976624, - -6.779284945399111 - ], - [ - 39.272261619559565, - -6.779665292928737 - ], - [ - 39.27210271074345, - -6.7797571049359115 - ], - [ - 39.271704543803665, - -6.779784747451681 - ], - [ - 39.27144452806939, - -6.779784849288132 - ], - [ - 39.27115738781928, - -6.779758148606385 - ], - [ - 39.27049709925575, - -6.779461996132346 - ], - [ - 39.270493082234935, - -6.779326099696981 - ], - [ - 39.2702131801902, - -6.779330763890751 - ], - [ - 39.26995991242604, - -6.779214787256136 - ], - [ - 39.2699510390659, - -6.77909635634638 - ], - [ - 39.26977275112402, - -6.779102490914896 - ], - [ - 39.26968410885647, - -6.779086412311449 - ], - [ - 39.26966426752008, - -6.77902210533535 - ], - [ - 39.269673217618006, - -6.778939901472921 - ], - [ - 39.26934578546989, - -6.778920487053642 - ], - [ - 39.26932971998377, - -6.778857872932586 - ], - [ - 39.26936566177434, - -6.778547988756392 - ], - [ - 39.2693481357915, - -6.778494457194883 - ], - [ - 39.269311412694705, - -6.778506382971034 - ], - [ - 39.2692772941224, - -6.778497070722168 - ], - [ - 39.26926014396197, - -6.778408889724252 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.25403029430106, - -6.779784892393674, - 39.2731659202684, - -6.759102343973786 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "a017f9", + "properties": { + "area": "dar", + "license": "CC-BY-4.0", + "datetime": "2015-04-17T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.26926014396197, + -6.778408889724252 + ], + [ + 39.26917573821576, + -6.778059433830696 + ], + [ + 39.269135899723494, + -6.777817111177557 + ], + [ + 39.26903939444476, + -6.777807075160049 + ], + [ + 39.26903081931803, + -6.777772501737724 + ], + [ + 39.26906382558145, + -6.777602227096167 + ], + [ + 39.26904873507929, + -6.777478560312792 + ], + [ + 39.26906977566508, + -6.777464896790929 + ], + [ + 39.26907206971568, + -6.777431243033367 + ], + [ + 39.26905482925254, + -6.777366237073936 + ], + [ + 39.26907664234658, + -6.777355931873772 + ], + [ + 39.26908175247129, + -6.777328474644556 + ], + [ + 39.26911153777823, + -6.7767048297691925 + ], + [ + 39.268882527014895, + -6.776678363032119 + ], + [ + 39.26893474099891, + -6.776264192380035 + ], + [ + 39.26888836161254, + -6.776253809942015 + ], + [ + 39.26890017221041, + -6.775895949943107 + ], + [ + 39.268876747767386, + -6.775880651901013 + ], + [ + 39.26887301554542, + -6.775825989281363 + ], + [ + 39.268905857415454, + -6.775201612825259 + ], + [ + 39.26894490739935, + -6.775063227577604 + ], + [ + 39.268941730886425, + -6.7746895631725605 + ], + [ + 39.26914364260038, + -6.774688448799986 + ], + [ + 39.269122361922676, + -6.774489250907138 + ], + [ + 39.26911632867811, + -6.774476515539691 + ], + [ + 39.268713642568706, + -6.7744760981296235 + ], + [ + 39.268737012604355, + -6.774398791342552 + ], + [ + 39.268732532666085, + -6.773790005571118 + ], + [ + 39.26843497013009, + -6.773735427452071 + ], + [ + 39.26795336212609, + -6.77391654782026 + ], + [ + 39.26791971568522, + -6.773724191695024 + ], + [ + 39.2673415071351, + -6.773740528442519 + ], + [ + 39.267350063052604, + -6.773399215834541 + ], + [ + 39.26723521167855, + -6.773414701179516 + ], + [ + 39.26720466086805, + -6.773248698765951 + ], + [ + 39.26678014120764, + -6.773242894087743 + ], + [ + 39.266762284676794, + -6.772986892804697 + ], + [ + 39.26675892922693, + -6.772350856460975 + ], + [ + 39.26674681765211, + -6.772255700716951 + ], + [ + 39.266689201437146, + -6.772246368011811 + ], + [ + 39.26668350788613, + -6.772191783623815 + ], + [ + 39.26672244519504, + -6.771767088705046 + ], + [ + 39.26626283690872, + -6.771780875871251 + ], + [ + 39.26625879082112, + -6.771454101013188 + ], + [ + 39.266132977817925, + -6.771472067783455 + ], + [ + 39.26611967798805, + -6.771424057777588 + ], + [ + 39.26608419604109, + -6.771424077200688 + ], + [ + 39.26577319095754, + -6.7715025578108285 + ], + [ + 39.265752052763695, + -6.771435782627501 + ], + [ + 39.265127479719204, + -6.77145467575174 + ], + [ + 39.26511907017294, + -6.771328213140658 + ], + [ + 39.26493015593419, + -6.771364468688907 + ], + [ + 39.264912911500495, + -6.771432744389225 + ], + [ + 39.26488039779026, + -6.771436738799636 + ], + [ + 39.26475100488943, + -6.771406733473938 + ], + [ + 39.2646926955789, + -6.771412615337635 + ], + [ + 39.2646606028275, + -6.771388711051411 + ], + [ + 39.26392674348407, + -6.771256584601807 + ], + [ + 39.26394389453707, + -6.771551356063654 + ], + [ + 39.263667091483114, + -6.771557919685502 + ], + [ + 39.26366183135522, + -6.771600089723924 + ], + [ + 39.263278347477964, + -6.77157318737759 + ], + [ + 39.26283023251785, + -6.771600245021459 + ], + [ + 39.262838493462255, + -6.771634543496247 + ], + [ + 39.262821747451255, + -6.771719954194481 + ], + [ + 39.26265424567296, + -6.771690993457386 + ], + [ + 39.26268995014445, + -6.771860885107266 + ], + [ + 39.26252831334852, + -6.771895873157618 + ], + [ + 39.26252880337638, + -6.7719554235439245 + ], + [ + 39.26210122775403, + -6.771977406295051 + ], + [ + 39.26143116288968, + -6.772125372485002 + ], + [ + 39.2614075982154, + -6.772141135811242 + ], + [ + 39.2613973736888, + -6.772275813955157 + ], + [ + 39.260536340561174, + -6.772228258989134 + ], + [ + 39.26053980618854, + -6.772319083364469 + ], + [ + 39.26056969675572, + -6.772373190295851 + ], + [ + 39.26060315386137, + -6.772554749985303 + ], + [ + 39.26059568903891, + -6.772595510376657 + ], + [ + 39.26001518300299, + -6.772559851743274 + ], + [ + 39.25890041510944, + -6.7725543793501695 + ], + [ + 39.258876087403834, + -6.772494197053711 + ], + [ + 39.258514479088554, + -6.772422102891361 + ], + [ + 39.258340434530666, + -6.772428219316269 + ], + [ + 39.25828032895121, + -6.77272075666584 + ], + [ + 39.25826082874752, + -6.772915188024547 + ], + [ + 39.25823949031402, + -6.772922057829271 + ], + [ + 39.2578520090327, + -6.772838102931263 + ], + [ + 39.257888798127965, + -6.77312332117879 + ], + [ + 39.25787979922622, + -6.773136270542293 + ], + [ + 39.25688475867173, + -6.773097642255739 + ], + [ + 39.25682520466523, + -6.773109721503685 + ], + [ + 39.25660478951343, + -6.773091077694639 + ], + [ + 39.255055254694156, + -6.772380238732794 + ], + [ + 39.25421275712266, + -6.771620622438099 + ], + [ + 39.25404556903243, + -6.77004589995448 + ], + [ + 39.25403029430106, + -6.769728767128998 + ], + [ + 39.25411994186844, + -6.768221861425687 + ], + [ + 39.254234107717096, + -6.767341435882211 + ], + [ + 39.25545106018141, + -6.764159936003817 + ], + [ + 39.257116110067585, + -6.76294877364334 + ], + [ + 39.25718438415638, + -6.762917093607382 + ], + [ + 39.25719972988617, + -6.762889019397921 + ], + [ + 39.257442153683776, + -6.76271321325156 + ], + [ + 39.25823278499986, + -6.762669559070356 + ], + [ + 39.25834142312088, + -6.762242194863409 + ], + [ + 39.25939647121495, + -6.762572558084997 + ], + [ + 39.25940845544633, + -6.762487777603404 + ], + [ + 39.259435774764846, + -6.762476198533605 + ], + [ + 39.26006839728735, + -6.76261436674474 + ], + [ + 39.260386403814906, + -6.762668411712047 + ], + [ + 39.260437484245585, + -6.762439496926193 + ], + [ + 39.260004647501674, + -6.762312837170598 + ], + [ + 39.2598716936977, + -6.762259959251641 + ], + [ + 39.2600401439903, + -6.761820065451443 + ], + [ + 39.26029388247496, + -6.761028721580892 + ], + [ + 39.26032420174658, + -6.761018967933076 + ], + [ + 39.26053391798681, + -6.76114659187534 + ], + [ + 39.260536816410294, + -6.760851252611107 + ], + [ + 39.260557853080336, + -6.760831034771786 + ], + [ + 39.260566844200056, + -6.76074069093421 + ], + [ + 39.26036239395784, + -6.760812697920982 + ], + [ + 39.26027824156397, + -6.760662075341912 + ], + [ + 39.261001400117976, + -6.760133550248436 + ], + [ + 39.261527087791784, + -6.759873405077786 + ], + [ + 39.26858580547449, + -6.759308696494632 + ], + [ + 39.268890992058246, + -6.759302984619273 + ], + [ + 39.268903430340835, + -6.759284771586644 + ], + [ + 39.26896542655929, + -6.759278367546962 + ], + [ + 39.271286224101196, + -6.759102343973786 + ], + [ + 39.27211963371433, + -6.759204458764863 + ], + [ + 39.27236321137411, + -6.759271479674409 + ], + [ + 39.273154184137965, + -6.7601369678318965 + ], + [ + 39.27316422844033, + -6.760700644939782 + ], + [ + 39.27314084480545, + -6.7619837448331745 + ], + [ + 39.27305675463236, + -6.762505234243227 + ], + [ + 39.273056789844496, + -6.762534967504247 + ], + [ + 39.27312507291691, + -6.762521393506317 + ], + [ + 39.27313212710874, + -6.76255601845254 + ], + [ + 39.27310309297801, + -6.764507772203796 + ], + [ + 39.27308849603215, + -6.764558304932267 + ], + [ + 39.27300138154325, + -6.764546994635472 + ], + [ + 39.273024316262344, + -6.7646614152403 + ], + [ + 39.27304589268655, + -6.7646695624275575 + ], + [ + 39.27304617892309, + -6.765101677547554 + ], + [ + 39.27308204747948, + -6.7651152272390975 + ], + [ + 39.27309139442161, + -6.765137274306364 + ], + [ + 39.273051905630446, + -6.765239264368901 + ], + [ + 39.27306448349893, + -6.765504735996792 + ], + [ + 39.27308187939778, + -6.765516334329867 + ], + [ + 39.27308560012812, + -6.765549913380341 + ], + [ + 39.27304752697193, + -6.767501671978607 + ], + [ + 39.273038935703134, + -6.767516058055352 + ], + [ + 39.27299835422545, + -6.7675217280475755 + ], + [ + 39.27299933800813, + -6.767537832177631 + ], + [ + 39.273045495033486, + -6.767541113558746 + ], + [ + 39.273050590782745, + -6.767592028459917 + ], + [ + 39.27302400267546, + -6.768534259451833 + ], + [ + 39.272968932976305, + -6.772411177910341 + ], + [ + 39.272959627433714, + -6.772630668635193 + ], + [ + 39.272905788858104, + -6.772643489838023 + ], + [ + 39.27288173837768, + -6.772739885404247 + ], + [ + 39.272882307015124, + -6.7727514232524175 + ], + [ + 39.272956057298394, + -6.772741000926849 + ], + [ + 39.272963123503956, + -6.772796709421321 + ], + [ + 39.27286982682731, + -6.778350777504887 + ], + [ + 39.27283086855745, + -6.7790374037343115 + ], + [ + 39.272701979976624, + -6.779284945399111 + ], + [ + 39.272261619559565, + -6.779665292928737 + ], + [ + 39.27210271074345, + -6.7797571049359115 + ], + [ + 39.271704543803665, + -6.779784747451681 + ], + [ + 39.27144452806939, + -6.779784849288132 + ], + [ + 39.27115738781928, + -6.779758148606385 + ], + [ + 39.27049709925575, + -6.779461996132346 + ], + [ + 39.270493082234935, + -6.779326099696981 + ], + [ + 39.2702131801902, + -6.779330763890751 + ], + [ + 39.26995991242604, + -6.779214787256136 + ], + [ + 39.2699510390659, + -6.77909635634638 + ], + [ + 39.26977275112402, + -6.779102490914896 + ], + [ + 39.26968410885647, + -6.779086412311449 + ], + [ + 39.26966426752008, + -6.77902210533535 + ], + [ + 39.269673217618006, + -6.778939901472921 + ], + [ + 39.26934578546989, + -6.778920487053642 + ], + [ + 39.26932971998377, + -6.778857872932586 + ], + [ + 39.26936566177434, + -6.778547988756392 + ], + [ + 39.2693481357915, + -6.778494457194883 + ], + [ + 39.269311412694705, + -6.778506382971034 + ], + [ + 39.2692772941224, + -6.778497070722168 + ], + [ + 39.26926014396197, + -6.778408889724252 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/a017f9/a017f9.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/a017f9/a017f9.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + 39.25403029430106, + -6.779784892393674, + 39.2731659202684, + -6.759102343973786 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json b/tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json index f536383c7..80c95e106 100644 --- a/tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json @@ -1,181 +1,182 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "b15fce-labels", - "properties": { - "label:description": "Geojson building labels for scene b15fce", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 23458 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2015-05-20T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "b15fce-labels", + "properties": { + "label:description": "Geojson building labels for scene b15fce", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 23458 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2015-05-20T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.23203578164141, - -6.79095224397089 - ], - [ - 39.23213878048826, - -6.789102523233581 - ], - [ - 39.23424736448953, - -6.786630199691261 - ], - [ - 39.24240426507967, - -6.787815561113139 - ], - [ - 39.24837432725425, - -6.789223358831122 - ], - [ - 39.24999459439558, - -6.792382403376687 - ], - [ - 39.250160911880975, - -6.792733513382334 - ], - [ - 39.25165161397758, - -6.799519472339792 - ], - [ - 39.252150559748735, - -6.805433243902031 - ], - [ - 39.25215431131969, - -6.80558550517296 - ], - [ - 39.252130818230775, - -6.805626361276197 - ], - [ - 39.252021891878556, - -6.805745910544025 - ], - [ - 39.251481491584926, - -6.806062768768616 - ], - [ - 39.25140290770081, - -6.806085857110386 - ], - [ - 39.246437022609506, - -6.805537709438442 - ], - [ - 39.24639412436282, - -6.805529495943441 - ], - [ - 39.24634489238881, - -6.805491830951268 - ], - [ - 39.245924545758825, - -6.805105022778037 - ], - [ - 39.245898468007404, - -6.805097690645329 - ], - [ - 39.23970175749278, - -6.804381219357365 - ], - [ - 39.23893051436519, - -6.80327787406404 - ], - [ - 39.23890949996164, - -6.803265948061028 - ], - [ - 39.23450774412018, - -6.80280156767769 - ], - [ - 39.23210317956967, - -6.801549954393777 - ], - [ - 39.231930285079976, - -6.801280005215021 - ], - [ - 39.23158398015406, - -6.799288999676232 - ], - [ - 39.23203578164141, - -6.79095224397089 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.23203578164141, + -6.79095224397089 + ], + [ + 39.23213878048826, + -6.789102523233581 + ], + [ + 39.23424736448953, + -6.786630199691261 + ], + [ + 39.24240426507967, + -6.787815561113139 + ], + [ + 39.24837432725425, + -6.789223358831122 + ], + [ + 39.24999459439558, + -6.792382403376687 + ], + [ + 39.250160911880975, + -6.792733513382334 + ], + [ + 39.25165161397758, + -6.799519472339792 + ], + [ + 39.252150559748735, + -6.805433243902031 + ], + [ + 39.25215431131969, + -6.80558550517296 + ], + [ + 39.252130818230775, + -6.805626361276197 + ], + [ + 39.252021891878556, + -6.805745910544025 + ], + [ + 39.251481491584926, + -6.806062768768616 + ], + [ + 39.25140290770081, + -6.806085857110386 + ], + [ + 39.246437022609506, + -6.805537709438442 + ], + [ + 39.24639412436282, + -6.805529495943441 + ], + [ + 39.24634489238881, + -6.805491830951268 + ], + [ + 39.245924545758825, + -6.805105022778037 + ], + [ + 39.245898468007404, + -6.805097690645329 + ], + [ + 39.23970175749278, + -6.804381219357365 + ], + [ + 39.23893051436519, + -6.80327787406404 + ], + [ + 39.23890949996164, + -6.803265948061028 + ], + [ + 39.23450774412018, + -6.80280156767769 + ], + [ + 39.23210317956967, + -6.801549954393777 + ], + [ + 39.231930285079976, + -6.801280005215021 + ], + [ + 39.23158398015406, + -6.799288999676232 + ], + [ + 39.23203578164141, + -6.79095224397089 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.231583919750726, - -6.806085900208043, - 39.25215435443532, - -6.786630132161881 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/b15fce-labels/b15fce.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/b15fce-labels/b15fce.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.231583919750726, + -6.806085900208043, + 39.25215435443532, + -6.786630132161881 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json b/tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json index 4d0a0af71..9920d57d4 100644 --- a/tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json +++ b/tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json @@ -1,156 +1,157 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "b15fce", - "properties": { - "area": "dar", - "license": "CC-BY-4.0", - "datetime": "2015-05-20T00:00:00Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.23203578164141, - -6.79095224397089 - ], - [ - 39.23213878048826, - -6.789102523233581 - ], - [ - 39.23424736448953, - -6.786630199691261 - ], - [ - 39.24240426507967, - -6.787815561113139 - ], - [ - 39.24837432725425, - -6.789223358831122 - ], - [ - 39.24999459439558, - -6.792382403376687 - ], - [ - 39.250160911880975, - -6.792733513382334 - ], - [ - 39.25165161397758, - -6.799519472339792 - ], - [ - 39.252150559748735, - -6.805433243902031 - ], - [ - 39.25215431131969, - -6.80558550517296 - ], - [ - 39.252130818230775, - -6.805626361276197 - ], - [ - 39.252021891878556, - -6.805745910544025 - ], - [ - 39.251481491584926, - -6.806062768768616 - ], - [ - 39.25140290770081, - -6.806085857110386 - ], - [ - 39.246437022609506, - -6.805537709438442 - ], - [ - 39.24639412436282, - -6.805529495943441 - ], - [ - 39.24634489238881, - -6.805491830951268 - ], - [ - 39.245924545758825, - -6.805105022778037 - ], - [ - 39.245898468007404, - -6.805097690645329 - ], - [ - 39.23970175749278, - -6.804381219357365 - ], - [ - 39.23893051436519, - -6.80327787406404 - ], - [ - 39.23890949996164, - -6.803265948061028 - ], - [ - 39.23450774412018, - -6.80280156767769 - ], - [ - 39.23210317956967, - -6.801549954393777 - ], - [ - 39.231930285079976, - -6.801280005215021 - ], - [ - 39.23158398015406, - -6.799288999676232 - ], - [ - 39.23203578164141, - -6.79095224397089 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "b15fce", + "properties": { + "area": "dar", + "license": "CC-BY-4.0", + "datetime": "2015-05-20T00:00:00Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.23203578164141, + -6.79095224397089 + ], + [ + 39.23213878048826, + -6.789102523233581 + ], + [ + 39.23424736448953, + -6.786630199691261 + ], + [ + 39.24240426507967, + -6.787815561113139 + ], + [ + 39.24837432725425, + -6.789223358831122 + ], + [ + 39.24999459439558, + -6.792382403376687 + ], + [ + 39.250160911880975, + -6.792733513382334 + ], + [ + 39.25165161397758, + -6.799519472339792 + ], + [ + 39.252150559748735, + -6.805433243902031 + ], + [ + 39.25215431131969, + -6.80558550517296 + ], + [ + 39.252130818230775, + -6.805626361276197 + ], + [ + 39.252021891878556, + -6.805745910544025 + ], + [ + 39.251481491584926, + -6.806062768768616 + ], + [ + 39.25140290770081, + -6.806085857110386 + ], + [ + 39.246437022609506, + -6.805537709438442 + ], + [ + 39.24639412436282, + -6.805529495943441 + ], + [ + 39.24634489238881, + -6.805491830951268 + ], + [ + 39.245924545758825, + -6.805105022778037 + ], + [ + 39.245898468007404, + -6.805097690645329 + ], + [ + 39.23970175749278, + -6.804381219357365 + ], + [ + 39.23893051436519, + -6.80327787406404 + ], + [ + 39.23890949996164, + -6.803265948061028 + ], + [ + 39.23450774412018, + -6.80280156767769 + ], + [ + 39.23210317956967, + -6.801549954393777 + ], + [ + 39.231930285079976, + -6.801280005215021 + ], + [ + 39.23158398015406, + -6.799288999676232 + ], + [ + 39.23203578164141, + -6.79095224397089 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.231583919750726, - -6.806085900208043, - 39.25215435443532, - -6.786630132161881 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/b15fce/b15fce.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/b15fce/b15fce.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "bbox": [ + 39.231583919750726, + -6.806085900208043, + 39.25215435443532, + -6.786630132161881 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/collection.json b/tests/data-files/catalogs/test-case-4/dar/collection.json index 2b58d6a92..8048d22e1 100644 --- a/tests/data-files/catalogs/test-case-4/dar/collection.json +++ b/tests/data-files/catalogs/test-case-4/dar/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "dar", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from dar", "links": [ { @@ -74,6 +75,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -94,8 +96,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json b/tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json index 86b53fa4d..1bb93fb69 100644 --- a/tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json +++ b/tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json @@ -1,1291 +1,1292 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f883a0-labels", - "properties": { - "label:description": "Geojson building labels for scene f883a0", - "area": "dar", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 35952 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - 39.27315718647447, - -6.804030897967275 - ], - [ - 39.273165048748965, - -6.804015735580677 - ], - [ - 39.273213892047806, - -6.804006275126262 - ], - [ - 39.27322980988637, - -6.804022244653453 - ], - [ - 39.273170342425956, - -6.804047430372633 - ], - [ - 39.27315718647447, - -6.804030897967275 - ] - ] - ], - [ - [ - [ - 39.273270319050496, - -6.804022808953547 - ], - [ - 39.273288769025, - -6.803992819792749 - ], - [ - 39.27331911984259, - -6.804029517800871 - ], - [ - 39.2732773025055, - -6.804036732038151 - ], - [ - 39.273270319050496, - -6.804022808953547 - ] - ] - ], - [ - [ - [ - 39.273416845431285, - -6.803993658369023 - ], - [ - 39.273432966277255, - -6.803999383623384 - ], - [ - 39.27345314382025, - -6.803979786475935 - ], - [ - 39.27322440426283, - -6.803973842746188 - ], - [ - 39.273203984545894, - -6.804001738617233 - ], - [ - 39.27307652605739, - -6.804015326271316 - ], - [ - 39.27305748415282, - -6.804061875764365 - ], - [ - 39.27243654623383, - -6.804062182204895 - ], - [ - 39.272422506412305, - -6.804030782430953 - ], - [ - 39.27233818959957, - -6.804033592321105 - ], - [ - 39.27232684217891, - -6.804062287567047 - ], - [ - 39.27050720474849, - -6.804063308351038 - ], - [ - 39.27050003464541, - -6.804267571943514 - ], - [ - 39.26958162504325, - -6.804292771833833 - ], - [ - 39.268183769535355, - -6.804289804955942 - ], - [ - 39.26817548170555, - -6.804064606402513 - ], - [ - 39.26638040640152, - -6.804065554449549 - ], - [ - 39.26635869387405, - -6.80392326747663 - ], - [ - 39.26612966449484, - -6.803973347620189 - ], - [ - 39.2660934787131, - -6.804065755940294 - ], - [ - 39.26187296555017, - -6.804068058726235 - ], - [ - 39.26184507218486, - -6.804052028033205 - ], - [ - 39.26184066298603, - -6.804023191210663 - ], - [ - 39.26172396812868, - -6.804068139349033 - ], - [ - 39.25777183400359, - -6.804070261106554 - ], - [ - 39.257755345525474, - -6.804004808291634 - ], - [ - 39.25731554868908, - -6.804070460389402 - ], - [ - 39.256625459443185, - -6.804070870513325 - ], - [ - 39.25662348593693, - -6.80422297079218 - ], - [ - 39.256462846950114, - -6.804260546096121 - ], - [ - 39.256084987159134, - -6.804259012772288 - ], - [ - 39.256069700572326, - -6.804123376288454 - ], - [ - 39.25557123594966, - -6.804199546422531 - ], - [ - 39.25540366700173, - -6.804257868759697 - ], - [ - 39.25364580884159, - -6.804253286905217 - ], - [ - 39.25336404330564, - -6.804245528109689 - ], - [ - 39.25184949473127, - -6.804063493477143 - ], - [ - 39.25045725022109, - -6.803626776738144 - ], - [ - 39.250038582443665, - -6.80287820958753 - ], - [ - 39.25000304212214, - -6.802509337350906 - ], - [ - 39.24988415760574, - -6.799080315984306 - ], - [ - 39.24990450849855, - -6.799063247023449 - ], - [ - 39.2498947561629, - -6.798995102709657 - ], - [ - 39.24988086958492, - -6.79898550113401 - ], - [ - 39.249810881599906, - -6.796966175954005 - ], - [ - 39.24981645844359, - -6.796947177743604 - ], - [ - 39.249872411376465, - -6.796943310209003 - ], - [ - 39.249872317302916, - -6.79667664523118 - ], - [ - 39.24989594446884, - -6.796632192786874 - ], - [ - 39.2498929959209, - -6.796620487479649 - ], - [ - 39.24981140260522, - -6.796653264268834 - ], - [ - 39.249799651394305, - -6.796639604421224 - ], - [ - 39.249702848316815, - -6.793849310612861 - ], - [ - 39.249709580102994, - -6.793444194527318 - ], - [ - 39.24968269293236, - -6.793263269989816 - ], - [ - 39.24966102436161, - -6.792603766179727 - ], - [ - 39.24967954445545, - -6.791728669025114 - ], - [ - 39.24970804593678, - -6.791366446494281 - ], - [ - 39.24968767919196, - -6.791352173642583 - ], - [ - 39.24969901238331, - -6.790812445435837 - ], - [ - 39.24971718787532, - -6.790709956750732 - ], - [ - 39.24976000940216, - -6.790676190366502 - ], - [ - 39.24974335811433, - -6.79066757715095 - ], - [ - 39.24975954380845, - -6.7905720968061 - ], - [ - 39.24971030856871, - -6.790285552729824 - ], - [ - 39.24971251701262, - -6.790166259808814 - ], - [ - 39.249755177302276, - -6.79007657102466 - ], - [ - 39.24978363439594, - -6.790069338302176 - ], - [ - 39.24973059995279, - -6.789901141277205 - ], - [ - 39.249748519583505, - -6.789843130737693 - ], - [ - 39.249722462770514, - -6.789712522643217 - ], - [ - 39.249757283518974, - -6.788060421836336 - ], - [ - 39.24981581028674, - -6.788035256591358 - ], - [ - 39.2497613539383, - -6.787878110167098 - ], - [ - 39.249768572097224, - -6.787518805148783 - ], - [ - 39.25004009533414, - -6.787434103342062 - ], - [ - 39.25002896611271, - -6.787302674153842 - ], - [ - 39.25004835160351, - -6.787293592535141 - ], - [ - 39.25004462643536, - -6.787248062083075 - ], - [ - 39.249913527740446, - -6.787275621098578 - ], - [ - 39.24987623121805, - -6.787223585244646 - ], - [ - 39.2498870533108, - -6.78675947061919 - ], - [ - 39.249878668979136, - -6.786651689669421 - ], - [ - 39.249854185335714, - -6.786644521595329 - ], - [ - 39.24985427604204, - -6.786567866890232 - ], - [ - 39.249878150583925, - -6.786543683174986 - ], - [ - 39.24987767258752, - -6.786514031590554 - ], - [ - 39.24985595176083, - -6.786480788612282 - ], - [ - 39.24985427458511, - -6.786004693765936 - ], - [ - 39.249835799952045, - -6.785985440085538 - ], - [ - 39.24981894796509, - -6.785692711962167 - ], - [ - 39.249835592123326, - -6.785666857043904 - ], - [ - 39.2498132441077, - -6.785399014698585 - ], - [ - 39.24982665102962, - -6.785376346020647 - ], - [ - 39.24981460672529, - -6.785349524356427 - ], - [ - 39.24981956779118, - -6.785099760997835 - ], - [ - 39.249871024621996, - -6.783491089857915 - ], - [ - 39.24990124402507, - -6.783476958079939 - ], - [ - 39.24987648913789, - -6.783320417149405 - ], - [ - 39.24990659151731, - -6.782389387144009 - ], - [ - 39.25010086176018, - -6.782346474227426 - ], - [ - 39.2501017767606, - -6.78232629074711 - ], - [ - 39.250066934399854, - -6.782156990037679 - ], - [ - 39.24992289431197, - -6.782182852009128 - ], - [ - 39.2499138298333, - -6.782153922351434 - ], - [ - 39.25006519231678, - -6.777778404257647 - ], - [ - 39.250159011995514, - -6.775687803188281 - ], - [ - 39.250652695356244, - -6.774710612955933 - ], - [ - 39.25973527817753, - -6.774051636265581 - ], - [ - 39.26420907625315, - -6.774027485936578 - ], - [ - 39.26430505647687, - -6.774065405657963 - ], - [ - 39.26433909336156, - -6.774026490186999 - ], - [ - 39.27016488290843, - -6.773994609904983 - ], - [ - 39.27021692938301, - -6.773995269543569 - ], - [ - 39.27026644245446, - -6.774033162706626 - ], - [ - 39.27031016915434, - -6.773994066588543 - ], - [ - 39.27159183301611, - -6.773986876672367 - ], - [ - 39.27168780046295, - -6.774108785772873 - ], - [ - 39.271686763240815, - -6.774136172079665 - ], - [ - 39.27192122999216, - -6.774436870625464 - ], - [ - 39.27225338516659, - -6.773984336984419 - ], - [ - 39.27280594364432, - -6.773980646345491 - ], - [ - 39.274209296190605, - -6.774041415140496 - ], - [ - 39.27802675727596, - -6.774426374513278 - ], - [ - 39.27864214751531, - -6.774515880892984 - ], - [ - 39.280023369706576, - -6.774755350012424 - ], - [ - 39.28050916887765, - -6.775147428625842 - ], - [ - 39.28074438487075, - -6.775756768054177 - ], - [ - 39.28081527672994, - -6.776054440091325 - ], - [ - 39.28082717168011, - -6.776695908769849 - ], - [ - 39.2807657423886, - -6.776717815104587 - ], - [ - 39.280775518747866, - -6.776754016935989 - ], - [ - 39.280818510912624, - -6.776750660815322 - ], - [ - 39.28082911053632, - -6.776772724163326 - ], - [ - 39.28091689459758, - -6.781345598621182 - ], - [ - 39.28080388738358, - -6.7813839793259 - ], - [ - 39.28084388776356, - -6.781454612139605 - ], - [ - 39.28085319429884, - -6.78143154903115 - ], - [ - 39.28087584049512, - -6.781437812905911 - ], - [ - 39.28086609036416, - -6.781472364858208 - ], - [ - 39.280881881877654, - -6.781483136266438 - ], - [ - 39.28086943504425, - -6.78149915312294 - ], - [ - 39.28088554011426, - -6.781534324494014 - ], - [ - 39.280873569959816, - -6.781638136661505 - ], - [ - 39.28092499494767, - -6.781750298004826 - ], - [ - 39.28095457939287, - -6.783298331704033 - ], - [ - 39.28086026889085, - -6.783333866874134 - ], - [ - 39.280959342849336, - -6.783533751067348 - ], - [ - 39.28096937002043, - -6.784069342141371 - ], - [ - 39.28095243868693, - -6.78408149778495 - ], - [ - 39.280970796197394, - -6.784135018997731 - ], - [ - 39.280990362247046, - -6.785161799575521 - ], - [ - 39.28090314512198, - -6.785218441815514 - ], - [ - 39.28094438037573, - -6.785212514854102 - ], - [ - 39.280994123789, - -6.785340368033896 - ], - [ - 39.28103471876776, - -6.787446616974048 - ], - [ - 39.281027381291025, - -6.787464990541269 - ], - [ - 39.280827513135605, - -6.787500088788153 - ], - [ - 39.28099960926122, - -6.78779064973444 - ], - [ - 39.28100210101566, - -6.787851081760929 - ], - [ - 39.281026585687755, - -6.787859226828259 - ], - [ - 39.28102285279996, - -6.787966432946407 - ], - [ - 39.28093203937434, - -6.788005057346286 - ], - [ - 39.28100675911835, - -6.788204324299769 - ], - [ - 39.280997459334884, - -6.788308436132202 - ], - [ - 39.28105380334917, - -6.788440096324056 - ], - [ - 39.281068553938496, - -6.789216155958014 - ], - [ - 39.28096066849781, - -6.789245774186083 - ], - [ - 39.281073839006595, - -6.789477514362669 - ], - [ - 39.28114397070973, - -6.793117958745426 - ], - [ - 39.28113420729278, - -6.79383486840148 - ], - [ - 39.281114726156694, - -6.793847842189681 - ], - [ - 39.28113330952829, - -6.79388250834817 - ], - [ - 39.281129262608935, - -6.794159983557968 - ], - [ - 39.28109084175889, - -6.794179680568165 - ], - [ - 39.28112752361519, - -6.794277502669122 - ], - [ - 39.28111635395094, - -6.795038314028078 - ], - [ - 39.281016070626016, - -6.795075741198945 - ], - [ - 39.281112775297714, - -6.795250226366647 - ], - [ - 39.2810949704288, - -6.79646195887955 - ], - [ - 39.2809583747182, - -6.796479910277594 - ], - [ - 39.280976796828014, - -6.79655746965899 - ], - [ - 39.28106599069538, - -6.796757139303622 - ], - [ - 39.28105528128861, - -6.79686803066901 - ], - [ - 39.281087453345336, - -6.796987885295481 - ], - [ - 39.28108073143122, - -6.797439731548687 - ], - [ - 39.28083992809423, - -6.797461906062759 - ], - [ - 39.28083642610969, - -6.797501481434426 - ], - [ - 39.28072006370183, - -6.797559721622083 - ], - [ - 39.280792511077834, - -6.797755460113359 - ], - [ - 39.280782299142885, - -6.797822644537164 - ], - [ - 39.28087877377628, - -6.798094898764859 - ], - [ - 39.28087160779872, - -6.798109486776828 - ], - [ - 39.28084495591411, - -6.798106189716947 - ], - [ - 39.28091358669126, - -6.798282661131834 - ], - [ - 39.280894058903186, - -6.798303988028182 - ], - [ - 39.28089993648302, - -6.798318476273004 - ], - [ - 39.28094807709286, - -6.798317376152552 - ], - [ - 39.28097704846284, - -6.798338565814496 - ], - [ - 39.280968589108724, - -6.798359909541208 - ], - [ - 39.28098862495911, - -6.798363833982053 - ], - [ - 39.28106384308544, - -6.798560924842693 - ], - [ - 39.281060047781274, - -6.798789172787439 - ], - [ - 39.280964648702366, - -6.79881579282715 - ], - [ - 39.2810549062557, - -6.799182593389792 - ], - [ - 39.281051210712, - -6.799417431324301 - ], - [ - 39.281012045218766, - -6.799461973169469 - ], - [ - 39.28092870982996, - -6.799469616595563 - ], - [ - 39.28102911738072, - -6.800116395040011 - ], - [ - 39.28096755147659, - -6.800136517203233 - ], - [ - 39.28098227562801, - -6.800231504054254 - ], - [ - 39.28092089023231, - -6.800365292854012 - ], - [ - 39.28092243227157, - -6.800466481559217 - ], - [ - 39.28099285109281, - -6.800487505251037 - ], - [ - 39.2809865598948, - -6.80056017366214 - ], - [ - 39.281022039297945, - -6.800676509145848 - ], - [ - 39.28100512867587, - -6.800686027414948 - ], - [ - 39.28094639492564, - -6.800673573505817 - ], - [ - 39.28098960816634, - -6.80080288458438 - ], - [ - 39.28100608830849, - -6.800808079923416 - ], - [ - 39.28101818264724, - -6.800788226357124 - ], - [ - 39.28103090221736, - -6.800799071421509 - ], - [ - 39.28100953313295, - -6.802247229426974 - ], - [ - 39.280969530277055, - -6.802983496860963 - ], - [ - 39.279742849863105, - -6.803751726145401 - ], - [ - 39.279491514203464, - -6.803861587800235 - ], - [ - 39.277158588001264, - -6.804087251350162 - ], - [ - 39.27513747683199, - -6.804142100699255 - ], - [ - 39.27511678207263, - -6.804135692385428 - ], - [ - 39.2751163497056, - -6.804060709416309 - ], - [ - 39.27391722620214, - -6.804061389785589 - ], - [ - 39.27385826397281, - -6.804047806856571 - ], - [ - 39.27384911606231, - -6.803999913343275 - ], - [ - 39.2735965361418, - -6.80398525563719 - ], - [ - 39.273500708320924, - -6.80401301734282 - ], - [ - 39.27346293523808, - -6.804003966970788 - ], - [ - 39.27342162790581, - -6.804025593648304 - ], - [ - 39.27340672680942, - -6.804008608011346 - ], - [ - 39.273416845431285, - -6.803993658369023 - ] - ], - [ - [ - 39.2734931545649, - -6.80398387288763 - ], - [ - 39.27349774748707, - -6.803981148820998 - ], - [ - 39.2734846042507, - -6.80398069373159 - ], - [ - 39.27348722790041, - -6.80398493599873 - ], - [ - 39.2734931545649, - -6.80398387288763 - ] - ], - [ - [ - 39.281045469223024, - -6.796406485350073 - ], - [ - 39.28104614552522, - -6.79640624303539 - ], - [ - 39.28104624219622, - -6.796405856316271 - ], - [ - 39.28104544832558, - -6.796406065132672 - ], - [ - 39.281045469223024, - -6.796406485350073 - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f883a0-labels", + "properties": { + "label:description": "Geojson building labels for scene f883a0", + "area": "dar", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - [ - 39.25013462741531, - -6.7822284539465 - ], - [ - 39.250139342204406, - -6.782225634227249 - ], - [ - 39.25013885529763, - -6.782223167263246 - ], - [ - 39.25013601751904, - -6.782223433931297 - ], - [ - 39.25013462741531, - -6.7822284539465 - ] + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 35952 + } + ] + } ], - [ - [ - 39.250133340148714, - -6.782197609422264 - ], - [ - 39.25013227568109, - -6.782192336183768 - ], - [ - 39.25013085449504, - -6.782186240541199 - ], - [ - 39.25012984461586, - -6.782195145644085 - ], - [ - 39.250133340148714, - -6.782197609422264 - ] + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ], - [ - [ - [ - 39.28101229778558, - -6.800726929325503 - ], - [ - 39.28102979517802, - -6.800722356034623 - ], - [ - 39.28102777800557, - -6.800751554944212 - ], - [ - 39.281011984140875, - -6.800747680790295 - ], - [ - 39.28101229778558, - -6.800726929325503 - ] - ] - ], - [ - [ - [ - 39.28098638863562, - -6.800732494588853 - ], - [ - 39.280997245783674, - -6.800723485911647 - ], - [ - 39.28100583405759, - -6.80073429499442 - ], - [ - 39.280995019972025, - -6.800742416990294 - ], - [ - 39.28098638863562, - -6.800732494588853 - ] - ] - ], - [ - [ - [ - 39.27333576292948, - -6.804020758896268 - ], - [ - 39.27334712034972, - -6.804009856466778 - ], - [ - 39.27335756490539, - -6.804021209097676 - ], - [ - 39.273346206700516, - -6.804030723971451 - ], - [ - 39.27333576292948, - -6.804020758896268 - ] - ] - ], - [ - [ - [ - 39.27338784445309, - -6.804011179704081 - ], - [ - 39.27340165825813, - -6.804007187497802 - ], - [ - 39.27340508925115, - -6.804022507747959 - ], - [ - 39.2733897619233, - -6.804025474696425 - ], - [ - 39.27338784445309, - -6.804011179704081 - ] - ] - ], - [ - [ - [ - 39.28093735076017, - -6.798277365237194 - ], - [ - 39.28094786501646, - -6.798293757944892 - ], - [ - 39.28092636574822, - -6.798297946514793 - ], - [ - 39.28091893391865, - -6.798272295760583 - ], - [ - 39.28093735076017, - -6.798277365237194 - ] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 39.27315718647447, + -6.804030897967275 + ], + [ + 39.273165048748965, + -6.804015735580677 + ], + [ + 39.273213892047806, + -6.804006275126262 + ], + [ + 39.27322980988637, + -6.804022244653453 + ], + [ + 39.273170342425956, + -6.804047430372633 + ], + [ + 39.27315718647447, + -6.804030897967275 + ] + ] + ], + [ + [ + [ + 39.273270319050496, + -6.804022808953547 + ], + [ + 39.273288769025, + -6.803992819792749 + ], + [ + 39.27331911984259, + -6.804029517800871 + ], + [ + 39.2732773025055, + -6.804036732038151 + ], + [ + 39.273270319050496, + -6.804022808953547 + ] + ] + ], + [ + [ + [ + 39.273416845431285, + -6.803993658369023 + ], + [ + 39.273432966277255, + -6.803999383623384 + ], + [ + 39.27345314382025, + -6.803979786475935 + ], + [ + 39.27322440426283, + -6.803973842746188 + ], + [ + 39.273203984545894, + -6.804001738617233 + ], + [ + 39.27307652605739, + -6.804015326271316 + ], + [ + 39.27305748415282, + -6.804061875764365 + ], + [ + 39.27243654623383, + -6.804062182204895 + ], + [ + 39.272422506412305, + -6.804030782430953 + ], + [ + 39.27233818959957, + -6.804033592321105 + ], + [ + 39.27232684217891, + -6.804062287567047 + ], + [ + 39.27050720474849, + -6.804063308351038 + ], + [ + 39.27050003464541, + -6.804267571943514 + ], + [ + 39.26958162504325, + -6.804292771833833 + ], + [ + 39.268183769535355, + -6.804289804955942 + ], + [ + 39.26817548170555, + -6.804064606402513 + ], + [ + 39.26638040640152, + -6.804065554449549 + ], + [ + 39.26635869387405, + -6.80392326747663 + ], + [ + 39.26612966449484, + -6.803973347620189 + ], + [ + 39.2660934787131, + -6.804065755940294 + ], + [ + 39.26187296555017, + -6.804068058726235 + ], + [ + 39.26184507218486, + -6.804052028033205 + ], + [ + 39.26184066298603, + -6.804023191210663 + ], + [ + 39.26172396812868, + -6.804068139349033 + ], + [ + 39.25777183400359, + -6.804070261106554 + ], + [ + 39.257755345525474, + -6.804004808291634 + ], + [ + 39.25731554868908, + -6.804070460389402 + ], + [ + 39.256625459443185, + -6.804070870513325 + ], + [ + 39.25662348593693, + -6.80422297079218 + ], + [ + 39.256462846950114, + -6.804260546096121 + ], + [ + 39.256084987159134, + -6.804259012772288 + ], + [ + 39.256069700572326, + -6.804123376288454 + ], + [ + 39.25557123594966, + -6.804199546422531 + ], + [ + 39.25540366700173, + -6.804257868759697 + ], + [ + 39.25364580884159, + -6.804253286905217 + ], + [ + 39.25336404330564, + -6.804245528109689 + ], + [ + 39.25184949473127, + -6.804063493477143 + ], + [ + 39.25045725022109, + -6.803626776738144 + ], + [ + 39.250038582443665, + -6.80287820958753 + ], + [ + 39.25000304212214, + -6.802509337350906 + ], + [ + 39.24988415760574, + -6.799080315984306 + ], + [ + 39.24990450849855, + -6.799063247023449 + ], + [ + 39.2498947561629, + -6.798995102709657 + ], + [ + 39.24988086958492, + -6.79898550113401 + ], + [ + 39.249810881599906, + -6.796966175954005 + ], + [ + 39.24981645844359, + -6.796947177743604 + ], + [ + 39.249872411376465, + -6.796943310209003 + ], + [ + 39.249872317302916, + -6.79667664523118 + ], + [ + 39.24989594446884, + -6.796632192786874 + ], + [ + 39.2498929959209, + -6.796620487479649 + ], + [ + 39.24981140260522, + -6.796653264268834 + ], + [ + 39.249799651394305, + -6.796639604421224 + ], + [ + 39.249702848316815, + -6.793849310612861 + ], + [ + 39.249709580102994, + -6.793444194527318 + ], + [ + 39.24968269293236, + -6.793263269989816 + ], + [ + 39.24966102436161, + -6.792603766179727 + ], + [ + 39.24967954445545, + -6.791728669025114 + ], + [ + 39.24970804593678, + -6.791366446494281 + ], + [ + 39.24968767919196, + -6.791352173642583 + ], + [ + 39.24969901238331, + -6.790812445435837 + ], + [ + 39.24971718787532, + -6.790709956750732 + ], + [ + 39.24976000940216, + -6.790676190366502 + ], + [ + 39.24974335811433, + -6.79066757715095 + ], + [ + 39.24975954380845, + -6.7905720968061 + ], + [ + 39.24971030856871, + -6.790285552729824 + ], + [ + 39.24971251701262, + -6.790166259808814 + ], + [ + 39.249755177302276, + -6.79007657102466 + ], + [ + 39.24978363439594, + -6.790069338302176 + ], + [ + 39.24973059995279, + -6.789901141277205 + ], + [ + 39.249748519583505, + -6.789843130737693 + ], + [ + 39.249722462770514, + -6.789712522643217 + ], + [ + 39.249757283518974, + -6.788060421836336 + ], + [ + 39.24981581028674, + -6.788035256591358 + ], + [ + 39.2497613539383, + -6.787878110167098 + ], + [ + 39.249768572097224, + -6.787518805148783 + ], + [ + 39.25004009533414, + -6.787434103342062 + ], + [ + 39.25002896611271, + -6.787302674153842 + ], + [ + 39.25004835160351, + -6.787293592535141 + ], + [ + 39.25004462643536, + -6.787248062083075 + ], + [ + 39.249913527740446, + -6.787275621098578 + ], + [ + 39.24987623121805, + -6.787223585244646 + ], + [ + 39.2498870533108, + -6.78675947061919 + ], + [ + 39.249878668979136, + -6.786651689669421 + ], + [ + 39.249854185335714, + -6.786644521595329 + ], + [ + 39.24985427604204, + -6.786567866890232 + ], + [ + 39.249878150583925, + -6.786543683174986 + ], + [ + 39.24987767258752, + -6.786514031590554 + ], + [ + 39.24985595176083, + -6.786480788612282 + ], + [ + 39.24985427458511, + -6.786004693765936 + ], + [ + 39.249835799952045, + -6.785985440085538 + ], + [ + 39.24981894796509, + -6.785692711962167 + ], + [ + 39.249835592123326, + -6.785666857043904 + ], + [ + 39.2498132441077, + -6.785399014698585 + ], + [ + 39.24982665102962, + -6.785376346020647 + ], + [ + 39.24981460672529, + -6.785349524356427 + ], + [ + 39.24981956779118, + -6.785099760997835 + ], + [ + 39.249871024621996, + -6.783491089857915 + ], + [ + 39.24990124402507, + -6.783476958079939 + ], + [ + 39.24987648913789, + -6.783320417149405 + ], + [ + 39.24990659151731, + -6.782389387144009 + ], + [ + 39.25010086176018, + -6.782346474227426 + ], + [ + 39.2501017767606, + -6.78232629074711 + ], + [ + 39.250066934399854, + -6.782156990037679 + ], + [ + 39.24992289431197, + -6.782182852009128 + ], + [ + 39.2499138298333, + -6.782153922351434 + ], + [ + 39.25006519231678, + -6.777778404257647 + ], + [ + 39.250159011995514, + -6.775687803188281 + ], + [ + 39.250652695356244, + -6.774710612955933 + ], + [ + 39.25973527817753, + -6.774051636265581 + ], + [ + 39.26420907625315, + -6.774027485936578 + ], + [ + 39.26430505647687, + -6.774065405657963 + ], + [ + 39.26433909336156, + -6.774026490186999 + ], + [ + 39.27016488290843, + -6.773994609904983 + ], + [ + 39.27021692938301, + -6.773995269543569 + ], + [ + 39.27026644245446, + -6.774033162706626 + ], + [ + 39.27031016915434, + -6.773994066588543 + ], + [ + 39.27159183301611, + -6.773986876672367 + ], + [ + 39.27168780046295, + -6.774108785772873 + ], + [ + 39.271686763240815, + -6.774136172079665 + ], + [ + 39.27192122999216, + -6.774436870625464 + ], + [ + 39.27225338516659, + -6.773984336984419 + ], + [ + 39.27280594364432, + -6.773980646345491 + ], + [ + 39.274209296190605, + -6.774041415140496 + ], + [ + 39.27802675727596, + -6.774426374513278 + ], + [ + 39.27864214751531, + -6.774515880892984 + ], + [ + 39.280023369706576, + -6.774755350012424 + ], + [ + 39.28050916887765, + -6.775147428625842 + ], + [ + 39.28074438487075, + -6.775756768054177 + ], + [ + 39.28081527672994, + -6.776054440091325 + ], + [ + 39.28082717168011, + -6.776695908769849 + ], + [ + 39.2807657423886, + -6.776717815104587 + ], + [ + 39.280775518747866, + -6.776754016935989 + ], + [ + 39.280818510912624, + -6.776750660815322 + ], + [ + 39.28082911053632, + -6.776772724163326 + ], + [ + 39.28091689459758, + -6.781345598621182 + ], + [ + 39.28080388738358, + -6.7813839793259 + ], + [ + 39.28084388776356, + -6.781454612139605 + ], + [ + 39.28085319429884, + -6.78143154903115 + ], + [ + 39.28087584049512, + -6.781437812905911 + ], + [ + 39.28086609036416, + -6.781472364858208 + ], + [ + 39.280881881877654, + -6.781483136266438 + ], + [ + 39.28086943504425, + -6.78149915312294 + ], + [ + 39.28088554011426, + -6.781534324494014 + ], + [ + 39.280873569959816, + -6.781638136661505 + ], + [ + 39.28092499494767, + -6.781750298004826 + ], + [ + 39.28095457939287, + -6.783298331704033 + ], + [ + 39.28086026889085, + -6.783333866874134 + ], + [ + 39.280959342849336, + -6.783533751067348 + ], + [ + 39.28096937002043, + -6.784069342141371 + ], + [ + 39.28095243868693, + -6.78408149778495 + ], + [ + 39.280970796197394, + -6.784135018997731 + ], + [ + 39.280990362247046, + -6.785161799575521 + ], + [ + 39.28090314512198, + -6.785218441815514 + ], + [ + 39.28094438037573, + -6.785212514854102 + ], + [ + 39.280994123789, + -6.785340368033896 + ], + [ + 39.28103471876776, + -6.787446616974048 + ], + [ + 39.281027381291025, + -6.787464990541269 + ], + [ + 39.280827513135605, + -6.787500088788153 + ], + [ + 39.28099960926122, + -6.78779064973444 + ], + [ + 39.28100210101566, + -6.787851081760929 + ], + [ + 39.281026585687755, + -6.787859226828259 + ], + [ + 39.28102285279996, + -6.787966432946407 + ], + [ + 39.28093203937434, + -6.788005057346286 + ], + [ + 39.28100675911835, + -6.788204324299769 + ], + [ + 39.280997459334884, + -6.788308436132202 + ], + [ + 39.28105380334917, + -6.788440096324056 + ], + [ + 39.281068553938496, + -6.789216155958014 + ], + [ + 39.28096066849781, + -6.789245774186083 + ], + [ + 39.281073839006595, + -6.789477514362669 + ], + [ + 39.28114397070973, + -6.793117958745426 + ], + [ + 39.28113420729278, + -6.79383486840148 + ], + [ + 39.281114726156694, + -6.793847842189681 + ], + [ + 39.28113330952829, + -6.79388250834817 + ], + [ + 39.281129262608935, + -6.794159983557968 + ], + [ + 39.28109084175889, + -6.794179680568165 + ], + [ + 39.28112752361519, + -6.794277502669122 + ], + [ + 39.28111635395094, + -6.795038314028078 + ], + [ + 39.281016070626016, + -6.795075741198945 + ], + [ + 39.281112775297714, + -6.795250226366647 + ], + [ + 39.2810949704288, + -6.79646195887955 + ], + [ + 39.2809583747182, + -6.796479910277594 + ], + [ + 39.280976796828014, + -6.79655746965899 + ], + [ + 39.28106599069538, + -6.796757139303622 + ], + [ + 39.28105528128861, + -6.79686803066901 + ], + [ + 39.281087453345336, + -6.796987885295481 + ], + [ + 39.28108073143122, + -6.797439731548687 + ], + [ + 39.28083992809423, + -6.797461906062759 + ], + [ + 39.28083642610969, + -6.797501481434426 + ], + [ + 39.28072006370183, + -6.797559721622083 + ], + [ + 39.280792511077834, + -6.797755460113359 + ], + [ + 39.280782299142885, + -6.797822644537164 + ], + [ + 39.28087877377628, + -6.798094898764859 + ], + [ + 39.28087160779872, + -6.798109486776828 + ], + [ + 39.28084495591411, + -6.798106189716947 + ], + [ + 39.28091358669126, + -6.798282661131834 + ], + [ + 39.280894058903186, + -6.798303988028182 + ], + [ + 39.28089993648302, + -6.798318476273004 + ], + [ + 39.28094807709286, + -6.798317376152552 + ], + [ + 39.28097704846284, + -6.798338565814496 + ], + [ + 39.280968589108724, + -6.798359909541208 + ], + [ + 39.28098862495911, + -6.798363833982053 + ], + [ + 39.28106384308544, + -6.798560924842693 + ], + [ + 39.281060047781274, + -6.798789172787439 + ], + [ + 39.280964648702366, + -6.79881579282715 + ], + [ + 39.2810549062557, + -6.799182593389792 + ], + [ + 39.281051210712, + -6.799417431324301 + ], + [ + 39.281012045218766, + -6.799461973169469 + ], + [ + 39.28092870982996, + -6.799469616595563 + ], + [ + 39.28102911738072, + -6.800116395040011 + ], + [ + 39.28096755147659, + -6.800136517203233 + ], + [ + 39.28098227562801, + -6.800231504054254 + ], + [ + 39.28092089023231, + -6.800365292854012 + ], + [ + 39.28092243227157, + -6.800466481559217 + ], + [ + 39.28099285109281, + -6.800487505251037 + ], + [ + 39.2809865598948, + -6.80056017366214 + ], + [ + 39.281022039297945, + -6.800676509145848 + ], + [ + 39.28100512867587, + -6.800686027414948 + ], + [ + 39.28094639492564, + -6.800673573505817 + ], + [ + 39.28098960816634, + -6.80080288458438 + ], + [ + 39.28100608830849, + -6.800808079923416 + ], + [ + 39.28101818264724, + -6.800788226357124 + ], + [ + 39.28103090221736, + -6.800799071421509 + ], + [ + 39.28100953313295, + -6.802247229426974 + ], + [ + 39.280969530277055, + -6.802983496860963 + ], + [ + 39.279742849863105, + -6.803751726145401 + ], + [ + 39.279491514203464, + -6.803861587800235 + ], + [ + 39.277158588001264, + -6.804087251350162 + ], + [ + 39.27513747683199, + -6.804142100699255 + ], + [ + 39.27511678207263, + -6.804135692385428 + ], + [ + 39.2751163497056, + -6.804060709416309 + ], + [ + 39.27391722620214, + -6.804061389785589 + ], + [ + 39.27385826397281, + -6.804047806856571 + ], + [ + 39.27384911606231, + -6.803999913343275 + ], + [ + 39.2735965361418, + -6.80398525563719 + ], + [ + 39.273500708320924, + -6.80401301734282 + ], + [ + 39.27346293523808, + -6.804003966970788 + ], + [ + 39.27342162790581, + -6.804025593648304 + ], + [ + 39.27340672680942, + -6.804008608011346 + ], + [ + 39.273416845431285, + -6.803993658369023 + ] + ], + [ + [ + 39.2734931545649, + -6.80398387288763 + ], + [ + 39.27349774748707, + -6.803981148820998 + ], + [ + 39.2734846042507, + -6.80398069373159 + ], + [ + 39.27348722790041, + -6.80398493599873 + ], + [ + 39.2734931545649, + -6.80398387288763 + ] + ], + [ + [ + 39.281045469223024, + -6.796406485350073 + ], + [ + 39.28104614552522, + -6.79640624303539 + ], + [ + 39.28104624219622, + -6.796405856316271 + ], + [ + 39.28104544832558, + -6.796406065132672 + ], + [ + 39.281045469223024, + -6.796406485350073 + ] + ], + [ + [ + 39.25013462741531, + -6.7822284539465 + ], + [ + 39.250139342204406, + -6.782225634227249 + ], + [ + 39.25013885529763, + -6.782223167263246 + ], + [ + 39.25013601751904, + -6.782223433931297 + ], + [ + 39.25013462741531, + -6.7822284539465 + ] + ], + [ + [ + 39.250133340148714, + -6.782197609422264 + ], + [ + 39.25013227568109, + -6.782192336183768 + ], + [ + 39.25013085449504, + -6.782186240541199 + ], + [ + 39.25012984461586, + -6.782195145644085 + ], + [ + 39.250133340148714, + -6.782197609422264 + ] + ] + ], + [ + [ + [ + 39.28101229778558, + -6.800726929325503 + ], + [ + 39.28102979517802, + -6.800722356034623 + ], + [ + 39.28102777800557, + -6.800751554944212 + ], + [ + 39.281011984140875, + -6.800747680790295 + ], + [ + 39.28101229778558, + -6.800726929325503 + ] + ] + ], + [ + [ + [ + 39.28098638863562, + -6.800732494588853 + ], + [ + 39.280997245783674, + -6.800723485911647 + ], + [ + 39.28100583405759, + -6.80073429499442 + ], + [ + 39.280995019972025, + -6.800742416990294 + ], + [ + 39.28098638863562, + -6.800732494588853 + ] + ] + ], + [ + [ + [ + 39.27333576292948, + -6.804020758896268 + ], + [ + 39.27334712034972, + -6.804009856466778 + ], + [ + 39.27335756490539, + -6.804021209097676 + ], + [ + 39.273346206700516, + -6.804030723971451 + ], + [ + 39.27333576292948, + -6.804020758896268 + ] + ] + ], + [ + [ + [ + 39.27338784445309, + -6.804011179704081 + ], + [ + 39.27340165825813, + -6.804007187497802 + ], + [ + 39.27340508925115, + -6.804022507747959 + ], + [ + 39.2733897619233, + -6.804025474696425 + ], + [ + 39.27338784445309, + -6.804011179704081 + ] + ] + ], + [ + [ + [ + 39.28093735076017, + -6.798277365237194 + ], + [ + 39.28094786501646, + -6.798293757944892 + ], + [ + 39.28092636574822, + -6.798297946514793 + ], + [ + 39.28091893391865, + -6.798272295760583 + ], + [ + 39.28093735076017, + -6.798277365237194 + ] + ] + ] ] - ] - ] - }, - "bbox": [ - 39.24966102436161, - -6.804292908938633, - 39.28114400293776, - -6.773980646345491 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/f883a0-labels/f883a0.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/f883a0-labels/f883a0.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.24966102436161, + -6.804292908938633, + 39.28114400293776, + -6.773980646345491 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json b/tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json index e9533e821..817441517 100644 --- a/tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json +++ b/tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json @@ -1,1266 +1,1267 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f883a0", - "properties": { - "area": "dar", - "license": "ODbL-1.0", - "datetime": "2017-11-01T00:00:00Z" - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - 39.27315718647447, - -6.804030897967275 - ], - [ - 39.273165048748965, - -6.804015735580677 - ], - [ - 39.273213892047806, - -6.804006275126262 - ], - [ - 39.27322980988637, - -6.804022244653453 - ], - [ - 39.273170342425956, - -6.804047430372633 - ], - [ - 39.27315718647447, - -6.804030897967275 - ] - ] - ], - [ - [ - [ - 39.273270319050496, - -6.804022808953547 - ], - [ - 39.273288769025, - -6.803992819792749 - ], - [ - 39.27331911984259, - -6.804029517800871 - ], - [ - 39.2732773025055, - -6.804036732038151 - ], - [ - 39.273270319050496, - -6.804022808953547 - ] - ] - ], - [ - [ - [ - 39.273416845431285, - -6.803993658369023 - ], - [ - 39.273432966277255, - -6.803999383623384 - ], - [ - 39.27345314382025, - -6.803979786475935 - ], - [ - 39.27322440426283, - -6.803973842746188 - ], - [ - 39.273203984545894, - -6.804001738617233 - ], - [ - 39.27307652605739, - -6.804015326271316 - ], - [ - 39.27305748415282, - -6.804061875764365 - ], - [ - 39.27243654623383, - -6.804062182204895 - ], - [ - 39.272422506412305, - -6.804030782430953 - ], - [ - 39.27233818959957, - -6.804033592321105 - ], - [ - 39.27232684217891, - -6.804062287567047 - ], - [ - 39.27050720474849, - -6.804063308351038 - ], - [ - 39.27050003464541, - -6.804267571943514 - ], - [ - 39.26958162504325, - -6.804292771833833 - ], - [ - 39.268183769535355, - -6.804289804955942 - ], - [ - 39.26817548170555, - -6.804064606402513 - ], - [ - 39.26638040640152, - -6.804065554449549 - ], - [ - 39.26635869387405, - -6.80392326747663 - ], - [ - 39.26612966449484, - -6.803973347620189 - ], - [ - 39.2660934787131, - -6.804065755940294 - ], - [ - 39.26187296555017, - -6.804068058726235 - ], - [ - 39.26184507218486, - -6.804052028033205 - ], - [ - 39.26184066298603, - -6.804023191210663 - ], - [ - 39.26172396812868, - -6.804068139349033 - ], - [ - 39.25777183400359, - -6.804070261106554 - ], - [ - 39.257755345525474, - -6.804004808291634 - ], - [ - 39.25731554868908, - -6.804070460389402 - ], - [ - 39.256625459443185, - -6.804070870513325 - ], - [ - 39.25662348593693, - -6.80422297079218 - ], - [ - 39.256462846950114, - -6.804260546096121 - ], - [ - 39.256084987159134, - -6.804259012772288 - ], - [ - 39.256069700572326, - -6.804123376288454 - ], - [ - 39.25557123594966, - -6.804199546422531 - ], - [ - 39.25540366700173, - -6.804257868759697 - ], - [ - 39.25364580884159, - -6.804253286905217 - ], - [ - 39.25336404330564, - -6.804245528109689 - ], - [ - 39.25184949473127, - -6.804063493477143 - ], - [ - 39.25045725022109, - -6.803626776738144 - ], - [ - 39.250038582443665, - -6.80287820958753 - ], - [ - 39.25000304212214, - -6.802509337350906 - ], - [ - 39.24988415760574, - -6.799080315984306 - ], - [ - 39.24990450849855, - -6.799063247023449 - ], - [ - 39.2498947561629, - -6.798995102709657 - ], - [ - 39.24988086958492, - -6.79898550113401 - ], - [ - 39.249810881599906, - -6.796966175954005 - ], - [ - 39.24981645844359, - -6.796947177743604 - ], - [ - 39.249872411376465, - -6.796943310209003 - ], - [ - 39.249872317302916, - -6.79667664523118 - ], - [ - 39.24989594446884, - -6.796632192786874 - ], - [ - 39.2498929959209, - -6.796620487479649 - ], - [ - 39.24981140260522, - -6.796653264268834 - ], - [ - 39.249799651394305, - -6.796639604421224 - ], - [ - 39.249702848316815, - -6.793849310612861 - ], - [ - 39.249709580102994, - -6.793444194527318 - ], - [ - 39.24968269293236, - -6.793263269989816 - ], - [ - 39.24966102436161, - -6.792603766179727 - ], - [ - 39.24967954445545, - -6.791728669025114 - ], - [ - 39.24970804593678, - -6.791366446494281 - ], - [ - 39.24968767919196, - -6.791352173642583 - ], - [ - 39.24969901238331, - -6.790812445435837 - ], - [ - 39.24971718787532, - -6.790709956750732 - ], - [ - 39.24976000940216, - -6.790676190366502 - ], - [ - 39.24974335811433, - -6.79066757715095 - ], - [ - 39.24975954380845, - -6.7905720968061 - ], - [ - 39.24971030856871, - -6.790285552729824 - ], - [ - 39.24971251701262, - -6.790166259808814 - ], - [ - 39.249755177302276, - -6.79007657102466 - ], - [ - 39.24978363439594, - -6.790069338302176 - ], - [ - 39.24973059995279, - -6.789901141277205 - ], - [ - 39.249748519583505, - -6.789843130737693 - ], - [ - 39.249722462770514, - -6.789712522643217 - ], - [ - 39.249757283518974, - -6.788060421836336 - ], - [ - 39.24981581028674, - -6.788035256591358 - ], - [ - 39.2497613539383, - -6.787878110167098 - ], - [ - 39.249768572097224, - -6.787518805148783 - ], - [ - 39.25004009533414, - -6.787434103342062 - ], - [ - 39.25002896611271, - -6.787302674153842 - ], - [ - 39.25004835160351, - -6.787293592535141 - ], - [ - 39.25004462643536, - -6.787248062083075 - ], - [ - 39.249913527740446, - -6.787275621098578 - ], - [ - 39.24987623121805, - -6.787223585244646 - ], - [ - 39.2498870533108, - -6.78675947061919 - ], - [ - 39.249878668979136, - -6.786651689669421 - ], - [ - 39.249854185335714, - -6.786644521595329 - ], - [ - 39.24985427604204, - -6.786567866890232 - ], - [ - 39.249878150583925, - -6.786543683174986 - ], - [ - 39.24987767258752, - -6.786514031590554 - ], - [ - 39.24985595176083, - -6.786480788612282 - ], - [ - 39.24985427458511, - -6.786004693765936 - ], - [ - 39.249835799952045, - -6.785985440085538 - ], - [ - 39.24981894796509, - -6.785692711962167 - ], - [ - 39.249835592123326, - -6.785666857043904 - ], - [ - 39.2498132441077, - -6.785399014698585 - ], - [ - 39.24982665102962, - -6.785376346020647 - ], - [ - 39.24981460672529, - -6.785349524356427 - ], - [ - 39.24981956779118, - -6.785099760997835 - ], - [ - 39.249871024621996, - -6.783491089857915 - ], - [ - 39.24990124402507, - -6.783476958079939 - ], - [ - 39.24987648913789, - -6.783320417149405 - ], - [ - 39.24990659151731, - -6.782389387144009 - ], - [ - 39.25010086176018, - -6.782346474227426 - ], - [ - 39.2501017767606, - -6.78232629074711 - ], - [ - 39.250066934399854, - -6.782156990037679 - ], - [ - 39.24992289431197, - -6.782182852009128 - ], - [ - 39.2499138298333, - -6.782153922351434 - ], - [ - 39.25006519231678, - -6.777778404257647 - ], - [ - 39.250159011995514, - -6.775687803188281 - ], - [ - 39.250652695356244, - -6.774710612955933 - ], - [ - 39.25973527817753, - -6.774051636265581 - ], - [ - 39.26420907625315, - -6.774027485936578 - ], - [ - 39.26430505647687, - -6.774065405657963 - ], - [ - 39.26433909336156, - -6.774026490186999 - ], - [ - 39.27016488290843, - -6.773994609904983 - ], - [ - 39.27021692938301, - -6.773995269543569 - ], - [ - 39.27026644245446, - -6.774033162706626 - ], - [ - 39.27031016915434, - -6.773994066588543 - ], - [ - 39.27159183301611, - -6.773986876672367 - ], - [ - 39.27168780046295, - -6.774108785772873 - ], - [ - 39.271686763240815, - -6.774136172079665 - ], - [ - 39.27192122999216, - -6.774436870625464 - ], - [ - 39.27225338516659, - -6.773984336984419 - ], - [ - 39.27280594364432, - -6.773980646345491 - ], - [ - 39.274209296190605, - -6.774041415140496 - ], - [ - 39.27802675727596, - -6.774426374513278 - ], - [ - 39.27864214751531, - -6.774515880892984 - ], - [ - 39.280023369706576, - -6.774755350012424 - ], - [ - 39.28050916887765, - -6.775147428625842 - ], - [ - 39.28074438487075, - -6.775756768054177 - ], - [ - 39.28081527672994, - -6.776054440091325 - ], - [ - 39.28082717168011, - -6.776695908769849 - ], - [ - 39.2807657423886, - -6.776717815104587 - ], - [ - 39.280775518747866, - -6.776754016935989 - ], - [ - 39.280818510912624, - -6.776750660815322 - ], - [ - 39.28082911053632, - -6.776772724163326 - ], - [ - 39.28091689459758, - -6.781345598621182 - ], - [ - 39.28080388738358, - -6.7813839793259 - ], - [ - 39.28084388776356, - -6.781454612139605 - ], - [ - 39.28085319429884, - -6.78143154903115 - ], - [ - 39.28087584049512, - -6.781437812905911 - ], - [ - 39.28086609036416, - -6.781472364858208 - ], - [ - 39.280881881877654, - -6.781483136266438 - ], - [ - 39.28086943504425, - -6.78149915312294 - ], - [ - 39.28088554011426, - -6.781534324494014 - ], - [ - 39.280873569959816, - -6.781638136661505 - ], - [ - 39.28092499494767, - -6.781750298004826 - ], - [ - 39.28095457939287, - -6.783298331704033 - ], - [ - 39.28086026889085, - -6.783333866874134 - ], - [ - 39.280959342849336, - -6.783533751067348 - ], - [ - 39.28096937002043, - -6.784069342141371 - ], - [ - 39.28095243868693, - -6.78408149778495 - ], - [ - 39.280970796197394, - -6.784135018997731 - ], - [ - 39.280990362247046, - -6.785161799575521 - ], - [ - 39.28090314512198, - -6.785218441815514 - ], - [ - 39.28094438037573, - -6.785212514854102 - ], - [ - 39.280994123789, - -6.785340368033896 - ], - [ - 39.28103471876776, - -6.787446616974048 - ], - [ - 39.281027381291025, - -6.787464990541269 - ], - [ - 39.280827513135605, - -6.787500088788153 - ], - [ - 39.28099960926122, - -6.78779064973444 - ], - [ - 39.28100210101566, - -6.787851081760929 - ], - [ - 39.281026585687755, - -6.787859226828259 - ], - [ - 39.28102285279996, - -6.787966432946407 - ], - [ - 39.28093203937434, - -6.788005057346286 - ], - [ - 39.28100675911835, - -6.788204324299769 - ], - [ - 39.280997459334884, - -6.788308436132202 - ], - [ - 39.28105380334917, - -6.788440096324056 - ], - [ - 39.281068553938496, - -6.789216155958014 - ], - [ - 39.28096066849781, - -6.789245774186083 - ], - [ - 39.281073839006595, - -6.789477514362669 - ], - [ - 39.28114397070973, - -6.793117958745426 - ], - [ - 39.28113420729278, - -6.79383486840148 - ], - [ - 39.281114726156694, - -6.793847842189681 - ], - [ - 39.28113330952829, - -6.79388250834817 - ], - [ - 39.281129262608935, - -6.794159983557968 - ], - [ - 39.28109084175889, - -6.794179680568165 - ], - [ - 39.28112752361519, - -6.794277502669122 - ], - [ - 39.28111635395094, - -6.795038314028078 - ], - [ - 39.281016070626016, - -6.795075741198945 - ], - [ - 39.281112775297714, - -6.795250226366647 - ], - [ - 39.2810949704288, - -6.79646195887955 - ], - [ - 39.2809583747182, - -6.796479910277594 - ], - [ - 39.280976796828014, - -6.79655746965899 - ], - [ - 39.28106599069538, - -6.796757139303622 - ], - [ - 39.28105528128861, - -6.79686803066901 - ], - [ - 39.281087453345336, - -6.796987885295481 - ], - [ - 39.28108073143122, - -6.797439731548687 - ], - [ - 39.28083992809423, - -6.797461906062759 - ], - [ - 39.28083642610969, - -6.797501481434426 - ], - [ - 39.28072006370183, - -6.797559721622083 - ], - [ - 39.280792511077834, - -6.797755460113359 - ], - [ - 39.280782299142885, - -6.797822644537164 - ], - [ - 39.28087877377628, - -6.798094898764859 - ], - [ - 39.28087160779872, - -6.798109486776828 - ], - [ - 39.28084495591411, - -6.798106189716947 - ], - [ - 39.28091358669126, - -6.798282661131834 - ], - [ - 39.280894058903186, - -6.798303988028182 - ], - [ - 39.28089993648302, - -6.798318476273004 - ], - [ - 39.28094807709286, - -6.798317376152552 - ], - [ - 39.28097704846284, - -6.798338565814496 - ], - [ - 39.280968589108724, - -6.798359909541208 - ], - [ - 39.28098862495911, - -6.798363833982053 - ], - [ - 39.28106384308544, - -6.798560924842693 - ], - [ - 39.281060047781274, - -6.798789172787439 - ], - [ - 39.280964648702366, - -6.79881579282715 - ], - [ - 39.2810549062557, - -6.799182593389792 - ], - [ - 39.281051210712, - -6.799417431324301 - ], - [ - 39.281012045218766, - -6.799461973169469 - ], - [ - 39.28092870982996, - -6.799469616595563 - ], - [ - 39.28102911738072, - -6.800116395040011 - ], - [ - 39.28096755147659, - -6.800136517203233 - ], - [ - 39.28098227562801, - -6.800231504054254 - ], - [ - 39.28092089023231, - -6.800365292854012 - ], - [ - 39.28092243227157, - -6.800466481559217 - ], - [ - 39.28099285109281, - -6.800487505251037 - ], - [ - 39.2809865598948, - -6.80056017366214 - ], - [ - 39.281022039297945, - -6.800676509145848 - ], - [ - 39.28100512867587, - -6.800686027414948 - ], - [ - 39.28094639492564, - -6.800673573505817 - ], - [ - 39.28098960816634, - -6.80080288458438 - ], - [ - 39.28100608830849, - -6.800808079923416 - ], - [ - 39.28101818264724, - -6.800788226357124 - ], - [ - 39.28103090221736, - -6.800799071421509 - ], - [ - 39.28100953313295, - -6.802247229426974 - ], - [ - 39.280969530277055, - -6.802983496860963 - ], - [ - 39.279742849863105, - -6.803751726145401 - ], - [ - 39.279491514203464, - -6.803861587800235 - ], - [ - 39.277158588001264, - -6.804087251350162 - ], - [ - 39.27513747683199, - -6.804142100699255 - ], - [ - 39.27511678207263, - -6.804135692385428 - ], - [ - 39.2751163497056, - -6.804060709416309 - ], - [ - 39.27391722620214, - -6.804061389785589 - ], - [ - 39.27385826397281, - -6.804047806856571 - ], - [ - 39.27384911606231, - -6.803999913343275 - ], - [ - 39.2735965361418, - -6.80398525563719 - ], - [ - 39.273500708320924, - -6.80401301734282 - ], - [ - 39.27346293523808, - -6.804003966970788 - ], - [ - 39.27342162790581, - -6.804025593648304 - ], - [ - 39.27340672680942, - -6.804008608011346 - ], - [ - 39.273416845431285, - -6.803993658369023 - ] - ], - [ - [ - 39.2734931545649, - -6.80398387288763 - ], - [ - 39.27349774748707, - -6.803981148820998 - ], - [ - 39.2734846042507, - -6.80398069373159 - ], - [ - 39.27348722790041, - -6.80398493599873 - ], - [ - 39.2734931545649, - -6.80398387288763 - ] - ], - [ - [ - 39.281045469223024, - -6.796406485350073 - ], - [ - 39.28104614552522, - -6.79640624303539 - ], - [ - 39.28104624219622, - -6.796405856316271 - ], - [ - 39.28104544832558, - -6.796406065132672 - ], - [ - 39.281045469223024, - -6.796406485350073 - ] - ], - [ - [ - 39.25013462741531, - -6.7822284539465 - ], - [ - 39.250139342204406, - -6.782225634227249 - ], - [ - 39.25013885529763, - -6.782223167263246 - ], - [ - 39.25013601751904, - -6.782223433931297 - ], - [ - 39.25013462741531, - -6.7822284539465 - ] - ], - [ - [ - 39.250133340148714, - -6.782197609422264 - ], - [ - 39.25013227568109, - -6.782192336183768 - ], - [ - 39.25013085449504, - -6.782186240541199 - ], - [ - 39.25012984461586, - -6.782195145644085 - ], - [ - 39.250133340148714, - -6.782197609422264 - ] - ] - ], - [ - [ - [ - 39.28101229778558, - -6.800726929325503 - ], - [ - 39.28102979517802, - -6.800722356034623 - ], - [ - 39.28102777800557, - -6.800751554944212 - ], - [ - 39.281011984140875, - -6.800747680790295 - ], - [ - 39.28101229778558, - -6.800726929325503 - ] - ] - ], - [ - [ - [ - 39.28098638863562, - -6.800732494588853 - ], - [ - 39.280997245783674, - -6.800723485911647 - ], - [ - 39.28100583405759, - -6.80073429499442 - ], - [ - 39.280995019972025, - -6.800742416990294 - ], - [ - 39.28098638863562, - -6.800732494588853 - ] - ] - ], - [ - [ - [ - 39.27333576292948, - -6.804020758896268 - ], - [ - 39.27334712034972, - -6.804009856466778 - ], - [ - 39.27335756490539, - -6.804021209097676 - ], - [ - 39.273346206700516, - -6.804030723971451 - ], - [ - 39.27333576292948, - -6.804020758896268 - ] - ] - ], - [ - [ - [ - 39.27338784445309, - -6.804011179704081 - ], - [ - 39.27340165825813, - -6.804007187497802 - ], - [ - 39.27340508925115, - -6.804022507747959 - ], - [ - 39.2733897619233, - -6.804025474696425 - ], - [ - 39.27338784445309, - -6.804011179704081 - ] - ] - ], - [ - [ - [ - 39.28093735076017, - -6.798277365237194 - ], - [ - 39.28094786501646, - -6.798293757944892 - ], - [ - 39.28092636574822, - -6.798297946514793 - ], - [ - 39.28091893391865, - -6.798272295760583 - ], - [ - 39.28093735076017, - -6.798277365237194 - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f883a0", + "properties": { + "area": "dar", + "license": "ODbL-1.0", + "datetime": "2017-11-01T00:00:00Z" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 39.27315718647447, + -6.804030897967275 + ], + [ + 39.273165048748965, + -6.804015735580677 + ], + [ + 39.273213892047806, + -6.804006275126262 + ], + [ + 39.27322980988637, + -6.804022244653453 + ], + [ + 39.273170342425956, + -6.804047430372633 + ], + [ + 39.27315718647447, + -6.804030897967275 + ] + ] + ], + [ + [ + [ + 39.273270319050496, + -6.804022808953547 + ], + [ + 39.273288769025, + -6.803992819792749 + ], + [ + 39.27331911984259, + -6.804029517800871 + ], + [ + 39.2732773025055, + -6.804036732038151 + ], + [ + 39.273270319050496, + -6.804022808953547 + ] + ] + ], + [ + [ + [ + 39.273416845431285, + -6.803993658369023 + ], + [ + 39.273432966277255, + -6.803999383623384 + ], + [ + 39.27345314382025, + -6.803979786475935 + ], + [ + 39.27322440426283, + -6.803973842746188 + ], + [ + 39.273203984545894, + -6.804001738617233 + ], + [ + 39.27307652605739, + -6.804015326271316 + ], + [ + 39.27305748415282, + -6.804061875764365 + ], + [ + 39.27243654623383, + -6.804062182204895 + ], + [ + 39.272422506412305, + -6.804030782430953 + ], + [ + 39.27233818959957, + -6.804033592321105 + ], + [ + 39.27232684217891, + -6.804062287567047 + ], + [ + 39.27050720474849, + -6.804063308351038 + ], + [ + 39.27050003464541, + -6.804267571943514 + ], + [ + 39.26958162504325, + -6.804292771833833 + ], + [ + 39.268183769535355, + -6.804289804955942 + ], + [ + 39.26817548170555, + -6.804064606402513 + ], + [ + 39.26638040640152, + -6.804065554449549 + ], + [ + 39.26635869387405, + -6.80392326747663 + ], + [ + 39.26612966449484, + -6.803973347620189 + ], + [ + 39.2660934787131, + -6.804065755940294 + ], + [ + 39.26187296555017, + -6.804068058726235 + ], + [ + 39.26184507218486, + -6.804052028033205 + ], + [ + 39.26184066298603, + -6.804023191210663 + ], + [ + 39.26172396812868, + -6.804068139349033 + ], + [ + 39.25777183400359, + -6.804070261106554 + ], + [ + 39.257755345525474, + -6.804004808291634 + ], + [ + 39.25731554868908, + -6.804070460389402 + ], + [ + 39.256625459443185, + -6.804070870513325 + ], + [ + 39.25662348593693, + -6.80422297079218 + ], + [ + 39.256462846950114, + -6.804260546096121 + ], + [ + 39.256084987159134, + -6.804259012772288 + ], + [ + 39.256069700572326, + -6.804123376288454 + ], + [ + 39.25557123594966, + -6.804199546422531 + ], + [ + 39.25540366700173, + -6.804257868759697 + ], + [ + 39.25364580884159, + -6.804253286905217 + ], + [ + 39.25336404330564, + -6.804245528109689 + ], + [ + 39.25184949473127, + -6.804063493477143 + ], + [ + 39.25045725022109, + -6.803626776738144 + ], + [ + 39.250038582443665, + -6.80287820958753 + ], + [ + 39.25000304212214, + -6.802509337350906 + ], + [ + 39.24988415760574, + -6.799080315984306 + ], + [ + 39.24990450849855, + -6.799063247023449 + ], + [ + 39.2498947561629, + -6.798995102709657 + ], + [ + 39.24988086958492, + -6.79898550113401 + ], + [ + 39.249810881599906, + -6.796966175954005 + ], + [ + 39.24981645844359, + -6.796947177743604 + ], + [ + 39.249872411376465, + -6.796943310209003 + ], + [ + 39.249872317302916, + -6.79667664523118 + ], + [ + 39.24989594446884, + -6.796632192786874 + ], + [ + 39.2498929959209, + -6.796620487479649 + ], + [ + 39.24981140260522, + -6.796653264268834 + ], + [ + 39.249799651394305, + -6.796639604421224 + ], + [ + 39.249702848316815, + -6.793849310612861 + ], + [ + 39.249709580102994, + -6.793444194527318 + ], + [ + 39.24968269293236, + -6.793263269989816 + ], + [ + 39.24966102436161, + -6.792603766179727 + ], + [ + 39.24967954445545, + -6.791728669025114 + ], + [ + 39.24970804593678, + -6.791366446494281 + ], + [ + 39.24968767919196, + -6.791352173642583 + ], + [ + 39.24969901238331, + -6.790812445435837 + ], + [ + 39.24971718787532, + -6.790709956750732 + ], + [ + 39.24976000940216, + -6.790676190366502 + ], + [ + 39.24974335811433, + -6.79066757715095 + ], + [ + 39.24975954380845, + -6.7905720968061 + ], + [ + 39.24971030856871, + -6.790285552729824 + ], + [ + 39.24971251701262, + -6.790166259808814 + ], + [ + 39.249755177302276, + -6.79007657102466 + ], + [ + 39.24978363439594, + -6.790069338302176 + ], + [ + 39.24973059995279, + -6.789901141277205 + ], + [ + 39.249748519583505, + -6.789843130737693 + ], + [ + 39.249722462770514, + -6.789712522643217 + ], + [ + 39.249757283518974, + -6.788060421836336 + ], + [ + 39.24981581028674, + -6.788035256591358 + ], + [ + 39.2497613539383, + -6.787878110167098 + ], + [ + 39.249768572097224, + -6.787518805148783 + ], + [ + 39.25004009533414, + -6.787434103342062 + ], + [ + 39.25002896611271, + -6.787302674153842 + ], + [ + 39.25004835160351, + -6.787293592535141 + ], + [ + 39.25004462643536, + -6.787248062083075 + ], + [ + 39.249913527740446, + -6.787275621098578 + ], + [ + 39.24987623121805, + -6.787223585244646 + ], + [ + 39.2498870533108, + -6.78675947061919 + ], + [ + 39.249878668979136, + -6.786651689669421 + ], + [ + 39.249854185335714, + -6.786644521595329 + ], + [ + 39.24985427604204, + -6.786567866890232 + ], + [ + 39.249878150583925, + -6.786543683174986 + ], + [ + 39.24987767258752, + -6.786514031590554 + ], + [ + 39.24985595176083, + -6.786480788612282 + ], + [ + 39.24985427458511, + -6.786004693765936 + ], + [ + 39.249835799952045, + -6.785985440085538 + ], + [ + 39.24981894796509, + -6.785692711962167 + ], + [ + 39.249835592123326, + -6.785666857043904 + ], + [ + 39.2498132441077, + -6.785399014698585 + ], + [ + 39.24982665102962, + -6.785376346020647 + ], + [ + 39.24981460672529, + -6.785349524356427 + ], + [ + 39.24981956779118, + -6.785099760997835 + ], + [ + 39.249871024621996, + -6.783491089857915 + ], + [ + 39.24990124402507, + -6.783476958079939 + ], + [ + 39.24987648913789, + -6.783320417149405 + ], + [ + 39.24990659151731, + -6.782389387144009 + ], + [ + 39.25010086176018, + -6.782346474227426 + ], + [ + 39.2501017767606, + -6.78232629074711 + ], + [ + 39.250066934399854, + -6.782156990037679 + ], + [ + 39.24992289431197, + -6.782182852009128 + ], + [ + 39.2499138298333, + -6.782153922351434 + ], + [ + 39.25006519231678, + -6.777778404257647 + ], + [ + 39.250159011995514, + -6.775687803188281 + ], + [ + 39.250652695356244, + -6.774710612955933 + ], + [ + 39.25973527817753, + -6.774051636265581 + ], + [ + 39.26420907625315, + -6.774027485936578 + ], + [ + 39.26430505647687, + -6.774065405657963 + ], + [ + 39.26433909336156, + -6.774026490186999 + ], + [ + 39.27016488290843, + -6.773994609904983 + ], + [ + 39.27021692938301, + -6.773995269543569 + ], + [ + 39.27026644245446, + -6.774033162706626 + ], + [ + 39.27031016915434, + -6.773994066588543 + ], + [ + 39.27159183301611, + -6.773986876672367 + ], + [ + 39.27168780046295, + -6.774108785772873 + ], + [ + 39.271686763240815, + -6.774136172079665 + ], + [ + 39.27192122999216, + -6.774436870625464 + ], + [ + 39.27225338516659, + -6.773984336984419 + ], + [ + 39.27280594364432, + -6.773980646345491 + ], + [ + 39.274209296190605, + -6.774041415140496 + ], + [ + 39.27802675727596, + -6.774426374513278 + ], + [ + 39.27864214751531, + -6.774515880892984 + ], + [ + 39.280023369706576, + -6.774755350012424 + ], + [ + 39.28050916887765, + -6.775147428625842 + ], + [ + 39.28074438487075, + -6.775756768054177 + ], + [ + 39.28081527672994, + -6.776054440091325 + ], + [ + 39.28082717168011, + -6.776695908769849 + ], + [ + 39.2807657423886, + -6.776717815104587 + ], + [ + 39.280775518747866, + -6.776754016935989 + ], + [ + 39.280818510912624, + -6.776750660815322 + ], + [ + 39.28082911053632, + -6.776772724163326 + ], + [ + 39.28091689459758, + -6.781345598621182 + ], + [ + 39.28080388738358, + -6.7813839793259 + ], + [ + 39.28084388776356, + -6.781454612139605 + ], + [ + 39.28085319429884, + -6.78143154903115 + ], + [ + 39.28087584049512, + -6.781437812905911 + ], + [ + 39.28086609036416, + -6.781472364858208 + ], + [ + 39.280881881877654, + -6.781483136266438 + ], + [ + 39.28086943504425, + -6.78149915312294 + ], + [ + 39.28088554011426, + -6.781534324494014 + ], + [ + 39.280873569959816, + -6.781638136661505 + ], + [ + 39.28092499494767, + -6.781750298004826 + ], + [ + 39.28095457939287, + -6.783298331704033 + ], + [ + 39.28086026889085, + -6.783333866874134 + ], + [ + 39.280959342849336, + -6.783533751067348 + ], + [ + 39.28096937002043, + -6.784069342141371 + ], + [ + 39.28095243868693, + -6.78408149778495 + ], + [ + 39.280970796197394, + -6.784135018997731 + ], + [ + 39.280990362247046, + -6.785161799575521 + ], + [ + 39.28090314512198, + -6.785218441815514 + ], + [ + 39.28094438037573, + -6.785212514854102 + ], + [ + 39.280994123789, + -6.785340368033896 + ], + [ + 39.28103471876776, + -6.787446616974048 + ], + [ + 39.281027381291025, + -6.787464990541269 + ], + [ + 39.280827513135605, + -6.787500088788153 + ], + [ + 39.28099960926122, + -6.78779064973444 + ], + [ + 39.28100210101566, + -6.787851081760929 + ], + [ + 39.281026585687755, + -6.787859226828259 + ], + [ + 39.28102285279996, + -6.787966432946407 + ], + [ + 39.28093203937434, + -6.788005057346286 + ], + [ + 39.28100675911835, + -6.788204324299769 + ], + [ + 39.280997459334884, + -6.788308436132202 + ], + [ + 39.28105380334917, + -6.788440096324056 + ], + [ + 39.281068553938496, + -6.789216155958014 + ], + [ + 39.28096066849781, + -6.789245774186083 + ], + [ + 39.281073839006595, + -6.789477514362669 + ], + [ + 39.28114397070973, + -6.793117958745426 + ], + [ + 39.28113420729278, + -6.79383486840148 + ], + [ + 39.281114726156694, + -6.793847842189681 + ], + [ + 39.28113330952829, + -6.79388250834817 + ], + [ + 39.281129262608935, + -6.794159983557968 + ], + [ + 39.28109084175889, + -6.794179680568165 + ], + [ + 39.28112752361519, + -6.794277502669122 + ], + [ + 39.28111635395094, + -6.795038314028078 + ], + [ + 39.281016070626016, + -6.795075741198945 + ], + [ + 39.281112775297714, + -6.795250226366647 + ], + [ + 39.2810949704288, + -6.79646195887955 + ], + [ + 39.2809583747182, + -6.796479910277594 + ], + [ + 39.280976796828014, + -6.79655746965899 + ], + [ + 39.28106599069538, + -6.796757139303622 + ], + [ + 39.28105528128861, + -6.79686803066901 + ], + [ + 39.281087453345336, + -6.796987885295481 + ], + [ + 39.28108073143122, + -6.797439731548687 + ], + [ + 39.28083992809423, + -6.797461906062759 + ], + [ + 39.28083642610969, + -6.797501481434426 + ], + [ + 39.28072006370183, + -6.797559721622083 + ], + [ + 39.280792511077834, + -6.797755460113359 + ], + [ + 39.280782299142885, + -6.797822644537164 + ], + [ + 39.28087877377628, + -6.798094898764859 + ], + [ + 39.28087160779872, + -6.798109486776828 + ], + [ + 39.28084495591411, + -6.798106189716947 + ], + [ + 39.28091358669126, + -6.798282661131834 + ], + [ + 39.280894058903186, + -6.798303988028182 + ], + [ + 39.28089993648302, + -6.798318476273004 + ], + [ + 39.28094807709286, + -6.798317376152552 + ], + [ + 39.28097704846284, + -6.798338565814496 + ], + [ + 39.280968589108724, + -6.798359909541208 + ], + [ + 39.28098862495911, + -6.798363833982053 + ], + [ + 39.28106384308544, + -6.798560924842693 + ], + [ + 39.281060047781274, + -6.798789172787439 + ], + [ + 39.280964648702366, + -6.79881579282715 + ], + [ + 39.2810549062557, + -6.799182593389792 + ], + [ + 39.281051210712, + -6.799417431324301 + ], + [ + 39.281012045218766, + -6.799461973169469 + ], + [ + 39.28092870982996, + -6.799469616595563 + ], + [ + 39.28102911738072, + -6.800116395040011 + ], + [ + 39.28096755147659, + -6.800136517203233 + ], + [ + 39.28098227562801, + -6.800231504054254 + ], + [ + 39.28092089023231, + -6.800365292854012 + ], + [ + 39.28092243227157, + -6.800466481559217 + ], + [ + 39.28099285109281, + -6.800487505251037 + ], + [ + 39.2809865598948, + -6.80056017366214 + ], + [ + 39.281022039297945, + -6.800676509145848 + ], + [ + 39.28100512867587, + -6.800686027414948 + ], + [ + 39.28094639492564, + -6.800673573505817 + ], + [ + 39.28098960816634, + -6.80080288458438 + ], + [ + 39.28100608830849, + -6.800808079923416 + ], + [ + 39.28101818264724, + -6.800788226357124 + ], + [ + 39.28103090221736, + -6.800799071421509 + ], + [ + 39.28100953313295, + -6.802247229426974 + ], + [ + 39.280969530277055, + -6.802983496860963 + ], + [ + 39.279742849863105, + -6.803751726145401 + ], + [ + 39.279491514203464, + -6.803861587800235 + ], + [ + 39.277158588001264, + -6.804087251350162 + ], + [ + 39.27513747683199, + -6.804142100699255 + ], + [ + 39.27511678207263, + -6.804135692385428 + ], + [ + 39.2751163497056, + -6.804060709416309 + ], + [ + 39.27391722620214, + -6.804061389785589 + ], + [ + 39.27385826397281, + -6.804047806856571 + ], + [ + 39.27384911606231, + -6.803999913343275 + ], + [ + 39.2735965361418, + -6.80398525563719 + ], + [ + 39.273500708320924, + -6.80401301734282 + ], + [ + 39.27346293523808, + -6.804003966970788 + ], + [ + 39.27342162790581, + -6.804025593648304 + ], + [ + 39.27340672680942, + -6.804008608011346 + ], + [ + 39.273416845431285, + -6.803993658369023 + ] + ], + [ + [ + 39.2734931545649, + -6.80398387288763 + ], + [ + 39.27349774748707, + -6.803981148820998 + ], + [ + 39.2734846042507, + -6.80398069373159 + ], + [ + 39.27348722790041, + -6.80398493599873 + ], + [ + 39.2734931545649, + -6.80398387288763 + ] + ], + [ + [ + 39.281045469223024, + -6.796406485350073 + ], + [ + 39.28104614552522, + -6.79640624303539 + ], + [ + 39.28104624219622, + -6.796405856316271 + ], + [ + 39.28104544832558, + -6.796406065132672 + ], + [ + 39.281045469223024, + -6.796406485350073 + ] + ], + [ + [ + 39.25013462741531, + -6.7822284539465 + ], + [ + 39.250139342204406, + -6.782225634227249 + ], + [ + 39.25013885529763, + -6.782223167263246 + ], + [ + 39.25013601751904, + -6.782223433931297 + ], + [ + 39.25013462741531, + -6.7822284539465 + ] + ], + [ + [ + 39.250133340148714, + -6.782197609422264 + ], + [ + 39.25013227568109, + -6.782192336183768 + ], + [ + 39.25013085449504, + -6.782186240541199 + ], + [ + 39.25012984461586, + -6.782195145644085 + ], + [ + 39.250133340148714, + -6.782197609422264 + ] + ] + ], + [ + [ + [ + 39.28101229778558, + -6.800726929325503 + ], + [ + 39.28102979517802, + -6.800722356034623 + ], + [ + 39.28102777800557, + -6.800751554944212 + ], + [ + 39.281011984140875, + -6.800747680790295 + ], + [ + 39.28101229778558, + -6.800726929325503 + ] + ] + ], + [ + [ + [ + 39.28098638863562, + -6.800732494588853 + ], + [ + 39.280997245783674, + -6.800723485911647 + ], + [ + 39.28100583405759, + -6.80073429499442 + ], + [ + 39.280995019972025, + -6.800742416990294 + ], + [ + 39.28098638863562, + -6.800732494588853 + ] + ] + ], + [ + [ + [ + 39.27333576292948, + -6.804020758896268 + ], + [ + 39.27334712034972, + -6.804009856466778 + ], + [ + 39.27335756490539, + -6.804021209097676 + ], + [ + 39.273346206700516, + -6.804030723971451 + ], + [ + 39.27333576292948, + -6.804020758896268 + ] + ] + ], + [ + [ + [ + 39.27338784445309, + -6.804011179704081 + ], + [ + 39.27340165825813, + -6.804007187497802 + ], + [ + 39.27340508925115, + -6.804022507747959 + ], + [ + 39.2733897619233, + -6.804025474696425 + ], + [ + 39.27338784445309, + -6.804011179704081 + ] + ] + ], + [ + [ + [ + 39.28093735076017, + -6.798277365237194 + ], + [ + 39.28094786501646, + -6.798293757944892 + ], + [ + 39.28092636574822, + -6.798297946514793 + ], + [ + 39.28091893391865, + -6.798272295760583 + ], + [ + 39.28093735076017, + -6.798277365237194 + ] + ] + ] ] - ] - ] - }, - "bbox": [ - 39.24966102436161, - -6.804292908938633, - 39.28114400293776, - -6.773980646345491 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/f883a0/f883a0.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/dar/f883a0/f883a0.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "dar" + "bbox": [ + 39.24966102436161, + -6.804292908938633, + 39.28114400293776, + -6.773980646345491 + ], + "stac_extensions": [], + "collection": "dar" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json b/tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json index 1f8c8569e..38250e5ff 100644 --- a/tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json +++ b/tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json @@ -1,425 +1,426 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "4e7c7f-labels", - "properties": { - "label:description": "Geojson building labels for scene 4e7c7f", - "area": "kam", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 4056 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-09-17T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 32.62648054593931, - 0.256417512540714 - ], - [ - 32.627126980840195, - 0.25620295116935593 - ], - [ - 32.62783415844573, - 0.255410994304475 - ], - [ - 32.629199259432276, - 0.2543302475722729 - ], - [ - 32.629052083887395, - 0.25414046858019 - ], - [ - 32.62942671102056, - 0.253927225241941 - ], - [ - 32.63011734620023, - 0.25322826915649893 - ], - [ - 32.6311796485047, - 0.252518218530017 - ], - [ - 32.63161216376019, - 0.25231626643179605 - ], - [ - 32.63213655266929, - 0.251658391599513 - ], - [ - 32.63272372642811, - 0.2511272429507839 - ], - [ - 32.63254169351733, - 0.25084451098298594 - ], - [ - 32.63314588785948, - 0.25055790597453304 - ], - [ - 32.633568049290844, - 0.251150481194712 - ], - [ - 32.633925547411806, - 0.25091228371465596 - ], - [ - 32.63412964018579, - 0.251274418495665 - ], - [ - 32.635159868999956, - 0.25041460347030703 - ], - [ - 32.635167615081265, - 0.24997307683566403 - ], - [ - 32.635764063342094, - 0.24950443891643803 - ], - [ - 32.63606616051317, - 0.24964386838000902 - ], - [ - 32.636248193423945, - 0.24927592951780594 - ], - [ - 32.63745658210823, - 0.24989174298191394 - ], - [ - 32.63721258054698, - 0.2503681269824499 - ], - [ - 32.63840929998812, - 0.250942713819728 - ], - [ - 32.63778349823557, - 0.2520010873099949 - ], - [ - 32.6377025843977, - 0.25218375675407106 - ], - [ - 32.63804242251679, - 0.25231321889467795 - ], - [ - 32.63796150867891, - 0.252685422548926 - ], - [ - 32.638649276300896, - 0.25271778808407813 - ], - [ - 32.638584545230586, - 0.25419041993349195 - ], - [ - 32.63860072799817, - 0.256917216270045 - ], - [ - 32.638649276300896, - 0.25820374629233506 - ], - [ - 32.638325620949374, - 0.258195654908547 - ], - [ - 32.63775113270042, - 0.258527401643855 - ], - [ - 32.638438900322406, - 0.25946600216326193 - ], - [ - 32.63706336507845, - 0.26075253218555194 - ], - [ - 32.63691772017026, - 0.26070398388282406 - ], - [ - 32.636747801110715, - 0.26080108048828 - ], - [ - 32.636950085705415, - 0.261068096153284 - ], - [ - 32.63539468442814, - 0.26201630282476607 - ], - [ - 32.63522848916817, - 0.26190455083961195 - ], - [ - 32.63505942847269, - 0.26207074609958386 - ], - [ - 32.63460382422552, - 0.26152344791588394 - ], - [ - 32.634022140815624, - 0.2612884822035099 - ], - [ - 32.63370407747327, - 0.261259827848342 - ], - [ - 32.63349203524503, - 0.261454677463481 - ], - [ - 32.63296766054546, - 0.260944629941499 - ], - [ - 32.63280719615652, - 0.261047785620102 - ], - [ - 32.632423227797275, - 0.260606508550522 - ], - [ - 32.632497729120715, - 0.260509083742952 - ], - [ - 32.63205645205113, - 0.2602569254174769 - ], - [ - 32.63213095337457, - 0.26007926841543794 - ], - [ - 32.63202493226045, - 0.259950323817184 - ], - [ - 32.631772773934976, - 0.259703896362743 - ], - [ - 32.631672483691894, - 0.25962939503930793 - ], - [ - 32.63148049951227, - 0.259586413506556 - ], - [ - 32.63135155491401, - 0.2596494530879249 - ], - [ - 32.63109939658854, - 0.259308466261431 - ], - [ - 32.631053549620276, - 0.259305600825914 - ], - [ - 32.630712562793775, - 0.2595061813120871 - ], - [ - 32.63042028837107, - 0.25911361664629196 - ], - [ - 32.63052630948519, - 0.25900759553217195 - ], - [ - 32.63037444140281, - 0.25878409156186494 - ], - [ - 32.63023116962697, - 0.2588901126759849 - ], - [ - 32.630067839802514, - 0.25860643455982596 - ], - [ - 32.629947491510805, - 0.2587267828515289 - ], - [ - 32.62981568147704, - 0.258560587591558 - ], - [ - 32.6297411801536, - 0.258620761737409 - ], - [ - 32.62950334900572, - 0.25829983295953296 - ], - [ - 32.629362942665395, - 0.2583857960250349 - ], - [ - 32.629236863502655, - 0.25821673532954686 - ], - [ - 32.6288328370948, - 0.258431642993304 - ], - [ - 32.62873254685171, - 0.25833421818573393 - ], - [ - 32.628357174799014, - 0.25857204933362493 - ], - [ - 32.62818689539351, - 0.25834584330486 - ], - [ - 32.627884377938756, - 0.258560587591558 - ], - [ - 32.627586372645005, - 0.258119310521977 - ], - [ - 32.62737719585229, - 0.25788721024511996 - ], - [ - 32.6273284834485, - 0.257947384390972 - ], - [ - 32.62717661536612, - 0.257821305228234 - ], - [ - 32.62729123278679, - 0.25772101498514793 - ], - [ - 32.627130768397855, - 0.257554819725176 - ], - [ - 32.62700755467063, - 0.25764937909722896 - ], - [ - 32.6265576812945, - 0.2572969305286679 - ], - [ - 32.626179443806286, - 0.256944481960107 - ], - [ - 32.6264545256159, - 0.2566779964570488 - ], - [ - 32.62631411927558, - 0.256600629698096 - ], - [ - 32.62648054593931, - 0.256417512540714 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "4e7c7f-labels", + "properties": { + "label:description": "Geojson building labels for scene 4e7c7f", + "area": "kam", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 4056 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-09-17T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 32.626179443806286, - 0.24927592951780594, - 32.638649276300896, - 0.26207074609958386 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 32.62648054593931, + 0.256417512540714 + ], + [ + 32.627126980840195, + 0.25620295116935593 + ], + [ + 32.62783415844573, + 0.255410994304475 + ], + [ + 32.629199259432276, + 0.2543302475722729 + ], + [ + 32.629052083887395, + 0.25414046858019 + ], + [ + 32.62942671102056, + 0.253927225241941 + ], + [ + 32.63011734620023, + 0.25322826915649893 + ], + [ + 32.6311796485047, + 0.252518218530017 + ], + [ + 32.63161216376019, + 0.25231626643179605 + ], + [ + 32.63213655266929, + 0.251658391599513 + ], + [ + 32.63272372642811, + 0.2511272429507839 + ], + [ + 32.63254169351733, + 0.25084451098298594 + ], + [ + 32.63314588785948, + 0.25055790597453304 + ], + [ + 32.633568049290844, + 0.251150481194712 + ], + [ + 32.633925547411806, + 0.25091228371465596 + ], + [ + 32.63412964018579, + 0.251274418495665 + ], + [ + 32.635159868999956, + 0.25041460347030703 + ], + [ + 32.635167615081265, + 0.24997307683566403 + ], + [ + 32.635764063342094, + 0.24950443891643803 + ], + [ + 32.63606616051317, + 0.24964386838000902 + ], + [ + 32.636248193423945, + 0.24927592951780594 + ], + [ + 32.63745658210823, + 0.24989174298191394 + ], + [ + 32.63721258054698, + 0.2503681269824499 + ], + [ + 32.63840929998812, + 0.250942713819728 + ], + [ + 32.63778349823557, + 0.2520010873099949 + ], + [ + 32.6377025843977, + 0.25218375675407106 + ], + [ + 32.63804242251679, + 0.25231321889467795 + ], + [ + 32.63796150867891, + 0.252685422548926 + ], + [ + 32.638649276300896, + 0.25271778808407813 + ], + [ + 32.638584545230586, + 0.25419041993349195 + ], + [ + 32.63860072799817, + 0.256917216270045 + ], + [ + 32.638649276300896, + 0.25820374629233506 + ], + [ + 32.638325620949374, + 0.258195654908547 + ], + [ + 32.63775113270042, + 0.258527401643855 + ], + [ + 32.638438900322406, + 0.25946600216326193 + ], + [ + 32.63706336507845, + 0.26075253218555194 + ], + [ + 32.63691772017026, + 0.26070398388282406 + ], + [ + 32.636747801110715, + 0.26080108048828 + ], + [ + 32.636950085705415, + 0.261068096153284 + ], + [ + 32.63539468442814, + 0.26201630282476607 + ], + [ + 32.63522848916817, + 0.26190455083961195 + ], + [ + 32.63505942847269, + 0.26207074609958386 + ], + [ + 32.63460382422552, + 0.26152344791588394 + ], + [ + 32.634022140815624, + 0.2612884822035099 + ], + [ + 32.63370407747327, + 0.261259827848342 + ], + [ + 32.63349203524503, + 0.261454677463481 + ], + [ + 32.63296766054546, + 0.260944629941499 + ], + [ + 32.63280719615652, + 0.261047785620102 + ], + [ + 32.632423227797275, + 0.260606508550522 + ], + [ + 32.632497729120715, + 0.260509083742952 + ], + [ + 32.63205645205113, + 0.2602569254174769 + ], + [ + 32.63213095337457, + 0.26007926841543794 + ], + [ + 32.63202493226045, + 0.259950323817184 + ], + [ + 32.631772773934976, + 0.259703896362743 + ], + [ + 32.631672483691894, + 0.25962939503930793 + ], + [ + 32.63148049951227, + 0.259586413506556 + ], + [ + 32.63135155491401, + 0.2596494530879249 + ], + [ + 32.63109939658854, + 0.259308466261431 + ], + [ + 32.631053549620276, + 0.259305600825914 + ], + [ + 32.630712562793775, + 0.2595061813120871 + ], + [ + 32.63042028837107, + 0.25911361664629196 + ], + [ + 32.63052630948519, + 0.25900759553217195 + ], + [ + 32.63037444140281, + 0.25878409156186494 + ], + [ + 32.63023116962697, + 0.2588901126759849 + ], + [ + 32.630067839802514, + 0.25860643455982596 + ], + [ + 32.629947491510805, + 0.2587267828515289 + ], + [ + 32.62981568147704, + 0.258560587591558 + ], + [ + 32.6297411801536, + 0.258620761737409 + ], + [ + 32.62950334900572, + 0.25829983295953296 + ], + [ + 32.629362942665395, + 0.2583857960250349 + ], + [ + 32.629236863502655, + 0.25821673532954686 + ], + [ + 32.6288328370948, + 0.258431642993304 + ], + [ + 32.62873254685171, + 0.25833421818573393 + ], + [ + 32.628357174799014, + 0.25857204933362493 + ], + [ + 32.62818689539351, + 0.25834584330486 + ], + [ + 32.627884377938756, + 0.258560587591558 + ], + [ + 32.627586372645005, + 0.258119310521977 + ], + [ + 32.62737719585229, + 0.25788721024511996 + ], + [ + 32.6273284834485, + 0.257947384390972 + ], + [ + 32.62717661536612, + 0.257821305228234 + ], + [ + 32.62729123278679, + 0.25772101498514793 + ], + [ + 32.627130768397855, + 0.257554819725176 + ], + [ + 32.62700755467063, + 0.25764937909722896 + ], + [ + 32.6265576812945, + 0.2572969305286679 + ], + [ + 32.626179443806286, + 0.256944481960107 + ], + [ + 32.6264545256159, + 0.2566779964570488 + ], + [ + 32.62631411927558, + 0.256600629698096 + ], + [ + 32.62648054593931, + 0.256417512540714 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/kam/4e7c7f-labels/4e7c7f.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/kam/4e7c7f-labels/4e7c7f.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 32.626179443806286, + 0.24927592951780594, + 32.638649276300896, + 0.26207074609958386 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "kam" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json b/tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json index d686c181d..a49d7626f 100644 --- a/tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json +++ b/tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json @@ -1,400 +1,401 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "4e7c7f", - "properties": { - "area": "kam", - "license": "CC-BY-4.0", - "datetime": "2018-09-17T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 32.62648054593931, - 0.256417512540714 - ], - [ - 32.627126980840195, - 0.25620295116935593 - ], - [ - 32.62783415844573, - 0.255410994304475 - ], - [ - 32.629199259432276, - 0.2543302475722729 - ], - [ - 32.629052083887395, - 0.25414046858019 - ], - [ - 32.62942671102056, - 0.253927225241941 - ], - [ - 32.63011734620023, - 0.25322826915649893 - ], - [ - 32.6311796485047, - 0.252518218530017 - ], - [ - 32.63161216376019, - 0.25231626643179605 - ], - [ - 32.63213655266929, - 0.251658391599513 - ], - [ - 32.63272372642811, - 0.2511272429507839 - ], - [ - 32.63254169351733, - 0.25084451098298594 - ], - [ - 32.63314588785948, - 0.25055790597453304 - ], - [ - 32.633568049290844, - 0.251150481194712 - ], - [ - 32.633925547411806, - 0.25091228371465596 - ], - [ - 32.63412964018579, - 0.251274418495665 - ], - [ - 32.635159868999956, - 0.25041460347030703 - ], - [ - 32.635167615081265, - 0.24997307683566403 - ], - [ - 32.635764063342094, - 0.24950443891643803 - ], - [ - 32.63606616051317, - 0.24964386838000902 - ], - [ - 32.636248193423945, - 0.24927592951780594 - ], - [ - 32.63745658210823, - 0.24989174298191394 - ], - [ - 32.63721258054698, - 0.2503681269824499 - ], - [ - 32.63840929998812, - 0.250942713819728 - ], - [ - 32.63778349823557, - 0.2520010873099949 - ], - [ - 32.6377025843977, - 0.25218375675407106 - ], - [ - 32.63804242251679, - 0.25231321889467795 - ], - [ - 32.63796150867891, - 0.252685422548926 - ], - [ - 32.638649276300896, - 0.25271778808407813 - ], - [ - 32.638584545230586, - 0.25419041993349195 - ], - [ - 32.63860072799817, - 0.256917216270045 - ], - [ - 32.638649276300896, - 0.25820374629233506 - ], - [ - 32.638325620949374, - 0.258195654908547 - ], - [ - 32.63775113270042, - 0.258527401643855 - ], - [ - 32.638438900322406, - 0.25946600216326193 - ], - [ - 32.63706336507845, - 0.26075253218555194 - ], - [ - 32.63691772017026, - 0.26070398388282406 - ], - [ - 32.636747801110715, - 0.26080108048828 - ], - [ - 32.636950085705415, - 0.261068096153284 - ], - [ - 32.63539468442814, - 0.26201630282476607 - ], - [ - 32.63522848916817, - 0.26190455083961195 - ], - [ - 32.63505942847269, - 0.26207074609958386 - ], - [ - 32.63460382422552, - 0.26152344791588394 - ], - [ - 32.634022140815624, - 0.2612884822035099 - ], - [ - 32.63370407747327, - 0.261259827848342 - ], - [ - 32.63349203524503, - 0.261454677463481 - ], - [ - 32.63296766054546, - 0.260944629941499 - ], - [ - 32.63280719615652, - 0.261047785620102 - ], - [ - 32.632423227797275, - 0.260606508550522 - ], - [ - 32.632497729120715, - 0.260509083742952 - ], - [ - 32.63205645205113, - 0.2602569254174769 - ], - [ - 32.63213095337457, - 0.26007926841543794 - ], - [ - 32.63202493226045, - 0.259950323817184 - ], - [ - 32.631772773934976, - 0.259703896362743 - ], - [ - 32.631672483691894, - 0.25962939503930793 - ], - [ - 32.63148049951227, - 0.259586413506556 - ], - [ - 32.63135155491401, - 0.2596494530879249 - ], - [ - 32.63109939658854, - 0.259308466261431 - ], - [ - 32.631053549620276, - 0.259305600825914 - ], - [ - 32.630712562793775, - 0.2595061813120871 - ], - [ - 32.63042028837107, - 0.25911361664629196 - ], - [ - 32.63052630948519, - 0.25900759553217195 - ], - [ - 32.63037444140281, - 0.25878409156186494 - ], - [ - 32.63023116962697, - 0.2588901126759849 - ], - [ - 32.630067839802514, - 0.25860643455982596 - ], - [ - 32.629947491510805, - 0.2587267828515289 - ], - [ - 32.62981568147704, - 0.258560587591558 - ], - [ - 32.6297411801536, - 0.258620761737409 - ], - [ - 32.62950334900572, - 0.25829983295953296 - ], - [ - 32.629362942665395, - 0.2583857960250349 - ], - [ - 32.629236863502655, - 0.25821673532954686 - ], - [ - 32.6288328370948, - 0.258431642993304 - ], - [ - 32.62873254685171, - 0.25833421818573393 - ], - [ - 32.628357174799014, - 0.25857204933362493 - ], - [ - 32.62818689539351, - 0.25834584330486 - ], - [ - 32.627884377938756, - 0.258560587591558 - ], - [ - 32.627586372645005, - 0.258119310521977 - ], - [ - 32.62737719585229, - 0.25788721024511996 - ], - [ - 32.6273284834485, - 0.257947384390972 - ], - [ - 32.62717661536612, - 0.257821305228234 - ], - [ - 32.62729123278679, - 0.25772101498514793 - ], - [ - 32.627130768397855, - 0.257554819725176 - ], - [ - 32.62700755467063, - 0.25764937909722896 - ], - [ - 32.6265576812945, - 0.2572969305286679 - ], - [ - 32.626179443806286, - 0.256944481960107 - ], - [ - 32.6264545256159, - 0.2566779964570488 - ], - [ - 32.62631411927558, - 0.256600629698096 - ], - [ - 32.62648054593931, - 0.256417512540714 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 32.626179443806286, - 0.24927592951780594, - 32.638649276300896, - 0.26207074609958386 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "4e7c7f", + "properties": { + "area": "kam", + "license": "CC-BY-4.0", + "datetime": "2018-09-17T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 32.62648054593931, + 0.256417512540714 + ], + [ + 32.627126980840195, + 0.25620295116935593 + ], + [ + 32.62783415844573, + 0.255410994304475 + ], + [ + 32.629199259432276, + 0.2543302475722729 + ], + [ + 32.629052083887395, + 0.25414046858019 + ], + [ + 32.62942671102056, + 0.253927225241941 + ], + [ + 32.63011734620023, + 0.25322826915649893 + ], + [ + 32.6311796485047, + 0.252518218530017 + ], + [ + 32.63161216376019, + 0.25231626643179605 + ], + [ + 32.63213655266929, + 0.251658391599513 + ], + [ + 32.63272372642811, + 0.2511272429507839 + ], + [ + 32.63254169351733, + 0.25084451098298594 + ], + [ + 32.63314588785948, + 0.25055790597453304 + ], + [ + 32.633568049290844, + 0.251150481194712 + ], + [ + 32.633925547411806, + 0.25091228371465596 + ], + [ + 32.63412964018579, + 0.251274418495665 + ], + [ + 32.635159868999956, + 0.25041460347030703 + ], + [ + 32.635167615081265, + 0.24997307683566403 + ], + [ + 32.635764063342094, + 0.24950443891643803 + ], + [ + 32.63606616051317, + 0.24964386838000902 + ], + [ + 32.636248193423945, + 0.24927592951780594 + ], + [ + 32.63745658210823, + 0.24989174298191394 + ], + [ + 32.63721258054698, + 0.2503681269824499 + ], + [ + 32.63840929998812, + 0.250942713819728 + ], + [ + 32.63778349823557, + 0.2520010873099949 + ], + [ + 32.6377025843977, + 0.25218375675407106 + ], + [ + 32.63804242251679, + 0.25231321889467795 + ], + [ + 32.63796150867891, + 0.252685422548926 + ], + [ + 32.638649276300896, + 0.25271778808407813 + ], + [ + 32.638584545230586, + 0.25419041993349195 + ], + [ + 32.63860072799817, + 0.256917216270045 + ], + [ + 32.638649276300896, + 0.25820374629233506 + ], + [ + 32.638325620949374, + 0.258195654908547 + ], + [ + 32.63775113270042, + 0.258527401643855 + ], + [ + 32.638438900322406, + 0.25946600216326193 + ], + [ + 32.63706336507845, + 0.26075253218555194 + ], + [ + 32.63691772017026, + 0.26070398388282406 + ], + [ + 32.636747801110715, + 0.26080108048828 + ], + [ + 32.636950085705415, + 0.261068096153284 + ], + [ + 32.63539468442814, + 0.26201630282476607 + ], + [ + 32.63522848916817, + 0.26190455083961195 + ], + [ + 32.63505942847269, + 0.26207074609958386 + ], + [ + 32.63460382422552, + 0.26152344791588394 + ], + [ + 32.634022140815624, + 0.2612884822035099 + ], + [ + 32.63370407747327, + 0.261259827848342 + ], + [ + 32.63349203524503, + 0.261454677463481 + ], + [ + 32.63296766054546, + 0.260944629941499 + ], + [ + 32.63280719615652, + 0.261047785620102 + ], + [ + 32.632423227797275, + 0.260606508550522 + ], + [ + 32.632497729120715, + 0.260509083742952 + ], + [ + 32.63205645205113, + 0.2602569254174769 + ], + [ + 32.63213095337457, + 0.26007926841543794 + ], + [ + 32.63202493226045, + 0.259950323817184 + ], + [ + 32.631772773934976, + 0.259703896362743 + ], + [ + 32.631672483691894, + 0.25962939503930793 + ], + [ + 32.63148049951227, + 0.259586413506556 + ], + [ + 32.63135155491401, + 0.2596494530879249 + ], + [ + 32.63109939658854, + 0.259308466261431 + ], + [ + 32.631053549620276, + 0.259305600825914 + ], + [ + 32.630712562793775, + 0.2595061813120871 + ], + [ + 32.63042028837107, + 0.25911361664629196 + ], + [ + 32.63052630948519, + 0.25900759553217195 + ], + [ + 32.63037444140281, + 0.25878409156186494 + ], + [ + 32.63023116962697, + 0.2588901126759849 + ], + [ + 32.630067839802514, + 0.25860643455982596 + ], + [ + 32.629947491510805, + 0.2587267828515289 + ], + [ + 32.62981568147704, + 0.258560587591558 + ], + [ + 32.6297411801536, + 0.258620761737409 + ], + [ + 32.62950334900572, + 0.25829983295953296 + ], + [ + 32.629362942665395, + 0.2583857960250349 + ], + [ + 32.629236863502655, + 0.25821673532954686 + ], + [ + 32.6288328370948, + 0.258431642993304 + ], + [ + 32.62873254685171, + 0.25833421818573393 + ], + [ + 32.628357174799014, + 0.25857204933362493 + ], + [ + 32.62818689539351, + 0.25834584330486 + ], + [ + 32.627884377938756, + 0.258560587591558 + ], + [ + 32.627586372645005, + 0.258119310521977 + ], + [ + 32.62737719585229, + 0.25788721024511996 + ], + [ + 32.6273284834485, + 0.257947384390972 + ], + [ + 32.62717661536612, + 0.257821305228234 + ], + [ + 32.62729123278679, + 0.25772101498514793 + ], + [ + 32.627130768397855, + 0.257554819725176 + ], + [ + 32.62700755467063, + 0.25764937909722896 + ], + [ + 32.6265576812945, + 0.2572969305286679 + ], + [ + 32.626179443806286, + 0.256944481960107 + ], + [ + 32.6264545256159, + 0.2566779964570488 + ], + [ + 32.62631411927558, + 0.256600629698096 + ], + [ + 32.62648054593931, + 0.256417512540714 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/kam/4e7c7f/4e7c7f.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "kam" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/kam/4e7c7f/4e7c7f.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + 32.626179443806286, + 0.24927592951780594, + 32.638649276300896, + 0.26207074609958386 + ], + "stac_extensions": [], + "collection": "kam" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/kam/collection.json b/tests/data-files/catalogs/test-case-4/kam/collection.json index 417f23677..1ddca3485 100644 --- a/tests/data-files/catalogs/test-case-4/kam/collection.json +++ b/tests/data-files/catalogs/test-case-4/kam/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "kam", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from kam", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -44,8 +46,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json b/tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json index 2d04bce3c..55fc79920 100644 --- a/tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json +++ b/tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json @@ -1,241 +1,242 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "207cc7-labels", - "properties": { - "label:description": "Geojson building labels for scene 207cc7", - "area": "mon", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1544 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-10-05T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -10.790252263071974, - 6.327654354234644 - ], - [ - -10.790755009504934, - 6.327458076517664 - ], - [ - -10.792373439802875, - 6.326361331730665 - ], - [ - -10.79307590742153, - 6.325934341609507 - ], - [ - -10.794090582870746, - 6.326383714277333 - ], - [ - -10.794546592256534, - 6.327018919524551 - ], - [ - -10.79407103916773, - 6.3272900234540765 - ], - [ - -10.793652114520532, - 6.328857846551934 - ], - [ - -10.79473795498992, - 6.329210228184179 - ], - [ - -10.796888975116362, - 6.329375514682693 - ], - [ - -10.798182715496184, - 6.329509777779871 - ], - [ - -10.798182715496177, - 6.329617191840718 - ], - [ - -10.798040329415507, - 6.329979402045897 - ], - [ - -10.797341492635896, - 6.330470791722323 - ], - [ - -10.797253982800587, - 6.33049464201636 - ], - [ - -10.796452802411968, - 6.330585893937414 - ], - [ - -10.796390819975015, - 6.330783319477304 - ], - [ - -10.796461984995213, - 6.330975005902661 - ], - [ - -10.79623242041395, - 6.3309543450903485 - ], - [ - -10.795979899374569, - 6.330988779777538 - ], - [ - -10.79595923856226, - 6.3314731610440065 - ], - [ - -10.795541431024347, - 6.331449630674429 - ], - [ - -10.795467876148185, - 6.33183839832465 - ], - [ - -10.795339414192823, - 6.331880638175758 - ], - [ - -10.795109849611574, - 6.3318622730092535 - ], - [ - -10.795178532885092, - 6.33203976757905 - ], - [ - -10.794388556717536, - 6.332538112815994 - ], - [ - -10.79351733283488, - 6.332929896116368 - ], - [ - -10.793124115983604, - 6.333146686841442 - ], - [ - -10.792915212214664, - 6.332830461630747 - ], - [ - -10.792644326008762, - 6.33220375032389 - ], - [ - -10.792022205993543, - 6.332094133236333 - ], - [ - -10.790640227214311, - 6.330934258189489 - ], - [ - -10.790072304258905, - 6.331193742761042 - ], - [ - -10.789681563288742, - 6.332031112763716 - ], - [ - -10.78938409766017, - 6.331880134151346 - ], - [ - -10.788359662453985, - 6.33118582694528 - ], - [ - -10.789184787769084, - 6.330292625184847 - ], - [ - -10.789487813016356, - 6.3300934779106 - ], - [ - -10.788826667022306, - 6.3294334797394605 - ], - [ - -10.788652197940552, - 6.328021083653217 - ], - [ - -10.790252263071974, - 6.327654354234644 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "207cc7-labels", + "properties": { + "label:description": "Geojson building labels for scene 207cc7", + "area": "mon", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1544 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-10-05T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.798182715496184, - 6.325934341609507, - -10.788359662453985, - 6.333146686841442 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.790252263071974, + 6.327654354234644 + ], + [ + -10.790755009504934, + 6.327458076517664 + ], + [ + -10.792373439802875, + 6.326361331730665 + ], + [ + -10.79307590742153, + 6.325934341609507 + ], + [ + -10.794090582870746, + 6.326383714277333 + ], + [ + -10.794546592256534, + 6.327018919524551 + ], + [ + -10.79407103916773, + 6.3272900234540765 + ], + [ + -10.793652114520532, + 6.328857846551934 + ], + [ + -10.79473795498992, + 6.329210228184179 + ], + [ + -10.796888975116362, + 6.329375514682693 + ], + [ + -10.798182715496184, + 6.329509777779871 + ], + [ + -10.798182715496177, + 6.329617191840718 + ], + [ + -10.798040329415507, + 6.329979402045897 + ], + [ + -10.797341492635896, + 6.330470791722323 + ], + [ + -10.797253982800587, + 6.33049464201636 + ], + [ + -10.796452802411968, + 6.330585893937414 + ], + [ + -10.796390819975015, + 6.330783319477304 + ], + [ + -10.796461984995213, + 6.330975005902661 + ], + [ + -10.79623242041395, + 6.3309543450903485 + ], + [ + -10.795979899374569, + 6.330988779777538 + ], + [ + -10.79595923856226, + 6.3314731610440065 + ], + [ + -10.795541431024347, + 6.331449630674429 + ], + [ + -10.795467876148185, + 6.33183839832465 + ], + [ + -10.795339414192823, + 6.331880638175758 + ], + [ + -10.795109849611574, + 6.3318622730092535 + ], + [ + -10.795178532885092, + 6.33203976757905 + ], + [ + -10.794388556717536, + 6.332538112815994 + ], + [ + -10.79351733283488, + 6.332929896116368 + ], + [ + -10.793124115983604, + 6.333146686841442 + ], + [ + -10.792915212214664, + 6.332830461630747 + ], + [ + -10.792644326008762, + 6.33220375032389 + ], + [ + -10.792022205993543, + 6.332094133236333 + ], + [ + -10.790640227214311, + 6.330934258189489 + ], + [ + -10.790072304258905, + 6.331193742761042 + ], + [ + -10.789681563288742, + 6.332031112763716 + ], + [ + -10.78938409766017, + 6.331880134151346 + ], + [ + -10.788359662453985, + 6.33118582694528 + ], + [ + -10.789184787769084, + 6.330292625184847 + ], + [ + -10.789487813016356, + 6.3300934779106 + ], + [ + -10.788826667022306, + 6.3294334797394605 + ], + [ + -10.788652197940552, + 6.328021083653217 + ], + [ + -10.790252263071974, + 6.327654354234644 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/207cc7-labels/207cc7.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/207cc7-labels/207cc7.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -10.798182715496184, + 6.325934341609507, + -10.788359662453985, + 6.333146686841442 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json b/tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json index b667a1517..e5ec355f9 100644 --- a/tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json +++ b/tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json @@ -1,216 +1,217 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "207cc7", - "properties": { - "area": "mon", - "license": "CC-BY-4.0", - "datetime": "2018-10-05T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -10.790252263071974, - 6.327654354234644 - ], - [ - -10.790755009504934, - 6.327458076517664 - ], - [ - -10.792373439802875, - 6.326361331730665 - ], - [ - -10.79307590742153, - 6.325934341609507 - ], - [ - -10.794090582870746, - 6.326383714277333 - ], - [ - -10.794546592256534, - 6.327018919524551 - ], - [ - -10.79407103916773, - 6.3272900234540765 - ], - [ - -10.793652114520532, - 6.328857846551934 - ], - [ - -10.79473795498992, - 6.329210228184179 - ], - [ - -10.796888975116362, - 6.329375514682693 - ], - [ - -10.798182715496184, - 6.329509777779871 - ], - [ - -10.798182715496177, - 6.329617191840718 - ], - [ - -10.798040329415507, - 6.329979402045897 - ], - [ - -10.797341492635896, - 6.330470791722323 - ], - [ - -10.797253982800587, - 6.33049464201636 - ], - [ - -10.796452802411968, - 6.330585893937414 - ], - [ - -10.796390819975015, - 6.330783319477304 - ], - [ - -10.796461984995213, - 6.330975005902661 - ], - [ - -10.79623242041395, - 6.3309543450903485 - ], - [ - -10.795979899374569, - 6.330988779777538 - ], - [ - -10.79595923856226, - 6.3314731610440065 - ], - [ - -10.795541431024347, - 6.331449630674429 - ], - [ - -10.795467876148185, - 6.33183839832465 - ], - [ - -10.795339414192823, - 6.331880638175758 - ], - [ - -10.795109849611574, - 6.3318622730092535 - ], - [ - -10.795178532885092, - 6.33203976757905 - ], - [ - -10.794388556717536, - 6.332538112815994 - ], - [ - -10.79351733283488, - 6.332929896116368 - ], - [ - -10.793124115983604, - 6.333146686841442 - ], - [ - -10.792915212214664, - 6.332830461630747 - ], - [ - -10.792644326008762, - 6.33220375032389 - ], - [ - -10.792022205993543, - 6.332094133236333 - ], - [ - -10.790640227214311, - 6.330934258189489 - ], - [ - -10.790072304258905, - 6.331193742761042 - ], - [ - -10.789681563288742, - 6.332031112763716 - ], - [ - -10.78938409766017, - 6.331880134151346 - ], - [ - -10.788359662453985, - 6.33118582694528 - ], - [ - -10.789184787769084, - 6.330292625184847 - ], - [ - -10.789487813016356, - 6.3300934779106 - ], - [ - -10.788826667022306, - 6.3294334797394605 - ], - [ - -10.788652197940552, - 6.328021083653217 - ], - [ - -10.790252263071974, - 6.327654354234644 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.798182715496184, - 6.325934341609507, - -10.788359662453985, - 6.333146686841442 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "207cc7", + "properties": { + "area": "mon", + "license": "CC-BY-4.0", + "datetime": "2018-10-05T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.790252263071974, + 6.327654354234644 + ], + [ + -10.790755009504934, + 6.327458076517664 + ], + [ + -10.792373439802875, + 6.326361331730665 + ], + [ + -10.79307590742153, + 6.325934341609507 + ], + [ + -10.794090582870746, + 6.326383714277333 + ], + [ + -10.794546592256534, + 6.327018919524551 + ], + [ + -10.79407103916773, + 6.3272900234540765 + ], + [ + -10.793652114520532, + 6.328857846551934 + ], + [ + -10.79473795498992, + 6.329210228184179 + ], + [ + -10.796888975116362, + 6.329375514682693 + ], + [ + -10.798182715496184, + 6.329509777779871 + ], + [ + -10.798182715496177, + 6.329617191840718 + ], + [ + -10.798040329415507, + 6.329979402045897 + ], + [ + -10.797341492635896, + 6.330470791722323 + ], + [ + -10.797253982800587, + 6.33049464201636 + ], + [ + -10.796452802411968, + 6.330585893937414 + ], + [ + -10.796390819975015, + 6.330783319477304 + ], + [ + -10.796461984995213, + 6.330975005902661 + ], + [ + -10.79623242041395, + 6.3309543450903485 + ], + [ + -10.795979899374569, + 6.330988779777538 + ], + [ + -10.79595923856226, + 6.3314731610440065 + ], + [ + -10.795541431024347, + 6.331449630674429 + ], + [ + -10.795467876148185, + 6.33183839832465 + ], + [ + -10.795339414192823, + 6.331880638175758 + ], + [ + -10.795109849611574, + 6.3318622730092535 + ], + [ + -10.795178532885092, + 6.33203976757905 + ], + [ + -10.794388556717536, + 6.332538112815994 + ], + [ + -10.79351733283488, + 6.332929896116368 + ], + [ + -10.793124115983604, + 6.333146686841442 + ], + [ + -10.792915212214664, + 6.332830461630747 + ], + [ + -10.792644326008762, + 6.33220375032389 + ], + [ + -10.792022205993543, + 6.332094133236333 + ], + [ + -10.790640227214311, + 6.330934258189489 + ], + [ + -10.790072304258905, + 6.331193742761042 + ], + [ + -10.789681563288742, + 6.332031112763716 + ], + [ + -10.78938409766017, + 6.331880134151346 + ], + [ + -10.788359662453985, + 6.33118582694528 + ], + [ + -10.789184787769084, + 6.330292625184847 + ], + [ + -10.789487813016356, + 6.3300934779106 + ], + [ + -10.788826667022306, + 6.3294334797394605 + ], + [ + -10.788652197940552, + 6.328021083653217 + ], + [ + -10.790252263071974, + 6.327654354234644 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/207cc7/207cc7.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "mon" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/207cc7/207cc7.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + -10.798182715496184, + 6.325934341609507, + -10.788359662453985, + 6.333146686841442 + ], + "stac_extensions": [], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json b/tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json index 88dae5986..3bfc716aa 100644 --- a/tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json +++ b/tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json @@ -1,233 +1,234 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "401175-labels", - "properties": { - "label:description": "Geojson building labels for scene 401175", - "area": "mon", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 2296 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-11-21T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -10.792405216952368, - 6.337610599919112 - ], - [ - -10.791331344097435, - 6.33837279463673 - ], - [ - -10.789197765575608, - 6.339049277866593 - ], - [ - -10.788920088689641, - 6.339080445680323 - ], - [ - -10.78832790022873, - 6.339029443803308 - ], - [ - -10.787707377391706, - 6.338913272861214 - ], - [ - -10.787147623429378, - 6.338765894526389 - ], - [ - -10.78626515764722, - 6.338429463388974 - ], - [ - -10.78595914638511, - 6.338061824858818 - ], - [ - -10.785080780725423, - 6.337114039977611 - ], - [ - -10.784375254760038, - 6.3362923430701406 - ], - [ - -10.784117411937343, - 6.335824117504759 - ], - [ - -10.783947405680633, - 6.3348664155919145 - ], - [ - -10.7841514131887, - 6.334414482292804 - ], - [ - -10.784429090074655, - 6.334001508760861 - ], - [ - -10.785565298557051, - 6.333184062009808 - ], - [ - -10.786270824522447, - 6.3327880891035315 - ], - [ - -10.7872285264353, - 6.332159065953676 - ], - [ - -10.787795213957688, - 6.33168659023188 - ], - [ - -10.788344900854407, - 6.3311680711488885 - ], - [ - -10.789047593382183, - 6.331842429300539 - ], - [ - -10.789665282781591, - 6.332250444316662 - ], - [ - -10.789897624665784, - 6.332151274000243 - ], - [ - -10.789985461231744, - 6.33195860024263 - ], - [ - -10.789914625291447, - 6.33187147203606 - ], - [ - -10.789906124978605, - 6.3316391301518795 - ], - [ - -10.790359474996508, - 6.330781306914856 - ], - [ - -10.791089757077815, - 6.331292928586951 - ], - [ - -10.791388012849666, - 6.33157041928979 - ], - [ - -10.791399346600114, - 6.331768051563226 - ], - [ - -10.791886697869387, - 6.332269570020544 - ], - [ - -10.792147374129684, - 6.332218568143528 - ], - [ - -10.792306046635945, - 6.332289404083829 - ], - [ - -10.792685727275948, - 6.332373698852782 - ], - [ - -10.792889734784001, - 6.33279871449458 - ], - [ - -10.793204246358947, - 6.333226563573987 - ], - [ - -10.793107909480138, - 6.335605234449236 - ], - [ - -10.79296057072432, - 6.336548060814622 - ], - [ - -10.792903901972071, - 6.3367449847286546 - ], - [ - -10.792405216952368, - 6.337610599919112 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "401175-labels", + "properties": { + "label:description": "Geojson building labels for scene 401175", + "area": "mon", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 2296 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-11-21T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.793204246358947, - 6.330781306914856, - -10.783947405680633, - 6.339080445680323 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.792405216952368, + 6.337610599919112 + ], + [ + -10.791331344097435, + 6.33837279463673 + ], + [ + -10.789197765575608, + 6.339049277866593 + ], + [ + -10.788920088689641, + 6.339080445680323 + ], + [ + -10.78832790022873, + 6.339029443803308 + ], + [ + -10.787707377391706, + 6.338913272861214 + ], + [ + -10.787147623429378, + 6.338765894526389 + ], + [ + -10.78626515764722, + 6.338429463388974 + ], + [ + -10.78595914638511, + 6.338061824858818 + ], + [ + -10.785080780725423, + 6.337114039977611 + ], + [ + -10.784375254760038, + 6.3362923430701406 + ], + [ + -10.784117411937343, + 6.335824117504759 + ], + [ + -10.783947405680633, + 6.3348664155919145 + ], + [ + -10.7841514131887, + 6.334414482292804 + ], + [ + -10.784429090074655, + 6.334001508760861 + ], + [ + -10.785565298557051, + 6.333184062009808 + ], + [ + -10.786270824522447, + 6.3327880891035315 + ], + [ + -10.7872285264353, + 6.332159065953676 + ], + [ + -10.787795213957688, + 6.33168659023188 + ], + [ + -10.788344900854407, + 6.3311680711488885 + ], + [ + -10.789047593382183, + 6.331842429300539 + ], + [ + -10.789665282781591, + 6.332250444316662 + ], + [ + -10.789897624665784, + 6.332151274000243 + ], + [ + -10.789985461231744, + 6.33195860024263 + ], + [ + -10.789914625291447, + 6.33187147203606 + ], + [ + -10.789906124978605, + 6.3316391301518795 + ], + [ + -10.790359474996508, + 6.330781306914856 + ], + [ + -10.791089757077815, + 6.331292928586951 + ], + [ + -10.791388012849666, + 6.33157041928979 + ], + [ + -10.791399346600114, + 6.331768051563226 + ], + [ + -10.791886697869387, + 6.332269570020544 + ], + [ + -10.792147374129684, + 6.332218568143528 + ], + [ + -10.792306046635945, + 6.332289404083829 + ], + [ + -10.792685727275948, + 6.332373698852782 + ], + [ + -10.792889734784001, + 6.33279871449458 + ], + [ + -10.793204246358947, + 6.333226563573987 + ], + [ + -10.793107909480138, + 6.335605234449236 + ], + [ + -10.79296057072432, + 6.336548060814622 + ], + [ + -10.792903901972071, + 6.3367449847286546 + ], + [ + -10.792405216952368, + 6.337610599919112 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/401175-labels/401175.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/401175-labels/401175.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -10.793204246358947, + 6.330781306914856, + -10.783947405680633, + 6.339080445680323 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/401175/401175.json b/tests/data-files/catalogs/test-case-4/mon/401175/401175.json index 231aaacde..26a75cca8 100644 --- a/tests/data-files/catalogs/test-case-4/mon/401175/401175.json +++ b/tests/data-files/catalogs/test-case-4/mon/401175/401175.json @@ -1,208 +1,209 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "401175", - "properties": { - "area": "mon", - "license": "CC-BY-4.0", - "datetime": "2018-11-21T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -10.792405216952368, - 6.337610599919112 - ], - [ - -10.791331344097435, - 6.33837279463673 - ], - [ - -10.789197765575608, - 6.339049277866593 - ], - [ - -10.788920088689641, - 6.339080445680323 - ], - [ - -10.78832790022873, - 6.339029443803308 - ], - [ - -10.787707377391706, - 6.338913272861214 - ], - [ - -10.787147623429378, - 6.338765894526389 - ], - [ - -10.78626515764722, - 6.338429463388974 - ], - [ - -10.78595914638511, - 6.338061824858818 - ], - [ - -10.785080780725423, - 6.337114039977611 - ], - [ - -10.784375254760038, - 6.3362923430701406 - ], - [ - -10.784117411937343, - 6.335824117504759 - ], - [ - -10.783947405680633, - 6.3348664155919145 - ], - [ - -10.7841514131887, - 6.334414482292804 - ], - [ - -10.784429090074655, - 6.334001508760861 - ], - [ - -10.785565298557051, - 6.333184062009808 - ], - [ - -10.786270824522447, - 6.3327880891035315 - ], - [ - -10.7872285264353, - 6.332159065953676 - ], - [ - -10.787795213957688, - 6.33168659023188 - ], - [ - -10.788344900854407, - 6.3311680711488885 - ], - [ - -10.789047593382183, - 6.331842429300539 - ], - [ - -10.789665282781591, - 6.332250444316662 - ], - [ - -10.789897624665784, - 6.332151274000243 - ], - [ - -10.789985461231744, - 6.33195860024263 - ], - [ - -10.789914625291447, - 6.33187147203606 - ], - [ - -10.789906124978605, - 6.3316391301518795 - ], - [ - -10.790359474996508, - 6.330781306914856 - ], - [ - -10.791089757077815, - 6.331292928586951 - ], - [ - -10.791388012849666, - 6.33157041928979 - ], - [ - -10.791399346600114, - 6.331768051563226 - ], - [ - -10.791886697869387, - 6.332269570020544 - ], - [ - -10.792147374129684, - 6.332218568143528 - ], - [ - -10.792306046635945, - 6.332289404083829 - ], - [ - -10.792685727275948, - 6.332373698852782 - ], - [ - -10.792889734784001, - 6.33279871449458 - ], - [ - -10.793204246358947, - 6.333226563573987 - ], - [ - -10.793107909480138, - 6.335605234449236 - ], - [ - -10.79296057072432, - 6.336548060814622 - ], - [ - -10.792903901972071, - 6.3367449847286546 - ], - [ - -10.792405216952368, - 6.337610599919112 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.793204246358947, - 6.330781306914856, - -10.783947405680633, - 6.339080445680323 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "401175", + "properties": { + "area": "mon", + "license": "CC-BY-4.0", + "datetime": "2018-11-21T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.792405216952368, + 6.337610599919112 + ], + [ + -10.791331344097435, + 6.33837279463673 + ], + [ + -10.789197765575608, + 6.339049277866593 + ], + [ + -10.788920088689641, + 6.339080445680323 + ], + [ + -10.78832790022873, + 6.339029443803308 + ], + [ + -10.787707377391706, + 6.338913272861214 + ], + [ + -10.787147623429378, + 6.338765894526389 + ], + [ + -10.78626515764722, + 6.338429463388974 + ], + [ + -10.78595914638511, + 6.338061824858818 + ], + [ + -10.785080780725423, + 6.337114039977611 + ], + [ + -10.784375254760038, + 6.3362923430701406 + ], + [ + -10.784117411937343, + 6.335824117504759 + ], + [ + -10.783947405680633, + 6.3348664155919145 + ], + [ + -10.7841514131887, + 6.334414482292804 + ], + [ + -10.784429090074655, + 6.334001508760861 + ], + [ + -10.785565298557051, + 6.333184062009808 + ], + [ + -10.786270824522447, + 6.3327880891035315 + ], + [ + -10.7872285264353, + 6.332159065953676 + ], + [ + -10.787795213957688, + 6.33168659023188 + ], + [ + -10.788344900854407, + 6.3311680711488885 + ], + [ + -10.789047593382183, + 6.331842429300539 + ], + [ + -10.789665282781591, + 6.332250444316662 + ], + [ + -10.789897624665784, + 6.332151274000243 + ], + [ + -10.789985461231744, + 6.33195860024263 + ], + [ + -10.789914625291447, + 6.33187147203606 + ], + [ + -10.789906124978605, + 6.3316391301518795 + ], + [ + -10.790359474996508, + 6.330781306914856 + ], + [ + -10.791089757077815, + 6.331292928586951 + ], + [ + -10.791388012849666, + 6.33157041928979 + ], + [ + -10.791399346600114, + 6.331768051563226 + ], + [ + -10.791886697869387, + 6.332269570020544 + ], + [ + -10.792147374129684, + 6.332218568143528 + ], + [ + -10.792306046635945, + 6.332289404083829 + ], + [ + -10.792685727275948, + 6.332373698852782 + ], + [ + -10.792889734784001, + 6.33279871449458 + ], + [ + -10.793204246358947, + 6.333226563573987 + ], + [ + -10.793107909480138, + 6.335605234449236 + ], + [ + -10.79296057072432, + 6.336548060814622 + ], + [ + -10.792903901972071, + 6.3367449847286546 + ], + [ + -10.792405216952368, + 6.337610599919112 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/401175/401175.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "mon" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/401175/401175.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + -10.793204246358947, + 6.330781306914856, + -10.783947405680633, + 6.339080445680323 + ], + "stac_extensions": [], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json b/tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json index 18c08dc12..691d1632f 100644 --- a/tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json +++ b/tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json @@ -1,201 +1,202 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "493701-labels", - "properties": { - "label:description": "Geojson building labels for scene 493701", - "area": "mon", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1719 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-08-21T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -10.78417140164097, - 6.334404010066445 - ], - [ - -10.783782349974084, - 6.3342425162635685 - ], - [ - -10.783682825442487, - 6.334403211739324 - ], - [ - -10.783654085666106, - 6.334626743333399 - ], - [ - -10.782721639587955, - 6.3339609385139 - ], - [ - -10.782462981600524, - 6.333322276816537 - ], - [ - -10.782076591273617, - 6.332277266614225 - ], - [ - -10.781770033658884, - 6.331613856776092 - ], - [ - -10.78130061731132, - 6.330110606805922 - ], - [ - -10.7812814574604, - 6.329879091940628 - ], - [ - -10.78311760984032, - 6.327981468372338 - ], - [ - -10.783258115413739, - 6.327764323395235 - ], - [ - -10.78415862840702, - 6.326823894045865 - ], - [ - -10.784758970402542, - 6.326574815983895 - ], - [ - -10.785656290087339, - 6.326348091081331 - ], - [ - -10.7863683978799, - 6.326344897772844 - ], - [ - -10.787227397862853, - 6.3266259089196835 - ], - [ - -10.789750111567438, - 6.3274002862277365 - ], - [ - -10.788768561338868, - 6.328341669029932 - ], - [ - -10.788575194406437, - 6.328808135190596 - ], - [ - -10.788669501067371, - 6.329046924492319 - ], - [ - -10.788638065513727, - 6.329432614554341 - ], - [ - -10.788855696269728, - 6.329710698298121 - ], - [ - -10.789325401538692, - 6.330232750855542 - ], - [ - -10.78900607069001, - 6.330509770366774 - ], - [ - -10.788430537721016, - 6.331145533682599 - ], - [ - -10.787152958356145, - 6.332263063398193 - ], - [ - -10.786062605322904, - 6.332942645814849 - ], - [ - -10.785497458219522, - 6.33325268915716 - ], - [ - -10.784928215752343, - 6.333651187590679 - ], - [ - -10.78437777790976, - 6.334071511536476 - ], - [ - -10.78417140164097, - 6.334404010066445 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "493701-labels", + "properties": { + "label:description": "Geojson building labels for scene 493701", + "area": "mon", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1719 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-08-21T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.789750111567438, - 6.326344897772844, - -10.7812814574604, - 6.334626743333399 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.78417140164097, + 6.334404010066445 + ], + [ + -10.783782349974084, + 6.3342425162635685 + ], + [ + -10.783682825442487, + 6.334403211739324 + ], + [ + -10.783654085666106, + 6.334626743333399 + ], + [ + -10.782721639587955, + 6.3339609385139 + ], + [ + -10.782462981600524, + 6.333322276816537 + ], + [ + -10.782076591273617, + 6.332277266614225 + ], + [ + -10.781770033658884, + 6.331613856776092 + ], + [ + -10.78130061731132, + 6.330110606805922 + ], + [ + -10.7812814574604, + 6.329879091940628 + ], + [ + -10.78311760984032, + 6.327981468372338 + ], + [ + -10.783258115413739, + 6.327764323395235 + ], + [ + -10.78415862840702, + 6.326823894045865 + ], + [ + -10.784758970402542, + 6.326574815983895 + ], + [ + -10.785656290087339, + 6.326348091081331 + ], + [ + -10.7863683978799, + 6.326344897772844 + ], + [ + -10.787227397862853, + 6.3266259089196835 + ], + [ + -10.789750111567438, + 6.3274002862277365 + ], + [ + -10.788768561338868, + 6.328341669029932 + ], + [ + -10.788575194406437, + 6.328808135190596 + ], + [ + -10.788669501067371, + 6.329046924492319 + ], + [ + -10.788638065513727, + 6.329432614554341 + ], + [ + -10.788855696269728, + 6.329710698298121 + ], + [ + -10.789325401538692, + 6.330232750855542 + ], + [ + -10.78900607069001, + 6.330509770366774 + ], + [ + -10.788430537721016, + 6.331145533682599 + ], + [ + -10.787152958356145, + 6.332263063398193 + ], + [ + -10.786062605322904, + 6.332942645814849 + ], + [ + -10.785497458219522, + 6.33325268915716 + ], + [ + -10.784928215752343, + 6.333651187590679 + ], + [ + -10.78437777790976, + 6.334071511536476 + ], + [ + -10.78417140164097, + 6.334404010066445 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/493701-labels/493701.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/493701-labels/493701.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + -10.789750111567438, + 6.326344897772844, + -10.7812814574604, + 6.334626743333399 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/493701/493701.json b/tests/data-files/catalogs/test-case-4/mon/493701/493701.json index 9ecbfb86f..951db2aab 100644 --- a/tests/data-files/catalogs/test-case-4/mon/493701/493701.json +++ b/tests/data-files/catalogs/test-case-4/mon/493701/493701.json @@ -1,176 +1,177 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "493701", - "properties": { - "area": "mon", - "license": "CC-BY-4.0", - "datetime": "2018-08-21T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -10.78417140164097, - 6.334404010066445 - ], - [ - -10.783782349974084, - 6.3342425162635685 - ], - [ - -10.783682825442487, - 6.334403211739324 - ], - [ - -10.783654085666106, - 6.334626743333399 - ], - [ - -10.782721639587955, - 6.3339609385139 - ], - [ - -10.782462981600524, - 6.333322276816537 - ], - [ - -10.782076591273617, - 6.332277266614225 - ], - [ - -10.781770033658884, - 6.331613856776092 - ], - [ - -10.78130061731132, - 6.330110606805922 - ], - [ - -10.7812814574604, - 6.329879091940628 - ], - [ - -10.78311760984032, - 6.327981468372338 - ], - [ - -10.783258115413739, - 6.327764323395235 - ], - [ - -10.78415862840702, - 6.326823894045865 - ], - [ - -10.784758970402542, - 6.326574815983895 - ], - [ - -10.785656290087339, - 6.326348091081331 - ], - [ - -10.7863683978799, - 6.326344897772844 - ], - [ - -10.787227397862853, - 6.3266259089196835 - ], - [ - -10.789750111567438, - 6.3274002862277365 - ], - [ - -10.788768561338868, - 6.328341669029932 - ], - [ - -10.788575194406437, - 6.328808135190596 - ], - [ - -10.788669501067371, - 6.329046924492319 - ], - [ - -10.788638065513727, - 6.329432614554341 - ], - [ - -10.788855696269728, - 6.329710698298121 - ], - [ - -10.789325401538692, - 6.330232750855542 - ], - [ - -10.78900607069001, - 6.330509770366774 - ], - [ - -10.788430537721016, - 6.331145533682599 - ], - [ - -10.787152958356145, - 6.332263063398193 - ], - [ - -10.786062605322904, - 6.332942645814849 - ], - [ - -10.785497458219522, - 6.33325268915716 - ], - [ - -10.784928215752343, - 6.333651187590679 - ], - [ - -10.78437777790976, - 6.334071511536476 - ], - [ - -10.78417140164097, - 6.334404010066445 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.789750111567438, - 6.326344897772844, - -10.7812814574604, - 6.334626743333399 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "493701", + "properties": { + "area": "mon", + "license": "CC-BY-4.0", + "datetime": "2018-08-21T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + -10.78417140164097, + 6.334404010066445 + ], + [ + -10.783782349974084, + 6.3342425162635685 + ], + [ + -10.783682825442487, + 6.334403211739324 + ], + [ + -10.783654085666106, + 6.334626743333399 + ], + [ + -10.782721639587955, + 6.3339609385139 + ], + [ + -10.782462981600524, + 6.333322276816537 + ], + [ + -10.782076591273617, + 6.332277266614225 + ], + [ + -10.781770033658884, + 6.331613856776092 + ], + [ + -10.78130061731132, + 6.330110606805922 + ], + [ + -10.7812814574604, + 6.329879091940628 + ], + [ + -10.78311760984032, + 6.327981468372338 + ], + [ + -10.783258115413739, + 6.327764323395235 + ], + [ + -10.78415862840702, + 6.326823894045865 + ], + [ + -10.784758970402542, + 6.326574815983895 + ], + [ + -10.785656290087339, + 6.326348091081331 + ], + [ + -10.7863683978799, + 6.326344897772844 + ], + [ + -10.787227397862853, + 6.3266259089196835 + ], + [ + -10.789750111567438, + 6.3274002862277365 + ], + [ + -10.788768561338868, + 6.328341669029932 + ], + [ + -10.788575194406437, + 6.328808135190596 + ], + [ + -10.788669501067371, + 6.329046924492319 + ], + [ + -10.788638065513727, + 6.329432614554341 + ], + [ + -10.788855696269728, + 6.329710698298121 + ], + [ + -10.789325401538692, + 6.330232750855542 + ], + [ + -10.78900607069001, + 6.330509770366774 + ], + [ + -10.788430537721016, + 6.331145533682599 + ], + [ + -10.787152958356145, + 6.332263063398193 + ], + [ + -10.786062605322904, + 6.332942645814849 + ], + [ + -10.785497458219522, + 6.33325268915716 + ], + [ + -10.784928215752343, + 6.333651187590679 + ], + [ + -10.78437777790976, + 6.334071511536476 + ], + [ + -10.78417140164097, + 6.334404010066445 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/493701/493701.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/493701/493701.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "mon" + "bbox": [ + -10.789750111567438, + 6.326344897772844, + -10.7812814574604, + 6.334626743333399 + ], + "stac_extensions": [], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/collection.json b/tests/data-files/catalogs/test-case-4/mon/collection.json index 7deab20ab..7cd984ba7 100644 --- a/tests/data-files/catalogs/test-case-4/mon/collection.json +++ b/tests/data-files/catalogs/test-case-4/mon/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "mon", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from mon", "links": [ { @@ -54,6 +55,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -74,8 +76,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json b/tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json index 296758970..ac04f9b15 100644 --- a/tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json +++ b/tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json @@ -1,189 +1,190 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f15272-labels", - "properties": { - "label:description": "Geojson building labels for scene f15272", - "area": "mon", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1388 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2018-08-22T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f15272-labels", + "properties": { + "label:description": "Geojson building labels for scene f15272", + "area": "mon", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1388 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2018-08-22T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - -10.794428391700563, - 6.326222824390839 - ], - [ - -10.795373361048709, - 6.32561672308313 - ], - [ - -10.79669225375182, - 6.325126964775813 - ], - [ - -10.797218591517776, - 6.324881069526079 - ], - [ - -10.797472615536094, - 6.324816547425426 - ], - [ - -10.79781605600886, - 6.32481451523328 - ], - [ - -10.798600482177429, - 6.32516354423445 - ], - [ - -10.799510904259083, - 6.325520702004206 - ], - [ - -10.8001490125931, - 6.325927140433517 - ], - [ - -10.800346135231315, - 6.326223332438877 - ], - [ - -10.800917181224495, - 6.326974227437026 - ], - [ - -10.801045209329727, - 6.327219614638724 - ], - [ - -10.801037982501601, - 6.327698090884192 - ], - [ - -10.800916330894824, - 6.328479190557416 - ], - [ - -10.800516446405219, - 6.329250955577641 - ], - [ - -10.800423702110942, - 6.329471373835468 - ], - [ - -10.800280370019788, - 6.330338292092678 - ], - [ - -10.799843863013646, - 6.331181449435057 - ], - [ - -10.79954513120753, - 6.331302091125989 - ], - [ - -10.798068706704237, - 6.330587576349347 - ], - [ - -10.798479462937644, - 6.32966265671888 - ], - [ - -10.796845055267655, - 6.328911518571778 - ], - [ - -10.794969521570112, - 6.32923047881336 - ], - [ - -10.79457153985325, - 6.329173326087836 - ], - [ - -10.793669675961551, - 6.328883800628626 - ], - [ - -10.794061962320207, - 6.32729047689175 - ], - [ - -10.794599095840873, - 6.326978799869356 - ], - [ - -10.79484295889846, - 6.326960510140037 - ], - [ - -10.794428391700563, - 6.326222824390839 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.801045209329727, - 6.32481451523328, - -10.793669675961551, - 6.331302091125989 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + -10.794428391700563, + 6.326222824390839 + ], + [ + -10.795373361048709, + 6.32561672308313 + ], + [ + -10.79669225375182, + 6.325126964775813 + ], + [ + -10.797218591517776, + 6.324881069526079 + ], + [ + -10.797472615536094, + 6.324816547425426 + ], + [ + -10.79781605600886, + 6.32481451523328 + ], + [ + -10.798600482177429, + 6.32516354423445 + ], + [ + -10.799510904259083, + 6.325520702004206 + ], + [ + -10.8001490125931, + 6.325927140433517 + ], + [ + -10.800346135231315, + 6.326223332438877 + ], + [ + -10.800917181224495, + 6.326974227437026 + ], + [ + -10.801045209329727, + 6.327219614638724 + ], + [ + -10.801037982501601, + 6.327698090884192 + ], + [ + -10.800916330894824, + 6.328479190557416 + ], + [ + -10.800516446405219, + 6.329250955577641 + ], + [ + -10.800423702110942, + 6.329471373835468 + ], + [ + -10.800280370019788, + 6.330338292092678 + ], + [ + -10.799843863013646, + 6.331181449435057 + ], + [ + -10.79954513120753, + 6.331302091125989 + ], + [ + -10.798068706704237, + 6.330587576349347 + ], + [ + -10.798479462937644, + 6.32966265671888 + ], + [ + -10.796845055267655, + 6.328911518571778 + ], + [ + -10.794969521570112, + 6.32923047881336 + ], + [ + -10.79457153985325, + 6.329173326087836 + ], + [ + -10.793669675961551, + 6.328883800628626 + ], + [ + -10.794061962320207, + 6.32729047689175 + ], + [ + -10.794599095840873, + 6.326978799869356 + ], + [ + -10.79484295889846, + 6.326960510140037 + ], + [ + -10.794428391700563, + 6.326222824390839 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/f15272-labels/f15272.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/f15272-labels/f15272.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + -10.801045209329727, + 6.32481451523328, + -10.793669675961551, + 6.331302091125989 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json b/tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json index 26a03f8b3..1d5f9fbb3 100644 --- a/tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json +++ b/tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json @@ -1,164 +1,165 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f15272", - "properties": { - "area": "mon", - "license": "CC-BY-4.0", - "datetime": "2018-08-22T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - -10.794428391700563, - 6.326222824390839 - ], - [ - -10.795373361048709, - 6.32561672308313 - ], - [ - -10.79669225375182, - 6.325126964775813 - ], - [ - -10.797218591517776, - 6.324881069526079 - ], - [ - -10.797472615536094, - 6.324816547425426 - ], - [ - -10.79781605600886, - 6.32481451523328 - ], - [ - -10.798600482177429, - 6.32516354423445 - ], - [ - -10.799510904259083, - 6.325520702004206 - ], - [ - -10.8001490125931, - 6.325927140433517 - ], - [ - -10.800346135231315, - 6.326223332438877 - ], - [ - -10.800917181224495, - 6.326974227437026 - ], - [ - -10.801045209329727, - 6.327219614638724 - ], - [ - -10.801037982501601, - 6.327698090884192 - ], - [ - -10.800916330894824, - 6.328479190557416 - ], - [ - -10.800516446405219, - 6.329250955577641 - ], - [ - -10.800423702110942, - 6.329471373835468 - ], - [ - -10.800280370019788, - 6.330338292092678 - ], - [ - -10.799843863013646, - 6.331181449435057 - ], - [ - -10.79954513120753, - 6.331302091125989 - ], - [ - -10.798068706704237, - 6.330587576349347 - ], - [ - -10.798479462937644, - 6.32966265671888 - ], - [ - -10.796845055267655, - 6.328911518571778 - ], - [ - -10.794969521570112, - 6.32923047881336 - ], - [ - -10.79457153985325, - 6.329173326087836 - ], - [ - -10.793669675961551, - 6.328883800628626 - ], - [ - -10.794061962320207, - 6.32729047689175 - ], - [ - -10.794599095840873, - 6.326978799869356 - ], - [ - -10.79484295889846, - 6.326960510140037 - ], - [ - -10.794428391700563, - 6.326222824390839 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - -10.801045209329727, - 6.32481451523328, - -10.793669675961551, - 6.331302091125989 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f15272", + "properties": { + "area": "mon", + "license": "CC-BY-4.0", + "datetime": "2018-08-22T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + -10.794428391700563, + 6.326222824390839 + ], + [ + -10.795373361048709, + 6.32561672308313 + ], + [ + -10.79669225375182, + 6.325126964775813 + ], + [ + -10.797218591517776, + 6.324881069526079 + ], + [ + -10.797472615536094, + 6.324816547425426 + ], + [ + -10.79781605600886, + 6.32481451523328 + ], + [ + -10.798600482177429, + 6.32516354423445 + ], + [ + -10.799510904259083, + 6.325520702004206 + ], + [ + -10.8001490125931, + 6.325927140433517 + ], + [ + -10.800346135231315, + 6.326223332438877 + ], + [ + -10.800917181224495, + 6.326974227437026 + ], + [ + -10.801045209329727, + 6.327219614638724 + ], + [ + -10.801037982501601, + 6.327698090884192 + ], + [ + -10.800916330894824, + 6.328479190557416 + ], + [ + -10.800516446405219, + 6.329250955577641 + ], + [ + -10.800423702110942, + 6.329471373835468 + ], + [ + -10.800280370019788, + 6.330338292092678 + ], + [ + -10.799843863013646, + 6.331181449435057 + ], + [ + -10.79954513120753, + 6.331302091125989 + ], + [ + -10.798068706704237, + 6.330587576349347 + ], + [ + -10.798479462937644, + 6.32966265671888 + ], + [ + -10.796845055267655, + 6.328911518571778 + ], + [ + -10.794969521570112, + 6.32923047881336 + ], + [ + -10.79457153985325, + 6.329173326087836 + ], + [ + -10.793669675961551, + 6.328883800628626 + ], + [ + -10.794061962320207, + 6.32729047689175 + ], + [ + -10.794599095840873, + 6.326978799869356 + ], + [ + -10.79484295889846, + 6.326960510140037 + ], + [ + -10.794428391700563, + 6.326222824390839 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/f15272/f15272.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/mon/f15272/f15272.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "mon" + "bbox": [ + -10.801045209329727, + 6.32481451523328, + -10.793669675961551, + 6.331302091125989 + ], + "stac_extensions": [], + "collection": "mon" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json b/tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json index ee58e9008..a14743923 100644 --- a/tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json +++ b/tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json @@ -1,129 +1,130 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "825a50-labels", - "properties": { - "label:description": "Geojson building labels for scene 825a50", - "area": "nia", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 634 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2017-09-19T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 2.000607112710697, - 13.577784497569377 - ], - [ - 2.000938627053272, - 13.57440384053762 - ], - [ - 2.0026898208721526, - 13.572186843360377 - ], - [ - 2.0046604894641256, - 13.570445755015827 - ], - [ - 2.005587501792436, - 13.572743332485638 - ], - [ - 2.004114104714325, - 13.574600773336414 - ], - [ - 2.007048620561563, - 13.576033006956274 - ], - [ - 2.0084759739809837, - 13.579792579072723 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "825a50-labels", + "properties": { + "label:description": "Geojson building labels for scene 825a50", + "area": "nia", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 2.0089069498293726, - 13.580816192675927 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 634 + } + ] + } ], - [ - 2.007310769661418, - 13.581639705514045 - ], - [ - 2.007598074888608, - 13.58234965082254 - ], - [ - 2.0043965058209636, - 13.583969811536608 - ], - [ - 2.001787365161808, - 13.578048562749643 - ], - [ - 2.000607112710697, - 13.577784497569377 + "license": "ODbL-1.0", + "datetime": "2017-09-19T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 2.000607112710697, - 13.570445755015827, - 2.0089069498293726, - 13.583969811536608 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 2.000607112710697, + 13.577784497569377 + ], + [ + 2.000938627053272, + 13.57440384053762 + ], + [ + 2.0026898208721526, + 13.572186843360377 + ], + [ + 2.0046604894641256, + 13.570445755015827 + ], + [ + 2.005587501792436, + 13.572743332485638 + ], + [ + 2.004114104714325, + 13.574600773336414 + ], + [ + 2.007048620561563, + 13.576033006956274 + ], + [ + 2.0084759739809837, + 13.579792579072723 + ], + [ + 2.0089069498293726, + 13.580816192675927 + ], + [ + 2.007310769661418, + 13.581639705514045 + ], + [ + 2.007598074888608, + 13.58234965082254 + ], + [ + 2.0043965058209636, + 13.583969811536608 + ], + [ + 2.001787365161808, + 13.578048562749643 + ], + [ + 2.000607112710697, + 13.577784497569377 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/nia/825a50-labels/825a50.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/nia/825a50-labels/825a50.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 2.000607112710697, + 13.570445755015827, + 2.0089069498293726, + 13.583969811536608 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "nia" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json b/tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json index 68f0d6f96..506945f9c 100644 --- a/tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json +++ b/tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json @@ -1,104 +1,105 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "825a50", - "properties": { - "area": "nia", - "license": "CC-BY-4.0", - "datetime": "2017-09-19T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 2.000607112710697, - 13.577784497569377 - ], - [ - 2.000938627053272, - 13.57440384053762 - ], - [ - 2.0026898208721526, - 13.572186843360377 - ], - [ - 2.0046604894641256, - 13.570445755015827 - ], - [ - 2.005587501792436, - 13.572743332485638 - ], - [ - 2.004114104714325, - 13.574600773336414 - ], - [ - 2.007048620561563, - 13.576033006956274 - ], - [ - 2.0084759739809837, - 13.579792579072723 - ], - [ - 2.0089069498293726, - 13.580816192675927 - ], - [ - 2.007310769661418, - 13.581639705514045 - ], - [ - 2.007598074888608, - 13.58234965082254 - ], - [ - 2.0043965058209636, - 13.583969811536608 - ], - [ - 2.001787365161808, - 13.578048562749643 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "825a50", + "properties": { + "area": "nia", + "license": "CC-BY-4.0", + "datetime": "2017-09-19T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 2.000607112710697, + 13.577784497569377 + ], + [ + 2.000938627053272, + 13.57440384053762 + ], + [ + 2.0026898208721526, + 13.572186843360377 + ], + [ + 2.0046604894641256, + 13.570445755015827 + ], + [ + 2.005587501792436, + 13.572743332485638 + ], + [ + 2.004114104714325, + 13.574600773336414 + ], + [ + 2.007048620561563, + 13.576033006956274 + ], + [ + 2.0084759739809837, + 13.579792579072723 + ], + [ + 2.0089069498293726, + 13.580816192675927 + ], + [ + 2.007310769661418, + 13.581639705514045 + ], + [ + 2.007598074888608, + 13.58234965082254 + ], + [ + 2.0043965058209636, + 13.583969811536608 + ], + [ + 2.001787365161808, + 13.578048562749643 + ], + [ + 2.000607112710697, + 13.577784497569377 + ] + ] ], - [ - 2.000607112710697, - 13.577784497569377 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 2.000607112710697, - 13.570445755015827, - 2.0089069498293726, - 13.583969811536608 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/nia/825a50/825a50.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/nia/825a50/825a50.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "nia" + "bbox": [ + 2.000607112710697, + 13.570445755015827, + 2.0089069498293726, + 13.583969811536608 + ], + "stac_extensions": [], + "collection": "nia" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/nia/collection.json b/tests/data-files/catalogs/test-case-4/nia/collection.json index 19cc515b0..7a3f0cc48 100644 --- a/tests/data-files/catalogs/test-case-4/nia/collection.json +++ b/tests/data-files/catalogs/test-case-4/nia/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "nia", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from nia", "links": [ { @@ -24,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -44,8 +46,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json b/tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json index 7503c9325..3896ab604 100644 --- a/tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json +++ b/tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json @@ -1,165 +1,166 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "abe1a3-labels", - "properties": { - "label:description": "Geojson building labels for scene abe1a3", - "area": "ptn", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 6135 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2019-01-04T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "abe1a3-labels", + "properties": { + "label:description": "Geojson building labels for scene abe1a3", + "area": "ptn", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 6135 + } + ] + } + ], + "license": "ODbL-1.0", + "datetime": "2019-01-04T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 11.885812792703362, - -4.768169362911695 - ], - [ - 11.887542976197118, - -4.7665107950056145 - ], - [ - 11.889180125287497, - -4.768291240422982 - ], - [ - 11.889699192128468, - -4.76889842830112 - ], - [ - 11.891293402748685, - -4.770574825339869 - ], - [ - 11.891822637745463, - -4.771199743826555 - ], - [ - 11.892485554938661, - -4.772038920080106 - ], - [ - 11.893232710228231, - -4.773116160519051 - ], - [ - 11.894047213717943, - -4.774217893668799 - ], - [ - 11.893651788278861, - -4.774975492862306 - ], - [ - 11.891492150063526, - -4.775882417747232 - ], - [ - 11.892229753203969, - -4.776764206607942 - ], - [ - 11.89160985269232, - -4.777368413435742 - ], - [ - 11.889106144363504, - -4.779159021273276 - ], - [ - 11.88601220532287, - -4.780589144725401 - ], - [ - 11.884413418672295, - -4.77899238585692 - ], - [ - 11.882857386891155, - -4.777623866347985 - ], - [ - 11.881942666093218, - -4.776764898160677 - ], - [ - 11.88116779930158, - -4.7758010394686305 - ], - [ - 11.881077083189387, - -4.775321000041606 - ], - [ - 11.879921982584099, - -4.773801879823182 - ], - [ - 11.880932380701283, - -4.772793177004842 - ], - [ - 11.885812792703362, - -4.768169362911695 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 11.879921982584099, - -4.780589144725401, - 11.894047213717943, - -4.7665107950056145 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 11.885812792703362, + -4.768169362911695 + ], + [ + 11.887542976197118, + -4.7665107950056145 + ], + [ + 11.889180125287497, + -4.768291240422982 + ], + [ + 11.889699192128468, + -4.76889842830112 + ], + [ + 11.891293402748685, + -4.770574825339869 + ], + [ + 11.891822637745463, + -4.771199743826555 + ], + [ + 11.892485554938661, + -4.772038920080106 + ], + [ + 11.893232710228231, + -4.773116160519051 + ], + [ + 11.894047213717943, + -4.774217893668799 + ], + [ + 11.893651788278861, + -4.774975492862306 + ], + [ + 11.891492150063526, + -4.775882417747232 + ], + [ + 11.892229753203969, + -4.776764206607942 + ], + [ + 11.89160985269232, + -4.777368413435742 + ], + [ + 11.889106144363504, + -4.779159021273276 + ], + [ + 11.88601220532287, + -4.780589144725401 + ], + [ + 11.884413418672295, + -4.77899238585692 + ], + [ + 11.882857386891155, + -4.777623866347985 + ], + [ + 11.881942666093218, + -4.776764898160677 + ], + [ + 11.88116779930158, + -4.7758010394686305 + ], + [ + 11.881077083189387, + -4.775321000041606 + ], + [ + 11.879921982584099, + -4.773801879823182 + ], + [ + 11.880932380701283, + -4.772793177004842 + ], + [ + 11.885812792703362, + -4.768169362911695 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/abe1a3-labels/abe1a3.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/abe1a3-labels/abe1a3.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + 11.879921982584099, + -4.780589144725401, + 11.894047213717943, + -4.7665107950056145 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json b/tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json index eb0be417d..b8deb4c6a 100644 --- a/tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json +++ b/tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json @@ -1,140 +1,141 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "abe1a3", - "properties": { - "area": "ptn", - "license": "CC-BY-4.0", - "datetime": "2019-01-04T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 11.885812792703362, - -4.768169362911695 - ], - [ - 11.887542976197118, - -4.7665107950056145 - ], - [ - 11.889180125287497, - -4.768291240422982 - ], - [ - 11.889699192128468, - -4.76889842830112 - ], - [ - 11.891293402748685, - -4.770574825339869 - ], - [ - 11.891822637745463, - -4.771199743826555 - ], - [ - 11.892485554938661, - -4.772038920080106 - ], - [ - 11.893232710228231, - -4.773116160519051 - ], - [ - 11.894047213717943, - -4.774217893668799 - ], - [ - 11.893651788278861, - -4.774975492862306 - ], - [ - 11.891492150063526, - -4.775882417747232 - ], - [ - 11.892229753203969, - -4.776764206607942 - ], - [ - 11.89160985269232, - -4.777368413435742 - ], - [ - 11.889106144363504, - -4.779159021273276 - ], - [ - 11.88601220532287, - -4.780589144725401 - ], - [ - 11.884413418672295, - -4.77899238585692 - ], - [ - 11.882857386891155, - -4.777623866347985 - ], - [ - 11.881942666093218, - -4.776764898160677 - ], - [ - 11.88116779930158, - -4.7758010394686305 - ], - [ - 11.881077083189387, - -4.775321000041606 - ], - [ - 11.879921982584099, - -4.773801879823182 - ], - [ - 11.880932380701283, - -4.772793177004842 - ], - [ - 11.885812792703362, - -4.768169362911695 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 11.879921982584099, - -4.780589144725401, - 11.894047213717943, - -4.7665107950056145 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "abe1a3", + "properties": { + "area": "ptn", + "license": "CC-BY-4.0", + "datetime": "2019-01-04T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 11.885812792703362, + -4.768169362911695 + ], + [ + 11.887542976197118, + -4.7665107950056145 + ], + [ + 11.889180125287497, + -4.768291240422982 + ], + [ + 11.889699192128468, + -4.76889842830112 + ], + [ + 11.891293402748685, + -4.770574825339869 + ], + [ + 11.891822637745463, + -4.771199743826555 + ], + [ + 11.892485554938661, + -4.772038920080106 + ], + [ + 11.893232710228231, + -4.773116160519051 + ], + [ + 11.894047213717943, + -4.774217893668799 + ], + [ + 11.893651788278861, + -4.774975492862306 + ], + [ + 11.891492150063526, + -4.775882417747232 + ], + [ + 11.892229753203969, + -4.776764206607942 + ], + [ + 11.89160985269232, + -4.777368413435742 + ], + [ + 11.889106144363504, + -4.779159021273276 + ], + [ + 11.88601220532287, + -4.780589144725401 + ], + [ + 11.884413418672295, + -4.77899238585692 + ], + [ + 11.882857386891155, + -4.777623866347985 + ], + [ + 11.881942666093218, + -4.776764898160677 + ], + [ + 11.88116779930158, + -4.7758010394686305 + ], + [ + 11.881077083189387, + -4.775321000041606 + ], + [ + 11.879921982584099, + -4.773801879823182 + ], + [ + 11.880932380701283, + -4.772793177004842 + ], + [ + 11.885812792703362, + -4.768169362911695 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/abe1a3/abe1a3.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "ptn" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/abe1a3/abe1a3.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + 11.879921982584099, + -4.780589144725401, + 11.894047213717943, + -4.7665107950056145 + ], + "stac_extensions": [], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/ptn/collection.json b/tests/data-files/catalogs/test-case-4/ptn/collection.json index e09ddd6f0..9f537d14a 100644 --- a/tests/data-files/catalogs/test-case-4/ptn/collection.json +++ b/tests/data-files/catalogs/test-case-4/ptn/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "ptn", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from ptn", "links": [ { @@ -34,6 +35,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -54,8 +56,5 @@ ] } }, - "license": "various", - "stac_extensions": [ - "label" - ] + "license": "various" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json b/tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json index 53b77823f..b29df6e49 100644 --- a/tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json +++ b/tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json @@ -1,121 +1,122 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f49f31-labels", - "properties": { - "label:description": "Geojson building labels for scene f49f31", - "area": "ptn", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 2596 - } - ] - } - ], - "license": "ODbL-1.0", - "datetime": "2019-01-04T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 11.8872283746714, - -4.797808782187126 - ], - [ - 11.88972534766154, - -4.798264503493327 - ], - [ - 11.891605198049556, - -4.798340457044355 - ], - [ - 11.8955075180291, - -4.802072082202417 - ], - [ - 11.897849273966862, - -4.804253455211011 - ], - [ - 11.897268627830597, - -4.804913066085889 - ], - [ - 11.89685404132025, - -4.805243717903951 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f49f31-labels", + "properties": { + "label:description": "Geojson building labels for scene f49f31", + "area": "ptn", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 11.891979599532954, - -4.803719848444295 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 2596 + } + ] + } ], - [ - 11.885939170933694, - -4.801674355838536 - ], - [ - 11.886296262431463, - -4.800109049407727 - ], - [ - 11.886753664977459, - -4.797722543259397 - ], - [ - 11.8872283746714, - -4.797808782187126 + "license": "ODbL-1.0", + "datetime": "2019-01-04T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 11.885939170933694, - -4.805243717903951, - 11.897849273966862, - -4.797722543259397 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 11.8872283746714, + -4.797808782187126 + ], + [ + 11.88972534766154, + -4.798264503493327 + ], + [ + 11.891605198049556, + -4.798340457044355 + ], + [ + 11.8955075180291, + -4.802072082202417 + ], + [ + 11.897849273966862, + -4.804253455211011 + ], + [ + 11.897268627830597, + -4.804913066085889 + ], + [ + 11.89685404132025, + -4.805243717903951 + ], + [ + 11.891979599532954, + -4.803719848444295 + ], + [ + 11.885939170933694, + -4.801674355838536 + ], + [ + 11.886296262431463, + -4.800109049407727 + ], + [ + 11.886753664977459, + -4.797722543259397 + ], + [ + 11.8872283746714, + -4.797808782187126 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/f49f31-labels/f49f31.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/f49f31-labels/f49f31.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + 11.885939170933694, + -4.805243717903951, + 11.897849273966862, + -4.797722543259397 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json b/tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json index 82ca1c2df..777c31a96 100644 --- a/tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json +++ b/tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "f49f31", - "properties": { - "area": "ptn", - "license": "CC-BY-4.0", - "datetime": "2019-01-04T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 11.8872283746714, - -4.797808782187126 - ], - [ - 11.88972534766154, - -4.798264503493327 - ], - [ - 11.891605198049556, - -4.798340457044355 - ], - [ - 11.8955075180291, - -4.802072082202417 - ], - [ - 11.897849273966862, - -4.804253455211011 - ], - [ - 11.897268627830597, - -4.804913066085889 - ], - [ - 11.89685404132025, - -4.805243717903951 - ], - [ - 11.891979599532954, - -4.803719848444295 - ], - [ - 11.885939170933694, - -4.801674355838536 - ], - [ - 11.886296262431463, - -4.800109049407727 - ], - [ - 11.886753664977459, - -4.797722543259397 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "f49f31", + "properties": { + "area": "ptn", + "license": "CC-BY-4.0", + "datetime": "2019-01-04T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 11.8872283746714, + -4.797808782187126 + ], + [ + 11.88972534766154, + -4.798264503493327 + ], + [ + 11.891605198049556, + -4.798340457044355 + ], + [ + 11.8955075180291, + -4.802072082202417 + ], + [ + 11.897849273966862, + -4.804253455211011 + ], + [ + 11.897268627830597, + -4.804913066085889 + ], + [ + 11.89685404132025, + -4.805243717903951 + ], + [ + 11.891979599532954, + -4.803719848444295 + ], + [ + 11.885939170933694, + -4.801674355838536 + ], + [ + 11.886296262431463, + -4.800109049407727 + ], + [ + 11.886753664977459, + -4.797722543259397 + ], + [ + 11.8872283746714, + -4.797808782187126 + ] + ] ], - [ - 11.8872283746714, - -4.797808782187126 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 11.885939170933694, - -4.805243717903951, - 11.897849273966862, - -4.797722543259397 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/f49f31/f49f31.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/ptn/f49f31/f49f31.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "ptn" + "bbox": [ + 11.885939170933694, + -4.805243717903951, + 11.897849273966862, + -4.797722543259397 + ], + "stac_extensions": [], + "collection": "ptn" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json b/tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json index 2c5d38a35..2c8ea9eef 100644 --- a/tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "06f252-labels", - "properties": { - "label:description": "Geojson building labels for scene 06f252", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1181 - } - ] - } - ], - "datetime": "2016-09-21T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.316367972905894, - -5.905850055917426 - ], - [ - 39.31369474037646, - -5.905851570308218 - ], - [ - 39.31367952293228, - -5.878740108331854 - ], - [ - 39.34078047573221, - -5.878724233277496 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "06f252-labels", + "properties": { + "label:description": "Geojson building labels for scene 06f252", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.340797007857546, - -5.905835621542266 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1181 + } + ] + } ], - [ - 39.316367972905894, - -5.905850055917426 + "datetime": "2016-09-21T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31367952293228, - -5.905851570308218, - 39.340797007857546, - -5.878724233277496 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.316367972905894, + -5.905850055917426 + ], + [ + 39.31369474037646, + -5.905851570308218 + ], + [ + 39.31367952293228, + -5.878740108331854 + ], + [ + 39.34078047573221, + -5.878724233277496 + ], + [ + 39.340797007857546, + -5.905835621542266 + ], + [ + 39.316367972905894, + -5.905850055917426 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/06f252-labels/06f252.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/06f252-labels/06f252.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.31367952293228, + -5.905851570308218, + 39.340797007857546, + -5.878724233277496 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json b/tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json index d897564a1..0650d7bec 100644 --- a/tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json +++ b/tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "06f252", - "properties": { - "area": "znz", - "datetime": "2016-09-21T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.316367972905894, - -5.905850055917426 - ], - [ - 39.31369474037646, - -5.905851570308218 - ], - [ - 39.31367952293228, - -5.878740108331854 - ], - [ - 39.34078047573221, - -5.878724233277496 - ], - [ - 39.340797007857546, - -5.905835621542266 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "06f252", + "properties": { + "area": "znz", + "datetime": "2016-09-21T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.316367972905894, + -5.905850055917426 + ], + [ + 39.31369474037646, + -5.905851570308218 + ], + [ + 39.31367952293228, + -5.878740108331854 + ], + [ + 39.34078047573221, + -5.878724233277496 + ], + [ + 39.340797007857546, + -5.905835621542266 + ], + [ + 39.316367972905894, + -5.905850055917426 + ] + ] ], - [ - 39.316367972905894, - -5.905850055917426 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31367952293228, - -5.905851570308218, - 39.340797007857546, - -5.878724233277496 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/06f252/06f252.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/06f252/06f252.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.31367952293228, + -5.905851570308218, + 39.340797007857546, + -5.878724233277496 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json b/tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json index 06b9a91de..366e5f7f0 100644 --- a/tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json @@ -1,108 +1,109 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "076995-labels", - "properties": { - "label:description": "Geojson building labels for scene 076995", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 843 - } - ] - } - ], - "datetime": "2016-10-04T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.3407474698636, - -5.824437937045834 - ], - [ - 39.36248225709067, - -5.824427688553141 - ], - [ - 39.3625604145535, - -5.824877893423849 - ], - [ - 39.36300752853781, - -5.828561459551643 - ], - [ - 39.36335786823858, - -5.832067348597373 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "076995-labels", + "properties": { + "label:description": "Geojson building labels for scene 076995", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.3631508602256, - -5.849635373302928 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 843 + } + ] + } ], - [ - 39.362683761021664, - -5.851560567141806 - ], - [ - 39.34075687849739, - -5.851573055804989 - ], - [ - 39.3407474698636, - -5.824437937045834 + "datetime": "2016-10-04T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ] - }, - "bbox": [ - 39.34073844240503, - -5.851576358919395, - 39.36335800442592, - -5.824424385662977 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.3407474698636, + -5.824437937045834 + ], + [ + 39.36248225709067, + -5.824427688553141 + ], + [ + 39.3625604145535, + -5.824877893423849 + ], + [ + 39.36300752853781, + -5.828561459551643 + ], + [ + 39.36335786823858, + -5.832067348597373 + ], + [ + 39.3631508602256, + -5.849635373302928 + ], + [ + 39.362683761021664, + -5.851560567141806 + ], + [ + 39.34075687849739, + -5.851573055804989 + ], + [ + 39.3407474698636, + -5.824437937045834 + ] + ] + ] }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/076995-labels/076995.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/076995-labels/076995.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + 39.34073844240503, + -5.851576358919395, + 39.36335800442592, + -5.824424385662977 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/076995/076995.json b/tests/data-files/catalogs/test-case-4/znz/076995/076995.json index 987303e83..b411ef394 100644 --- a/tests/data-files/catalogs/test-case-4/znz/076995/076995.json +++ b/tests/data-files/catalogs/test-case-4/znz/076995/076995.json @@ -1,83 +1,84 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "076995", - "properties": { - "area": "znz", - "datetime": "2016-10-04T00:00:00Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.3407474698636, - -5.824437937045834 - ], - [ - 39.36248225709067, - -5.824427688553141 - ], - [ - 39.3625604145535, - -5.824877893423849 - ], - [ - 39.36300752853781, - -5.828561459551643 - ], - [ - 39.36335786823858, - -5.832067348597373 - ], - [ - 39.3631508602256, - -5.849635373302928 - ], - [ - 39.362683761021664, - -5.851560567141806 - ], - [ - 39.34075687849739, - -5.851573055804989 - ], - [ - 39.3407474698636, - -5.824437937045834 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "076995", + "properties": { + "area": "znz", + "datetime": "2016-10-04T00:00:00Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.3407474698636, + -5.824437937045834 + ], + [ + 39.36248225709067, + -5.824427688553141 + ], + [ + 39.3625604145535, + -5.824877893423849 + ], + [ + 39.36300752853781, + -5.828561459551643 + ], + [ + 39.36335786823858, + -5.832067348597373 + ], + [ + 39.3631508602256, + -5.849635373302928 + ], + [ + 39.362683761021664, + -5.851560567141806 + ], + [ + 39.34075687849739, + -5.851573055804989 + ], + [ + 39.3407474698636, + -5.824437937045834 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.34073844240503, - -5.851576358919395, - 39.36335800442592, - -5.824424385662977 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/076995/076995.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/076995/076995.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.34073844240503, + -5.851576358919395, + 39.36335800442592, + -5.824424385662977 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json b/tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json index c466c3505..c7e40d47f 100644 --- a/tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json @@ -1,1164 +1,1165 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "33cae6-labels", - "properties": { - "label:description": "Geojson building labels for scene 33cae6", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 4439 - } - ] - } - ], - "datetime": "2016-08-28T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "33cae6-labels", + "properties": { + "label:description": "Geojson building labels for scene 33cae6", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 4439 + } + ] + } + ], + "datetime": "2016-08-28T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.313602918515315, - -5.740028584511519 - ], - [ - 39.31360456846548, - -5.743046680921876 - ], - [ - 39.28771158148321, - -5.743060299502631 - ], - [ - 39.287833992413255, - -5.7415673066017945 - ], - [ - 39.28784518594579, - -5.741561693713168 - ], - [ - 39.287867395743724, - -5.741197211035715 - ], - [ - 39.28787859137919, - -5.741195803586043 - ], - [ - 39.28788965898581, - -5.740939266150898 - ], - [ - 39.28789736103782, - -5.740948374060875 - ], - [ - 39.28794532863633, - -5.740317533846062 - ], - [ - 39.287956518978035, - -5.740305612797183 - ], - [ - 39.28795645988263, - -5.740187860493621 - ], - [ - 39.28796695643181, - -5.7401878552086325 - ], - [ - 39.287978729048056, - -5.739941831012015 - ], - [ - 39.28799062442844, - -5.739940423208991 - ], - [ - 39.28801212626218, - -5.739559820014053 - ], - [ - 39.288023319754615, - -5.739554207123601 - ], - [ - 39.28805666134313, - -5.7390614528792385 - ], - [ - 39.28806785412197, - -5.739054438175143 - ], - [ - 39.288068492322395, - -5.7389317791690555 - ], - [ - 39.28807898849667, - -5.7389310729765715 - ], - [ - 39.288078926927646, - -5.738808414322463 - ], - [ - 39.28809012040507, - -5.738802801431323 - ], - [ - 39.288123461924656, - -5.73831004717524 - ], - [ - 39.28813465468841, - -5.738303032470432 - ], - [ - 39.28813459311428, - -5.73818037381424 - ], - [ - 39.288145786579165, - -5.738174760922529 - ], - [ - 39.28817912804129, - -5.737682006656658 - ], - [ - 39.28819032114434, - -5.737675692857872 - ], - [ - 39.28819025921321, - -5.737552333292979 - ], - [ - 39.28820145548069, - -5.737552327653656 - ], - [ - 39.28825705989423, - -5.736801628458127 - ], - [ - 39.28826825297967, - -5.736795314658528 - ], - [ - 39.28826819420862, - -5.736678263250428 - ], - [ - 39.288279389755104, - -5.736676855797169 - ], - [ - 39.28829046247506, - -5.736430831926878 - ], - [ - 39.288301655905016, - -5.736425219033569 - ], - [ - 39.28831272544848, - -5.736172887001599 - ], - [ - 39.28832392168893, - -5.736172881361234 - ], - [ - 39.28832386009737, - -5.736050222697981 - ], - [ - 39.28833505316777, - -5.736043908897686 - ], - [ - 39.28833569380363, - -5.735926156228032 - ], - [ - 39.28833849849387, - -5.735937369324319 - ], - [ - 39.28834688968728, - -5.735925449680809 - ], - [ - 39.288368397250515, - -5.735556761851868 - ], - [ - 39.28837959277493, - -5.7355553543976985 - ], - [ - 39.28843519161201, - -5.734794141554009 - ], - [ - 39.288446387121425, - -5.734792734099254 - ], - [ - 39.28844632551862, - -5.734670075431432 - ], - [ - 39.28845752137762, - -5.73466936888325 - ], - [ - 39.288468594025474, - -5.734423344998106 - ], - [ - 39.28847978741536, - -5.734417732102973 - ], - [ - 39.288490857237925, - -5.734166100962491 - ], - [ - 39.28850205273499, - -5.734164693507241 - ], - [ - 39.28855765700006, - -5.733414695136926 - ], - [ - 39.28856885248234, - -5.733413287681076 - ], - [ - 39.28856879122039, - -5.733291329915387 - ], - [ - 39.288579986700235, - -5.733289922459435 - ], - [ - 39.288579925085166, - -5.7331672637866395 - ], - [ - 39.288586228570765, - -5.733178475119921 - ], - [ - 39.288591061058995, - -5.733047403097616 - ], - [ - 39.28860225301303, - -5.733038986574412 - ], - [ - 39.28861332243345, - -5.732786654516813 - ], - [ - 39.28862451790337, - -5.732785247060469 - ], - [ - 39.28862445663636, - -5.732663289292712 - ], - [ - 39.288635652103856, - -5.732661881836264 - ], - [ - 39.28863629024428, - -5.7325392228087 - ], - [ - 39.28865791802952, - -5.732410245039324 - ], - [ - 39.28866898742718, - -5.732157912976972 - ], - [ - 39.28868018358902, - -5.7321579073335736 - ], - [ - 39.28868012196481, - -5.732035248657027 - ], - [ - 39.28869131495492, - -5.732028934853017 - ], - [ - 39.2886912557947, - -5.731911182523131 - ], - [ - 39.28870315135938, - -5.731910475620081 - ], - [ - 39.288802586506456, - -5.730655802114353 - ], - [ - 39.28881378193448, - -5.730654394656317 - ], - [ - 39.28881372029802, - -5.730531735974798 - ], - [ - 39.28882002375656, - -5.73054294730776 - ], - [ - 39.28883599174512, - -5.730291313668497 - ], - [ - 39.288847182939215, - -5.730281495329069 - ], - [ - 39.288847117073004, - -5.730150425765198 - ], - [ - 39.28885831319556, - -5.73015042012029 - ], - [ - 39.28885825155506, - -5.730027761437102 - ], - [ - 39.28886944450505, - -5.730021447631236 - ], - [ - 39.28886938709037, - -5.7299071998288 - ], - [ - 39.28888058109469, - -5.729902988743108 - ], - [ - 39.288880523326746, - -5.729788040033545 - ], - [ - 39.28890284721865, - -5.729652052830054 - ], - [ - 39.288902782051544, - -5.7295223850775026 - ], - [ - 39.28891397816181, - -5.72952237943212 - ], - [ - 39.28894247655001, - -5.729141071679603 - ], - [ - 39.288947380129216, - -5.729151582811278 - ], - [ - 39.28904751678759, - -5.727901815188423 - ], - [ - 39.28905940945, - -5.7278955010276675 - ], - [ - 39.28914770917402, - -5.726763491701007 - ], - [ - 39.28916029346079, - -5.726741056329244 - ], - [ - 39.28918111127163, - -5.726393395953501 - ], - [ - 39.289192304853835, - -5.726388483957942 - ], - [ - 39.28919223965883, - -5.726258816193987 - ], - [ - 39.28920343288595, - -5.72625320329149 - ], - [ - 39.289214507699526, - -5.726012085692045 - ], - [ - 39.289225700921726, - -5.72600647278932 - ], - [ - 39.289225641714694, - -5.725888720440319 - ], - [ - 39.28923613764901, - -5.725888014238375 - ], - [ - 39.2892367757264, - -5.725765355188089 - ], - [ - 39.28924727165846, - -5.7257646489860505 - ], - [ - 39.289403768567006, - -5.7238812328140325 - ], - [ - 39.289415663964434, - -5.723880525904426 - ], - [ - 39.289503962766254, - -5.7227485164315155 - ], - [ - 39.28951654695881, - -5.722726081053947 - ], - [ - 39.289537364530666, - -5.722378420634993 - ], - [ - 39.289548559090875, - -5.722375611356616 - ], - [ - 39.28961529374991, - -5.721498742899586 - ], - [ - 39.29004098667544, - -5.720599264075941 - ], - [ - 39.290073866967184, - -5.7205838275003424 - ], - [ - 39.29009694163223, - -5.720550172295505 - ], - [ - 39.29011863375526, - -5.720550161329309 - ], - [ - 39.29013051778038, - -5.720527025387051 - ], - [ - 39.29015220990249, - -5.720527014419632 - ], - [ - 39.290164094279014, - -5.720504579383667 - ], - [ - 39.290181588278394, - -5.720505271444961 - ], - [ - 39.290208861408814, - -5.720471614112995 - ], - [ - 39.29026413001999, - -5.720449157133799 - ], - [ - 39.2903207811543, - -5.72039305590999 - ], - [ - 39.29034317266325, - -5.720392343674706 - ], - [ - 39.2903543576374, - -5.720370609896142 - ], - [ - 39.290421516252074, - -5.720336932373324 - ], - [ - 39.29043270086868, - -5.720314497686497 - ], - [ - 39.290488668847225, - -5.720291339425736 - ], - [ - 39.29054462055196, - -5.720235939442509 - ], - [ - 39.29059988877251, - -5.720212781525714 - ], - [ - 39.29065654020414, - -5.720157381178035 - ], - [ - 39.290711808412254, - -5.7201342232510095 - ], - [ - 39.290768459825216, - -5.720078822893069 - ], - [ - 39.290823728020904, - -5.720055664955817 - ], - [ - 39.290880379415185, - -5.720000264587617 - ], - [ - 39.29093564759845, - -5.719977106640133 - ], - [ - 39.29099229897406, - -5.719921706261678 - ], - [ - 39.29104756714489, - -5.719898548303964 - ], - [ - 39.29110421850183, - -5.719843147915251 - ], - [ - 39.291159486660234, - -5.71981998994731 - ], - [ - 39.2912161379985, - -5.719764589548339 - ], - [ - 39.29127140614448, - -5.719741431570171 - ], - [ - 39.291328057464064, - -5.719686031160943 - ], - [ - 39.29138332559762, - -5.719662873172546 - ], - [ - 39.29143997689853, - -5.7196074727530615 - ], - [ - 39.29149524501966, - -5.71958431475444 - ], - [ - 39.2915518963019, - -5.7195289143247 - ], - [ - 39.2916071644106, - -5.719505756315848 - ], - [ - 39.29166381567417, - -5.719450355875855 - ], - [ - 39.29171908377044, - -5.719427197856779 - ], - [ - 39.291775735015335, - -5.719371797406529 - ], - [ - 39.291831003099176, - -5.719348639377229 - ], - [ - 39.2918876543254, - -5.719293238916724 - ], - [ - 39.291942922396814, - -5.719270080877199 - ], - [ - 39.29199957360437, - -5.719214680406439 - ], - [ - 39.29205484166336, - -5.719191522356688 - ], - [ - 39.29211149249672, - -5.719135420968851 - ], - [ - 39.292166760898795, - -5.719112963815699 - ], - [ - 39.29222341171336, - -5.7190568624176175 - ], - [ - 39.292279379847066, - -5.719034404897935 - ], - [ - 39.29228986497856, - -5.719012671441992 - ], - [ - 39.294664106892064, - -5.719033185666966 - ], - [ - 39.29467603158522, - -5.719089953000165 - ], - [ - 39.29465437431092, - -5.719157952082305 - ], - [ - 39.29465371114527, - -5.719229444921172 - ], - [ - 39.29463135734711, - -5.7193037525216885 - ], - [ - 39.29462023566773, - -5.719448845947391 - ], - [ - 39.29460904011777, - -5.719449552600848 - ], - [ - 39.29447793731705, - -5.720327856151281 - ], - [ - 39.29566180023131, - -5.720120479955832 - ], - [ - 39.29563842688224, - -5.719571681995611 - ], - [ - 39.29681400855618, - -5.719592803370352 - ], - [ - 39.29682521096831, - -5.7196054138976775 - ], - [ - 39.29732832640864, - -5.719603751672776 - ], - [ - 39.29734023364275, - -5.719626174517702 - ], - [ - 39.297709699002866, - -5.7196266839006 - ], - [ - 39.297720125955514, - -5.719492805322403 - ], - [ - 39.29773201073462, - -5.719471771958087 - ], - [ - 39.297743206640426, - -5.719471766150352 - ], - [ - 39.29900146876119, - -5.71970661665732 - ], - [ - 39.29903085693057, - -5.719704498626807 - ], - [ - 39.2990308274491, - -5.719647725210644 - ], - [ - 39.2991875701586, - -5.719647643525276 - ], - [ - 39.29918827572937, - -5.719658857662318 - ], - [ - 39.2993443186954, - -5.719658776298749 - ], - [ - 39.29951086985279, - -5.719681819324766 - ], - [ - 39.299517826100136, - -5.719602613256507 - ], - [ - 39.29961229156103, - -5.719602563951844 - ], - [ - 39.299615796116605, - -5.71961377662683 - ], - [ - 39.29974665411206, - -5.719624922805369 - ], - [ - 39.29974805943782, - -5.719636136575718 - ], - [ - 39.299847423111444, - -5.719636084673931 - ], - [ - 39.299851627415954, - -5.719647296981645 - ], - [ - 39.29994819795517, - -5.719658461025615 - ], - [ - 39.299925719258574, - -5.719491657023776 - ], - [ - 39.299914522989596, - -5.719490961967785 - ], - [ - 39.29991514680767, - -5.719345173087227 - ], - [ - 39.3000830849578, - -5.7193443844051135 - ], - [ - 39.30009354641723, - -5.719277792817777 - ], - [ - 39.300127096127426, - -5.719204880999829 - ], - [ - 39.300138941725315, - -5.719108850614782 - ], - [ - 39.3015986183503, - -5.719130514515799 - ], - [ - 39.301606321401344, - -5.7191417249709 - ], - [ - 39.30197509796063, - -5.71916396008395 - ], - [ - 39.30197650332791, - -5.719175173846801 - ], - [ - 39.30209755894922, - -5.719175110147159 - ], - [ - 39.302103862523296, - -5.719186321331581 - ], - [ - 39.30408422522446, - -5.719353493113251 - ], - [ - 39.30409122895187, - -5.7193654048089595 - ], - [ - 39.30421508356364, - -5.719365339177715 - ], - [ - 39.30421998732256, - -5.719375850172232 - ], - [ - 39.30433754424296, - -5.719375787852325 - ], - [ - 39.304338249542674, - -5.719386301071832 - ], - [ - 39.304460005295915, - -5.719386937406598 - ], - [ - 39.30446071059788, - -5.71939745062583 - ], - [ - 39.304583865839405, - -5.719398086191834 - ], - [ - 39.30458457114364, - -5.719408599410791 - ], - [ - 39.3047063268991, - -5.719409235693033 - ], - [ - 39.304707731949186, - -5.719419748540238 - ], - [ - 39.30483018744935, - -5.719420384424596 - ], - [ - 39.30483509159077, - -5.719431596319216 - ], - [ - 39.304964544156945, - -5.719431527552395 - ], - [ - 39.304965249839356, - -5.719442741676491 - ], - [ - 39.30570350908857, - -5.719498421435598 - ], - [ - 39.305737803224545, - -5.719511019479368 - ], - [ - 39.30571619973147, - -5.7196778466475715 - ], - [ - 39.30605068324513, - -5.719688882882785 - ], - [ - 39.30610737404596, - -5.7197105807395285 - ], - [ - 39.30641456569894, - -5.719718126786076 - ], - [ - 39.30643057887232, - -5.719566021615056 - ], - [ - 39.30644737234725, - -5.7195653117428185 - ], - [ - 39.30658243481406, - -5.71958766860887 - ], - [ - 39.30755791938568, - -5.71966564819061 - ], - [ - 39.30756702204364, - -5.719676857809007 - ], - [ - 39.30805965961166, - -5.719710237089115 - ], - [ - 39.308066663423446, - -5.719722148731534 - ], - [ - 39.308189818321985, - -5.719722082614072 - ], - [ - 39.308312279104385, - -5.719732530432556 - ], - [ - 39.308312284733425, - -5.719743044019035 - ], - [ - 39.30843544000899, - -5.719743678754385 - ], - [ - 39.30843544564029, - -5.719754192340735 - ], - [ - 39.30855860091696, - -5.7197548270495195 - ], - [ - 39.30855930629429, - -5.7197653402595465 - ], - [ - 39.308681761828296, - -5.719765975317966 - ], - [ - 39.30868246720789, - -5.719776488527715 - ], - [ - 39.308804922742986, - -5.719777123559718 - ], - [ - 39.308805628124844, - -5.71978763676919 - ], - [ - 39.308927383917265, - -5.719788272151425 - ], - [ - 39.308928789045154, - -5.719798784983975 - ], - [ - 39.30905124458244, - -5.71979941996315 - ], - [ - 39.309056148807734, - -5.719810631816969 - ], - [ - 39.309923167539615, - -5.719877451244321 - ], - [ - 39.31063422293008, - -5.720091544007215 - ], - [ - 39.31065381500645, - -5.720090131590763 - ], - [ - 39.310662899577814, - -5.720067697687457 - ], - [ - 39.31067404178256, - -5.7199681630314965 - ], - [ - 39.31074121076393, - -5.719956211268805 - ], - [ - 39.31163696137919, - -5.720100813064275 - ], - [ - 39.311699250733554, - -5.720123208220514 - ], - [ - 39.31180561219929, - -5.720123851348939 - ], - [ - 39.311906387102475, - -5.720145524665541 - ], - [ - 39.311906393177225, - -5.720156739153791 - ], - [ - 39.31197216911476, - -5.720156703401592 - ], - [ - 39.31197427442288, - -5.720167916748612 - ], - [ - 39.31210233973633, - -5.720190276099752 - ], - [ - 39.31212192953011, - -5.720184658200196 - ], - [ - 39.3121295815013, - -5.720101246259322 - ], - [ - 39.31215196266817, - -5.720081608726603 - ], - [ - 39.31337307522748, - -5.720190284531242 - ], - [ - 39.3133506857093, - -5.720194502190286 - ], - [ - 39.31358511825828, - -5.720228017609581 - ], - [ - 39.313602918515315, - -5.740028584511519 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.287711527683925, - -5.743060299502631, - 39.31360456846548, - -5.719012276837555 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.313602918515315, + -5.740028584511519 + ], + [ + 39.31360456846548, + -5.743046680921876 + ], + [ + 39.28771158148321, + -5.743060299502631 + ], + [ + 39.287833992413255, + -5.7415673066017945 + ], + [ + 39.28784518594579, + -5.741561693713168 + ], + [ + 39.287867395743724, + -5.741197211035715 + ], + [ + 39.28787859137919, + -5.741195803586043 + ], + [ + 39.28788965898581, + -5.740939266150898 + ], + [ + 39.28789736103782, + -5.740948374060875 + ], + [ + 39.28794532863633, + -5.740317533846062 + ], + [ + 39.287956518978035, + -5.740305612797183 + ], + [ + 39.28795645988263, + -5.740187860493621 + ], + [ + 39.28796695643181, + -5.7401878552086325 + ], + [ + 39.287978729048056, + -5.739941831012015 + ], + [ + 39.28799062442844, + -5.739940423208991 + ], + [ + 39.28801212626218, + -5.739559820014053 + ], + [ + 39.288023319754615, + -5.739554207123601 + ], + [ + 39.28805666134313, + -5.7390614528792385 + ], + [ + 39.28806785412197, + -5.739054438175143 + ], + [ + 39.288068492322395, + -5.7389317791690555 + ], + [ + 39.28807898849667, + -5.7389310729765715 + ], + [ + 39.288078926927646, + -5.738808414322463 + ], + [ + 39.28809012040507, + -5.738802801431323 + ], + [ + 39.288123461924656, + -5.73831004717524 + ], + [ + 39.28813465468841, + -5.738303032470432 + ], + [ + 39.28813459311428, + -5.73818037381424 + ], + [ + 39.288145786579165, + -5.738174760922529 + ], + [ + 39.28817912804129, + -5.737682006656658 + ], + [ + 39.28819032114434, + -5.737675692857872 + ], + [ + 39.28819025921321, + -5.737552333292979 + ], + [ + 39.28820145548069, + -5.737552327653656 + ], + [ + 39.28825705989423, + -5.736801628458127 + ], + [ + 39.28826825297967, + -5.736795314658528 + ], + [ + 39.28826819420862, + -5.736678263250428 + ], + [ + 39.288279389755104, + -5.736676855797169 + ], + [ + 39.28829046247506, + -5.736430831926878 + ], + [ + 39.288301655905016, + -5.736425219033569 + ], + [ + 39.28831272544848, + -5.736172887001599 + ], + [ + 39.28832392168893, + -5.736172881361234 + ], + [ + 39.28832386009737, + -5.736050222697981 + ], + [ + 39.28833505316777, + -5.736043908897686 + ], + [ + 39.28833569380363, + -5.735926156228032 + ], + [ + 39.28833849849387, + -5.735937369324319 + ], + [ + 39.28834688968728, + -5.735925449680809 + ], + [ + 39.288368397250515, + -5.735556761851868 + ], + [ + 39.28837959277493, + -5.7355553543976985 + ], + [ + 39.28843519161201, + -5.734794141554009 + ], + [ + 39.288446387121425, + -5.734792734099254 + ], + [ + 39.28844632551862, + -5.734670075431432 + ], + [ + 39.28845752137762, + -5.73466936888325 + ], + [ + 39.288468594025474, + -5.734423344998106 + ], + [ + 39.28847978741536, + -5.734417732102973 + ], + [ + 39.288490857237925, + -5.734166100962491 + ], + [ + 39.28850205273499, + -5.734164693507241 + ], + [ + 39.28855765700006, + -5.733414695136926 + ], + [ + 39.28856885248234, + -5.733413287681076 + ], + [ + 39.28856879122039, + -5.733291329915387 + ], + [ + 39.288579986700235, + -5.733289922459435 + ], + [ + 39.288579925085166, + -5.7331672637866395 + ], + [ + 39.288586228570765, + -5.733178475119921 + ], + [ + 39.288591061058995, + -5.733047403097616 + ], + [ + 39.28860225301303, + -5.733038986574412 + ], + [ + 39.28861332243345, + -5.732786654516813 + ], + [ + 39.28862451790337, + -5.732785247060469 + ], + [ + 39.28862445663636, + -5.732663289292712 + ], + [ + 39.288635652103856, + -5.732661881836264 + ], + [ + 39.28863629024428, + -5.7325392228087 + ], + [ + 39.28865791802952, + -5.732410245039324 + ], + [ + 39.28866898742718, + -5.732157912976972 + ], + [ + 39.28868018358902, + -5.7321579073335736 + ], + [ + 39.28868012196481, + -5.732035248657027 + ], + [ + 39.28869131495492, + -5.732028934853017 + ], + [ + 39.2886912557947, + -5.731911182523131 + ], + [ + 39.28870315135938, + -5.731910475620081 + ], + [ + 39.288802586506456, + -5.730655802114353 + ], + [ + 39.28881378193448, + -5.730654394656317 + ], + [ + 39.28881372029802, + -5.730531735974798 + ], + [ + 39.28882002375656, + -5.73054294730776 + ], + [ + 39.28883599174512, + -5.730291313668497 + ], + [ + 39.288847182939215, + -5.730281495329069 + ], + [ + 39.288847117073004, + -5.730150425765198 + ], + [ + 39.28885831319556, + -5.73015042012029 + ], + [ + 39.28885825155506, + -5.730027761437102 + ], + [ + 39.28886944450505, + -5.730021447631236 + ], + [ + 39.28886938709037, + -5.7299071998288 + ], + [ + 39.28888058109469, + -5.729902988743108 + ], + [ + 39.288880523326746, + -5.729788040033545 + ], + [ + 39.28890284721865, + -5.729652052830054 + ], + [ + 39.288902782051544, + -5.7295223850775026 + ], + [ + 39.28891397816181, + -5.72952237943212 + ], + [ + 39.28894247655001, + -5.729141071679603 + ], + [ + 39.288947380129216, + -5.729151582811278 + ], + [ + 39.28904751678759, + -5.727901815188423 + ], + [ + 39.28905940945, + -5.7278955010276675 + ], + [ + 39.28914770917402, + -5.726763491701007 + ], + [ + 39.28916029346079, + -5.726741056329244 + ], + [ + 39.28918111127163, + -5.726393395953501 + ], + [ + 39.289192304853835, + -5.726388483957942 + ], + [ + 39.28919223965883, + -5.726258816193987 + ], + [ + 39.28920343288595, + -5.72625320329149 + ], + [ + 39.289214507699526, + -5.726012085692045 + ], + [ + 39.289225700921726, + -5.72600647278932 + ], + [ + 39.289225641714694, + -5.725888720440319 + ], + [ + 39.28923613764901, + -5.725888014238375 + ], + [ + 39.2892367757264, + -5.725765355188089 + ], + [ + 39.28924727165846, + -5.7257646489860505 + ], + [ + 39.289403768567006, + -5.7238812328140325 + ], + [ + 39.289415663964434, + -5.723880525904426 + ], + [ + 39.289503962766254, + -5.7227485164315155 + ], + [ + 39.28951654695881, + -5.722726081053947 + ], + [ + 39.289537364530666, + -5.722378420634993 + ], + [ + 39.289548559090875, + -5.722375611356616 + ], + [ + 39.28961529374991, + -5.721498742899586 + ], + [ + 39.29004098667544, + -5.720599264075941 + ], + [ + 39.290073866967184, + -5.7205838275003424 + ], + [ + 39.29009694163223, + -5.720550172295505 + ], + [ + 39.29011863375526, + -5.720550161329309 + ], + [ + 39.29013051778038, + -5.720527025387051 + ], + [ + 39.29015220990249, + -5.720527014419632 + ], + [ + 39.290164094279014, + -5.720504579383667 + ], + [ + 39.290181588278394, + -5.720505271444961 + ], + [ + 39.290208861408814, + -5.720471614112995 + ], + [ + 39.29026413001999, + -5.720449157133799 + ], + [ + 39.2903207811543, + -5.72039305590999 + ], + [ + 39.29034317266325, + -5.720392343674706 + ], + [ + 39.2903543576374, + -5.720370609896142 + ], + [ + 39.290421516252074, + -5.720336932373324 + ], + [ + 39.29043270086868, + -5.720314497686497 + ], + [ + 39.290488668847225, + -5.720291339425736 + ], + [ + 39.29054462055196, + -5.720235939442509 + ], + [ + 39.29059988877251, + -5.720212781525714 + ], + [ + 39.29065654020414, + -5.720157381178035 + ], + [ + 39.290711808412254, + -5.7201342232510095 + ], + [ + 39.290768459825216, + -5.720078822893069 + ], + [ + 39.290823728020904, + -5.720055664955817 + ], + [ + 39.290880379415185, + -5.720000264587617 + ], + [ + 39.29093564759845, + -5.719977106640133 + ], + [ + 39.29099229897406, + -5.719921706261678 + ], + [ + 39.29104756714489, + -5.719898548303964 + ], + [ + 39.29110421850183, + -5.719843147915251 + ], + [ + 39.291159486660234, + -5.71981998994731 + ], + [ + 39.2912161379985, + -5.719764589548339 + ], + [ + 39.29127140614448, + -5.719741431570171 + ], + [ + 39.291328057464064, + -5.719686031160943 + ], + [ + 39.29138332559762, + -5.719662873172546 + ], + [ + 39.29143997689853, + -5.7196074727530615 + ], + [ + 39.29149524501966, + -5.71958431475444 + ], + [ + 39.2915518963019, + -5.7195289143247 + ], + [ + 39.2916071644106, + -5.719505756315848 + ], + [ + 39.29166381567417, + -5.719450355875855 + ], + [ + 39.29171908377044, + -5.719427197856779 + ], + [ + 39.291775735015335, + -5.719371797406529 + ], + [ + 39.291831003099176, + -5.719348639377229 + ], + [ + 39.2918876543254, + -5.719293238916724 + ], + [ + 39.291942922396814, + -5.719270080877199 + ], + [ + 39.29199957360437, + -5.719214680406439 + ], + [ + 39.29205484166336, + -5.719191522356688 + ], + [ + 39.29211149249672, + -5.719135420968851 + ], + [ + 39.292166760898795, + -5.719112963815699 + ], + [ + 39.29222341171336, + -5.7190568624176175 + ], + [ + 39.292279379847066, + -5.719034404897935 + ], + [ + 39.29228986497856, + -5.719012671441992 + ], + [ + 39.294664106892064, + -5.719033185666966 + ], + [ + 39.29467603158522, + -5.719089953000165 + ], + [ + 39.29465437431092, + -5.719157952082305 + ], + [ + 39.29465371114527, + -5.719229444921172 + ], + [ + 39.29463135734711, + -5.7193037525216885 + ], + [ + 39.29462023566773, + -5.719448845947391 + ], + [ + 39.29460904011777, + -5.719449552600848 + ], + [ + 39.29447793731705, + -5.720327856151281 + ], + [ + 39.29566180023131, + -5.720120479955832 + ], + [ + 39.29563842688224, + -5.719571681995611 + ], + [ + 39.29681400855618, + -5.719592803370352 + ], + [ + 39.29682521096831, + -5.7196054138976775 + ], + [ + 39.29732832640864, + -5.719603751672776 + ], + [ + 39.29734023364275, + -5.719626174517702 + ], + [ + 39.297709699002866, + -5.7196266839006 + ], + [ + 39.297720125955514, + -5.719492805322403 + ], + [ + 39.29773201073462, + -5.719471771958087 + ], + [ + 39.297743206640426, + -5.719471766150352 + ], + [ + 39.29900146876119, + -5.71970661665732 + ], + [ + 39.29903085693057, + -5.719704498626807 + ], + [ + 39.2990308274491, + -5.719647725210644 + ], + [ + 39.2991875701586, + -5.719647643525276 + ], + [ + 39.29918827572937, + -5.719658857662318 + ], + [ + 39.2993443186954, + -5.719658776298749 + ], + [ + 39.29951086985279, + -5.719681819324766 + ], + [ + 39.299517826100136, + -5.719602613256507 + ], + [ + 39.29961229156103, + -5.719602563951844 + ], + [ + 39.299615796116605, + -5.71961377662683 + ], + [ + 39.29974665411206, + -5.719624922805369 + ], + [ + 39.29974805943782, + -5.719636136575718 + ], + [ + 39.299847423111444, + -5.719636084673931 + ], + [ + 39.299851627415954, + -5.719647296981645 + ], + [ + 39.29994819795517, + -5.719658461025615 + ], + [ + 39.299925719258574, + -5.719491657023776 + ], + [ + 39.299914522989596, + -5.719490961967785 + ], + [ + 39.29991514680767, + -5.719345173087227 + ], + [ + 39.3000830849578, + -5.7193443844051135 + ], + [ + 39.30009354641723, + -5.719277792817777 + ], + [ + 39.300127096127426, + -5.719204880999829 + ], + [ + 39.300138941725315, + -5.719108850614782 + ], + [ + 39.3015986183503, + -5.719130514515799 + ], + [ + 39.301606321401344, + -5.7191417249709 + ], + [ + 39.30197509796063, + -5.71916396008395 + ], + [ + 39.30197650332791, + -5.719175173846801 + ], + [ + 39.30209755894922, + -5.719175110147159 + ], + [ + 39.302103862523296, + -5.719186321331581 + ], + [ + 39.30408422522446, + -5.719353493113251 + ], + [ + 39.30409122895187, + -5.7193654048089595 + ], + [ + 39.30421508356364, + -5.719365339177715 + ], + [ + 39.30421998732256, + -5.719375850172232 + ], + [ + 39.30433754424296, + -5.719375787852325 + ], + [ + 39.304338249542674, + -5.719386301071832 + ], + [ + 39.304460005295915, + -5.719386937406598 + ], + [ + 39.30446071059788, + -5.71939745062583 + ], + [ + 39.304583865839405, + -5.719398086191834 + ], + [ + 39.30458457114364, + -5.719408599410791 + ], + [ + 39.3047063268991, + -5.719409235693033 + ], + [ + 39.304707731949186, + -5.719419748540238 + ], + [ + 39.30483018744935, + -5.719420384424596 + ], + [ + 39.30483509159077, + -5.719431596319216 + ], + [ + 39.304964544156945, + -5.719431527552395 + ], + [ + 39.304965249839356, + -5.719442741676491 + ], + [ + 39.30570350908857, + -5.719498421435598 + ], + [ + 39.305737803224545, + -5.719511019479368 + ], + [ + 39.30571619973147, + -5.7196778466475715 + ], + [ + 39.30605068324513, + -5.719688882882785 + ], + [ + 39.30610737404596, + -5.7197105807395285 + ], + [ + 39.30641456569894, + -5.719718126786076 + ], + [ + 39.30643057887232, + -5.719566021615056 + ], + [ + 39.30644737234725, + -5.7195653117428185 + ], + [ + 39.30658243481406, + -5.71958766860887 + ], + [ + 39.30755791938568, + -5.71966564819061 + ], + [ + 39.30756702204364, + -5.719676857809007 + ], + [ + 39.30805965961166, + -5.719710237089115 + ], + [ + 39.308066663423446, + -5.719722148731534 + ], + [ + 39.308189818321985, + -5.719722082614072 + ], + [ + 39.308312279104385, + -5.719732530432556 + ], + [ + 39.308312284733425, + -5.719743044019035 + ], + [ + 39.30843544000899, + -5.719743678754385 + ], + [ + 39.30843544564029, + -5.719754192340735 + ], + [ + 39.30855860091696, + -5.7197548270495195 + ], + [ + 39.30855930629429, + -5.7197653402595465 + ], + [ + 39.308681761828296, + -5.719765975317966 + ], + [ + 39.30868246720789, + -5.719776488527715 + ], + [ + 39.308804922742986, + -5.719777123559718 + ], + [ + 39.308805628124844, + -5.71978763676919 + ], + [ + 39.308927383917265, + -5.719788272151425 + ], + [ + 39.308928789045154, + -5.719798784983975 + ], + [ + 39.30905124458244, + -5.71979941996315 + ], + [ + 39.309056148807734, + -5.719810631816969 + ], + [ + 39.309923167539615, + -5.719877451244321 + ], + [ + 39.31063422293008, + -5.720091544007215 + ], + [ + 39.31065381500645, + -5.720090131590763 + ], + [ + 39.310662899577814, + -5.720067697687457 + ], + [ + 39.31067404178256, + -5.7199681630314965 + ], + [ + 39.31074121076393, + -5.719956211268805 + ], + [ + 39.31163696137919, + -5.720100813064275 + ], + [ + 39.311699250733554, + -5.720123208220514 + ], + [ + 39.31180561219929, + -5.720123851348939 + ], + [ + 39.311906387102475, + -5.720145524665541 + ], + [ + 39.311906393177225, + -5.720156739153791 + ], + [ + 39.31197216911476, + -5.720156703401592 + ], + [ + 39.31197427442288, + -5.720167916748612 + ], + [ + 39.31210233973633, + -5.720190276099752 + ], + [ + 39.31212192953011, + -5.720184658200196 + ], + [ + 39.3121295815013, + -5.720101246259322 + ], + [ + 39.31215196266817, + -5.720081608726603 + ], + [ + 39.31337307522748, + -5.720190284531242 + ], + [ + 39.3133506857093, + -5.720194502190286 + ], + [ + 39.31358511825828, + -5.720228017609581 + ], + [ + 39.313602918515315, + -5.740028584511519 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/33cae6-labels/33cae6.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/33cae6-labels/33cae6.geojson", + "type": "application/geo+json" + } + }, + "bbox": [ + 39.287711527683925, + -5.743060299502631, + 39.31360456846548, + -5.719012276837555 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json b/tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json index 0646a5ec6..f1928cb44 100644 --- a/tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json +++ b/tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json @@ -1,1139 +1,1140 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "33cae6", - "properties": { - "area": "znz", - "datetime": "2016-08-28T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.313602918515315, - -5.740028584511519 - ], - [ - 39.31360456846548, - -5.743046680921876 - ], - [ - 39.28771158148321, - -5.743060299502631 - ], - [ - 39.287833992413255, - -5.7415673066017945 - ], - [ - 39.28784518594579, - -5.741561693713168 - ], - [ - 39.287867395743724, - -5.741197211035715 - ], - [ - 39.28787859137919, - -5.741195803586043 - ], - [ - 39.28788965898581, - -5.740939266150898 - ], - [ - 39.28789736103782, - -5.740948374060875 - ], - [ - 39.28794532863633, - -5.740317533846062 - ], - [ - 39.287956518978035, - -5.740305612797183 - ], - [ - 39.28795645988263, - -5.740187860493621 - ], - [ - 39.28796695643181, - -5.7401878552086325 - ], - [ - 39.287978729048056, - -5.739941831012015 - ], - [ - 39.28799062442844, - -5.739940423208991 - ], - [ - 39.28801212626218, - -5.739559820014053 - ], - [ - 39.288023319754615, - -5.739554207123601 - ], - [ - 39.28805666134313, - -5.7390614528792385 - ], - [ - 39.28806785412197, - -5.739054438175143 - ], - [ - 39.288068492322395, - -5.7389317791690555 - ], - [ - 39.28807898849667, - -5.7389310729765715 - ], - [ - 39.288078926927646, - -5.738808414322463 - ], - [ - 39.28809012040507, - -5.738802801431323 - ], - [ - 39.288123461924656, - -5.73831004717524 - ], - [ - 39.28813465468841, - -5.738303032470432 - ], - [ - 39.28813459311428, - -5.73818037381424 - ], - [ - 39.288145786579165, - -5.738174760922529 - ], - [ - 39.28817912804129, - -5.737682006656658 - ], - [ - 39.28819032114434, - -5.737675692857872 - ], - [ - 39.28819025921321, - -5.737552333292979 - ], - [ - 39.28820145548069, - -5.737552327653656 - ], - [ - 39.28825705989423, - -5.736801628458127 - ], - [ - 39.28826825297967, - -5.736795314658528 - ], - [ - 39.28826819420862, - -5.736678263250428 - ], - [ - 39.288279389755104, - -5.736676855797169 - ], - [ - 39.28829046247506, - -5.736430831926878 - ], - [ - 39.288301655905016, - -5.736425219033569 - ], - [ - 39.28831272544848, - -5.736172887001599 - ], - [ - 39.28832392168893, - -5.736172881361234 - ], - [ - 39.28832386009737, - -5.736050222697981 - ], - [ - 39.28833505316777, - -5.736043908897686 - ], - [ - 39.28833569380363, - -5.735926156228032 - ], - [ - 39.28833849849387, - -5.735937369324319 - ], - [ - 39.28834688968728, - -5.735925449680809 - ], - [ - 39.288368397250515, - -5.735556761851868 - ], - [ - 39.28837959277493, - -5.7355553543976985 - ], - [ - 39.28843519161201, - -5.734794141554009 - ], - [ - 39.288446387121425, - -5.734792734099254 - ], - [ - 39.28844632551862, - -5.734670075431432 - ], - [ - 39.28845752137762, - -5.73466936888325 - ], - [ - 39.288468594025474, - -5.734423344998106 - ], - [ - 39.28847978741536, - -5.734417732102973 - ], - [ - 39.288490857237925, - -5.734166100962491 - ], - [ - 39.28850205273499, - -5.734164693507241 - ], - [ - 39.28855765700006, - -5.733414695136926 - ], - [ - 39.28856885248234, - -5.733413287681076 - ], - [ - 39.28856879122039, - -5.733291329915387 - ], - [ - 39.288579986700235, - -5.733289922459435 - ], - [ - 39.288579925085166, - -5.7331672637866395 - ], - [ - 39.288586228570765, - -5.733178475119921 - ], - [ - 39.288591061058995, - -5.733047403097616 - ], - [ - 39.28860225301303, - -5.733038986574412 - ], - [ - 39.28861332243345, - -5.732786654516813 - ], - [ - 39.28862451790337, - -5.732785247060469 - ], - [ - 39.28862445663636, - -5.732663289292712 - ], - [ - 39.288635652103856, - -5.732661881836264 - ], - [ - 39.28863629024428, - -5.7325392228087 - ], - [ - 39.28865791802952, - -5.732410245039324 - ], - [ - 39.28866898742718, - -5.732157912976972 - ], - [ - 39.28868018358902, - -5.7321579073335736 - ], - [ - 39.28868012196481, - -5.732035248657027 - ], - [ - 39.28869131495492, - -5.732028934853017 - ], - [ - 39.2886912557947, - -5.731911182523131 - ], - [ - 39.28870315135938, - -5.731910475620081 - ], - [ - 39.288802586506456, - -5.730655802114353 - ], - [ - 39.28881378193448, - -5.730654394656317 - ], - [ - 39.28881372029802, - -5.730531735974798 - ], - [ - 39.28882002375656, - -5.73054294730776 - ], - [ - 39.28883599174512, - -5.730291313668497 - ], - [ - 39.288847182939215, - -5.730281495329069 - ], - [ - 39.288847117073004, - -5.730150425765198 - ], - [ - 39.28885831319556, - -5.73015042012029 - ], - [ - 39.28885825155506, - -5.730027761437102 - ], - [ - 39.28886944450505, - -5.730021447631236 - ], - [ - 39.28886938709037, - -5.7299071998288 - ], - [ - 39.28888058109469, - -5.729902988743108 - ], - [ - 39.288880523326746, - -5.729788040033545 - ], - [ - 39.28890284721865, - -5.729652052830054 - ], - [ - 39.288902782051544, - -5.7295223850775026 - ], - [ - 39.28891397816181, - -5.72952237943212 - ], - [ - 39.28894247655001, - -5.729141071679603 - ], - [ - 39.288947380129216, - -5.729151582811278 - ], - [ - 39.28904751678759, - -5.727901815188423 - ], - [ - 39.28905940945, - -5.7278955010276675 - ], - [ - 39.28914770917402, - -5.726763491701007 - ], - [ - 39.28916029346079, - -5.726741056329244 - ], - [ - 39.28918111127163, - -5.726393395953501 - ], - [ - 39.289192304853835, - -5.726388483957942 - ], - [ - 39.28919223965883, - -5.726258816193987 - ], - [ - 39.28920343288595, - -5.72625320329149 - ], - [ - 39.289214507699526, - -5.726012085692045 - ], - [ - 39.289225700921726, - -5.72600647278932 - ], - [ - 39.289225641714694, - -5.725888720440319 - ], - [ - 39.28923613764901, - -5.725888014238375 - ], - [ - 39.2892367757264, - -5.725765355188089 - ], - [ - 39.28924727165846, - -5.7257646489860505 - ], - [ - 39.289403768567006, - -5.7238812328140325 - ], - [ - 39.289415663964434, - -5.723880525904426 - ], - [ - 39.289503962766254, - -5.7227485164315155 - ], - [ - 39.28951654695881, - -5.722726081053947 - ], - [ - 39.289537364530666, - -5.722378420634993 - ], - [ - 39.289548559090875, - -5.722375611356616 - ], - [ - 39.28961529374991, - -5.721498742899586 - ], - [ - 39.29004098667544, - -5.720599264075941 - ], - [ - 39.290073866967184, - -5.7205838275003424 - ], - [ - 39.29009694163223, - -5.720550172295505 - ], - [ - 39.29011863375526, - -5.720550161329309 - ], - [ - 39.29013051778038, - -5.720527025387051 - ], - [ - 39.29015220990249, - -5.720527014419632 - ], - [ - 39.290164094279014, - -5.720504579383667 - ], - [ - 39.290181588278394, - -5.720505271444961 - ], - [ - 39.290208861408814, - -5.720471614112995 - ], - [ - 39.29026413001999, - -5.720449157133799 - ], - [ - 39.2903207811543, - -5.72039305590999 - ], - [ - 39.29034317266325, - -5.720392343674706 - ], - [ - 39.2903543576374, - -5.720370609896142 - ], - [ - 39.290421516252074, - -5.720336932373324 - ], - [ - 39.29043270086868, - -5.720314497686497 - ], - [ - 39.290488668847225, - -5.720291339425736 - ], - [ - 39.29054462055196, - -5.720235939442509 - ], - [ - 39.29059988877251, - -5.720212781525714 - ], - [ - 39.29065654020414, - -5.720157381178035 - ], - [ - 39.290711808412254, - -5.7201342232510095 - ], - [ - 39.290768459825216, - -5.720078822893069 - ], - [ - 39.290823728020904, - -5.720055664955817 - ], - [ - 39.290880379415185, - -5.720000264587617 - ], - [ - 39.29093564759845, - -5.719977106640133 - ], - [ - 39.29099229897406, - -5.719921706261678 - ], - [ - 39.29104756714489, - -5.719898548303964 - ], - [ - 39.29110421850183, - -5.719843147915251 - ], - [ - 39.291159486660234, - -5.71981998994731 - ], - [ - 39.2912161379985, - -5.719764589548339 - ], - [ - 39.29127140614448, - -5.719741431570171 - ], - [ - 39.291328057464064, - -5.719686031160943 - ], - [ - 39.29138332559762, - -5.719662873172546 - ], - [ - 39.29143997689853, - -5.7196074727530615 - ], - [ - 39.29149524501966, - -5.71958431475444 - ], - [ - 39.2915518963019, - -5.7195289143247 - ], - [ - 39.2916071644106, - -5.719505756315848 - ], - [ - 39.29166381567417, - -5.719450355875855 - ], - [ - 39.29171908377044, - -5.719427197856779 - ], - [ - 39.291775735015335, - -5.719371797406529 - ], - [ - 39.291831003099176, - -5.719348639377229 - ], - [ - 39.2918876543254, - -5.719293238916724 - ], - [ - 39.291942922396814, - -5.719270080877199 - ], - [ - 39.29199957360437, - -5.719214680406439 - ], - [ - 39.29205484166336, - -5.719191522356688 - ], - [ - 39.29211149249672, - -5.719135420968851 - ], - [ - 39.292166760898795, - -5.719112963815699 - ], - [ - 39.29222341171336, - -5.7190568624176175 - ], - [ - 39.292279379847066, - -5.719034404897935 - ], - [ - 39.29228986497856, - -5.719012671441992 - ], - [ - 39.294664106892064, - -5.719033185666966 - ], - [ - 39.29467603158522, - -5.719089953000165 - ], - [ - 39.29465437431092, - -5.719157952082305 - ], - [ - 39.29465371114527, - -5.719229444921172 - ], - [ - 39.29463135734711, - -5.7193037525216885 - ], - [ - 39.29462023566773, - -5.719448845947391 - ], - [ - 39.29460904011777, - -5.719449552600848 - ], - [ - 39.29447793731705, - -5.720327856151281 - ], - [ - 39.29566180023131, - -5.720120479955832 - ], - [ - 39.29563842688224, - -5.719571681995611 - ], - [ - 39.29681400855618, - -5.719592803370352 - ], - [ - 39.29682521096831, - -5.7196054138976775 - ], - [ - 39.29732832640864, - -5.719603751672776 - ], - [ - 39.29734023364275, - -5.719626174517702 - ], - [ - 39.297709699002866, - -5.7196266839006 - ], - [ - 39.297720125955514, - -5.719492805322403 - ], - [ - 39.29773201073462, - -5.719471771958087 - ], - [ - 39.297743206640426, - -5.719471766150352 - ], - [ - 39.29900146876119, - -5.71970661665732 - ], - [ - 39.29903085693057, - -5.719704498626807 - ], - [ - 39.2990308274491, - -5.719647725210644 - ], - [ - 39.2991875701586, - -5.719647643525276 - ], - [ - 39.29918827572937, - -5.719658857662318 - ], - [ - 39.2993443186954, - -5.719658776298749 - ], - [ - 39.29951086985279, - -5.719681819324766 - ], - [ - 39.299517826100136, - -5.719602613256507 - ], - [ - 39.29961229156103, - -5.719602563951844 - ], - [ - 39.299615796116605, - -5.71961377662683 - ], - [ - 39.29974665411206, - -5.719624922805369 - ], - [ - 39.29974805943782, - -5.719636136575718 - ], - [ - 39.299847423111444, - -5.719636084673931 - ], - [ - 39.299851627415954, - -5.719647296981645 - ], - [ - 39.29994819795517, - -5.719658461025615 - ], - [ - 39.299925719258574, - -5.719491657023776 - ], - [ - 39.299914522989596, - -5.719490961967785 - ], - [ - 39.29991514680767, - -5.719345173087227 - ], - [ - 39.3000830849578, - -5.7193443844051135 - ], - [ - 39.30009354641723, - -5.719277792817777 - ], - [ - 39.300127096127426, - -5.719204880999829 - ], - [ - 39.300138941725315, - -5.719108850614782 - ], - [ - 39.3015986183503, - -5.719130514515799 - ], - [ - 39.301606321401344, - -5.7191417249709 - ], - [ - 39.30197509796063, - -5.71916396008395 - ], - [ - 39.30197650332791, - -5.719175173846801 - ], - [ - 39.30209755894922, - -5.719175110147159 - ], - [ - 39.302103862523296, - -5.719186321331581 - ], - [ - 39.30408422522446, - -5.719353493113251 - ], - [ - 39.30409122895187, - -5.7193654048089595 - ], - [ - 39.30421508356364, - -5.719365339177715 - ], - [ - 39.30421998732256, - -5.719375850172232 - ], - [ - 39.30433754424296, - -5.719375787852325 - ], - [ - 39.304338249542674, - -5.719386301071832 - ], - [ - 39.304460005295915, - -5.719386937406598 - ], - [ - 39.30446071059788, - -5.71939745062583 - ], - [ - 39.304583865839405, - -5.719398086191834 - ], - [ - 39.30458457114364, - -5.719408599410791 - ], - [ - 39.3047063268991, - -5.719409235693033 - ], - [ - 39.304707731949186, - -5.719419748540238 - ], - [ - 39.30483018744935, - -5.719420384424596 - ], - [ - 39.30483509159077, - -5.719431596319216 - ], - [ - 39.304964544156945, - -5.719431527552395 - ], - [ - 39.304965249839356, - -5.719442741676491 - ], - [ - 39.30570350908857, - -5.719498421435598 - ], - [ - 39.305737803224545, - -5.719511019479368 - ], - [ - 39.30571619973147, - -5.7196778466475715 - ], - [ - 39.30605068324513, - -5.719688882882785 - ], - [ - 39.30610737404596, - -5.7197105807395285 - ], - [ - 39.30641456569894, - -5.719718126786076 - ], - [ - 39.30643057887232, - -5.719566021615056 - ], - [ - 39.30644737234725, - -5.7195653117428185 - ], - [ - 39.30658243481406, - -5.71958766860887 - ], - [ - 39.30755791938568, - -5.71966564819061 - ], - [ - 39.30756702204364, - -5.719676857809007 - ], - [ - 39.30805965961166, - -5.719710237089115 - ], - [ - 39.308066663423446, - -5.719722148731534 - ], - [ - 39.308189818321985, - -5.719722082614072 - ], - [ - 39.308312279104385, - -5.719732530432556 - ], - [ - 39.308312284733425, - -5.719743044019035 - ], - [ - 39.30843544000899, - -5.719743678754385 - ], - [ - 39.30843544564029, - -5.719754192340735 - ], - [ - 39.30855860091696, - -5.7197548270495195 - ], - [ - 39.30855930629429, - -5.7197653402595465 - ], - [ - 39.308681761828296, - -5.719765975317966 - ], - [ - 39.30868246720789, - -5.719776488527715 - ], - [ - 39.308804922742986, - -5.719777123559718 - ], - [ - 39.308805628124844, - -5.71978763676919 - ], - [ - 39.308927383917265, - -5.719788272151425 - ], - [ - 39.308928789045154, - -5.719798784983975 - ], - [ - 39.30905124458244, - -5.71979941996315 - ], - [ - 39.309056148807734, - -5.719810631816969 - ], - [ - 39.309923167539615, - -5.719877451244321 - ], - [ - 39.31063422293008, - -5.720091544007215 - ], - [ - 39.31065381500645, - -5.720090131590763 - ], - [ - 39.310662899577814, - -5.720067697687457 - ], - [ - 39.31067404178256, - -5.7199681630314965 - ], - [ - 39.31074121076393, - -5.719956211268805 - ], - [ - 39.31163696137919, - -5.720100813064275 - ], - [ - 39.311699250733554, - -5.720123208220514 - ], - [ - 39.31180561219929, - -5.720123851348939 - ], - [ - 39.311906387102475, - -5.720145524665541 - ], - [ - 39.311906393177225, - -5.720156739153791 - ], - [ - 39.31197216911476, - -5.720156703401592 - ], - [ - 39.31197427442288, - -5.720167916748612 - ], - [ - 39.31210233973633, - -5.720190276099752 - ], - [ - 39.31212192953011, - -5.720184658200196 - ], - [ - 39.3121295815013, - -5.720101246259322 - ], - [ - 39.31215196266817, - -5.720081608726603 - ], - [ - 39.31337307522748, - -5.720190284531242 - ], - [ - 39.3133506857093, - -5.720194502190286 - ], - [ - 39.31358511825828, - -5.720228017609581 - ], - [ - 39.313602918515315, - -5.740028584511519 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.287711527683925, - -5.743060299502631, - 39.31360456846548, - -5.719012276837555 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "33cae6", + "properties": { + "area": "znz", + "datetime": "2016-08-28T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.313602918515315, + -5.740028584511519 + ], + [ + 39.31360456846548, + -5.743046680921876 + ], + [ + 39.28771158148321, + -5.743060299502631 + ], + [ + 39.287833992413255, + -5.7415673066017945 + ], + [ + 39.28784518594579, + -5.741561693713168 + ], + [ + 39.287867395743724, + -5.741197211035715 + ], + [ + 39.28787859137919, + -5.741195803586043 + ], + [ + 39.28788965898581, + -5.740939266150898 + ], + [ + 39.28789736103782, + -5.740948374060875 + ], + [ + 39.28794532863633, + -5.740317533846062 + ], + [ + 39.287956518978035, + -5.740305612797183 + ], + [ + 39.28795645988263, + -5.740187860493621 + ], + [ + 39.28796695643181, + -5.7401878552086325 + ], + [ + 39.287978729048056, + -5.739941831012015 + ], + [ + 39.28799062442844, + -5.739940423208991 + ], + [ + 39.28801212626218, + -5.739559820014053 + ], + [ + 39.288023319754615, + -5.739554207123601 + ], + [ + 39.28805666134313, + -5.7390614528792385 + ], + [ + 39.28806785412197, + -5.739054438175143 + ], + [ + 39.288068492322395, + -5.7389317791690555 + ], + [ + 39.28807898849667, + -5.7389310729765715 + ], + [ + 39.288078926927646, + -5.738808414322463 + ], + [ + 39.28809012040507, + -5.738802801431323 + ], + [ + 39.288123461924656, + -5.73831004717524 + ], + [ + 39.28813465468841, + -5.738303032470432 + ], + [ + 39.28813459311428, + -5.73818037381424 + ], + [ + 39.288145786579165, + -5.738174760922529 + ], + [ + 39.28817912804129, + -5.737682006656658 + ], + [ + 39.28819032114434, + -5.737675692857872 + ], + [ + 39.28819025921321, + -5.737552333292979 + ], + [ + 39.28820145548069, + -5.737552327653656 + ], + [ + 39.28825705989423, + -5.736801628458127 + ], + [ + 39.28826825297967, + -5.736795314658528 + ], + [ + 39.28826819420862, + -5.736678263250428 + ], + [ + 39.288279389755104, + -5.736676855797169 + ], + [ + 39.28829046247506, + -5.736430831926878 + ], + [ + 39.288301655905016, + -5.736425219033569 + ], + [ + 39.28831272544848, + -5.736172887001599 + ], + [ + 39.28832392168893, + -5.736172881361234 + ], + [ + 39.28832386009737, + -5.736050222697981 + ], + [ + 39.28833505316777, + -5.736043908897686 + ], + [ + 39.28833569380363, + -5.735926156228032 + ], + [ + 39.28833849849387, + -5.735937369324319 + ], + [ + 39.28834688968728, + -5.735925449680809 + ], + [ + 39.288368397250515, + -5.735556761851868 + ], + [ + 39.28837959277493, + -5.7355553543976985 + ], + [ + 39.28843519161201, + -5.734794141554009 + ], + [ + 39.288446387121425, + -5.734792734099254 + ], + [ + 39.28844632551862, + -5.734670075431432 + ], + [ + 39.28845752137762, + -5.73466936888325 + ], + [ + 39.288468594025474, + -5.734423344998106 + ], + [ + 39.28847978741536, + -5.734417732102973 + ], + [ + 39.288490857237925, + -5.734166100962491 + ], + [ + 39.28850205273499, + -5.734164693507241 + ], + [ + 39.28855765700006, + -5.733414695136926 + ], + [ + 39.28856885248234, + -5.733413287681076 + ], + [ + 39.28856879122039, + -5.733291329915387 + ], + [ + 39.288579986700235, + -5.733289922459435 + ], + [ + 39.288579925085166, + -5.7331672637866395 + ], + [ + 39.288586228570765, + -5.733178475119921 + ], + [ + 39.288591061058995, + -5.733047403097616 + ], + [ + 39.28860225301303, + -5.733038986574412 + ], + [ + 39.28861332243345, + -5.732786654516813 + ], + [ + 39.28862451790337, + -5.732785247060469 + ], + [ + 39.28862445663636, + -5.732663289292712 + ], + [ + 39.288635652103856, + -5.732661881836264 + ], + [ + 39.28863629024428, + -5.7325392228087 + ], + [ + 39.28865791802952, + -5.732410245039324 + ], + [ + 39.28866898742718, + -5.732157912976972 + ], + [ + 39.28868018358902, + -5.7321579073335736 + ], + [ + 39.28868012196481, + -5.732035248657027 + ], + [ + 39.28869131495492, + -5.732028934853017 + ], + [ + 39.2886912557947, + -5.731911182523131 + ], + [ + 39.28870315135938, + -5.731910475620081 + ], + [ + 39.288802586506456, + -5.730655802114353 + ], + [ + 39.28881378193448, + -5.730654394656317 + ], + [ + 39.28881372029802, + -5.730531735974798 + ], + [ + 39.28882002375656, + -5.73054294730776 + ], + [ + 39.28883599174512, + -5.730291313668497 + ], + [ + 39.288847182939215, + -5.730281495329069 + ], + [ + 39.288847117073004, + -5.730150425765198 + ], + [ + 39.28885831319556, + -5.73015042012029 + ], + [ + 39.28885825155506, + -5.730027761437102 + ], + [ + 39.28886944450505, + -5.730021447631236 + ], + [ + 39.28886938709037, + -5.7299071998288 + ], + [ + 39.28888058109469, + -5.729902988743108 + ], + [ + 39.288880523326746, + -5.729788040033545 + ], + [ + 39.28890284721865, + -5.729652052830054 + ], + [ + 39.288902782051544, + -5.7295223850775026 + ], + [ + 39.28891397816181, + -5.72952237943212 + ], + [ + 39.28894247655001, + -5.729141071679603 + ], + [ + 39.288947380129216, + -5.729151582811278 + ], + [ + 39.28904751678759, + -5.727901815188423 + ], + [ + 39.28905940945, + -5.7278955010276675 + ], + [ + 39.28914770917402, + -5.726763491701007 + ], + [ + 39.28916029346079, + -5.726741056329244 + ], + [ + 39.28918111127163, + -5.726393395953501 + ], + [ + 39.289192304853835, + -5.726388483957942 + ], + [ + 39.28919223965883, + -5.726258816193987 + ], + [ + 39.28920343288595, + -5.72625320329149 + ], + [ + 39.289214507699526, + -5.726012085692045 + ], + [ + 39.289225700921726, + -5.72600647278932 + ], + [ + 39.289225641714694, + -5.725888720440319 + ], + [ + 39.28923613764901, + -5.725888014238375 + ], + [ + 39.2892367757264, + -5.725765355188089 + ], + [ + 39.28924727165846, + -5.7257646489860505 + ], + [ + 39.289403768567006, + -5.7238812328140325 + ], + [ + 39.289415663964434, + -5.723880525904426 + ], + [ + 39.289503962766254, + -5.7227485164315155 + ], + [ + 39.28951654695881, + -5.722726081053947 + ], + [ + 39.289537364530666, + -5.722378420634993 + ], + [ + 39.289548559090875, + -5.722375611356616 + ], + [ + 39.28961529374991, + -5.721498742899586 + ], + [ + 39.29004098667544, + -5.720599264075941 + ], + [ + 39.290073866967184, + -5.7205838275003424 + ], + [ + 39.29009694163223, + -5.720550172295505 + ], + [ + 39.29011863375526, + -5.720550161329309 + ], + [ + 39.29013051778038, + -5.720527025387051 + ], + [ + 39.29015220990249, + -5.720527014419632 + ], + [ + 39.290164094279014, + -5.720504579383667 + ], + [ + 39.290181588278394, + -5.720505271444961 + ], + [ + 39.290208861408814, + -5.720471614112995 + ], + [ + 39.29026413001999, + -5.720449157133799 + ], + [ + 39.2903207811543, + -5.72039305590999 + ], + [ + 39.29034317266325, + -5.720392343674706 + ], + [ + 39.2903543576374, + -5.720370609896142 + ], + [ + 39.290421516252074, + -5.720336932373324 + ], + [ + 39.29043270086868, + -5.720314497686497 + ], + [ + 39.290488668847225, + -5.720291339425736 + ], + [ + 39.29054462055196, + -5.720235939442509 + ], + [ + 39.29059988877251, + -5.720212781525714 + ], + [ + 39.29065654020414, + -5.720157381178035 + ], + [ + 39.290711808412254, + -5.7201342232510095 + ], + [ + 39.290768459825216, + -5.720078822893069 + ], + [ + 39.290823728020904, + -5.720055664955817 + ], + [ + 39.290880379415185, + -5.720000264587617 + ], + [ + 39.29093564759845, + -5.719977106640133 + ], + [ + 39.29099229897406, + -5.719921706261678 + ], + [ + 39.29104756714489, + -5.719898548303964 + ], + [ + 39.29110421850183, + -5.719843147915251 + ], + [ + 39.291159486660234, + -5.71981998994731 + ], + [ + 39.2912161379985, + -5.719764589548339 + ], + [ + 39.29127140614448, + -5.719741431570171 + ], + [ + 39.291328057464064, + -5.719686031160943 + ], + [ + 39.29138332559762, + -5.719662873172546 + ], + [ + 39.29143997689853, + -5.7196074727530615 + ], + [ + 39.29149524501966, + -5.71958431475444 + ], + [ + 39.2915518963019, + -5.7195289143247 + ], + [ + 39.2916071644106, + -5.719505756315848 + ], + [ + 39.29166381567417, + -5.719450355875855 + ], + [ + 39.29171908377044, + -5.719427197856779 + ], + [ + 39.291775735015335, + -5.719371797406529 + ], + [ + 39.291831003099176, + -5.719348639377229 + ], + [ + 39.2918876543254, + -5.719293238916724 + ], + [ + 39.291942922396814, + -5.719270080877199 + ], + [ + 39.29199957360437, + -5.719214680406439 + ], + [ + 39.29205484166336, + -5.719191522356688 + ], + [ + 39.29211149249672, + -5.719135420968851 + ], + [ + 39.292166760898795, + -5.719112963815699 + ], + [ + 39.29222341171336, + -5.7190568624176175 + ], + [ + 39.292279379847066, + -5.719034404897935 + ], + [ + 39.29228986497856, + -5.719012671441992 + ], + [ + 39.294664106892064, + -5.719033185666966 + ], + [ + 39.29467603158522, + -5.719089953000165 + ], + [ + 39.29465437431092, + -5.719157952082305 + ], + [ + 39.29465371114527, + -5.719229444921172 + ], + [ + 39.29463135734711, + -5.7193037525216885 + ], + [ + 39.29462023566773, + -5.719448845947391 + ], + [ + 39.29460904011777, + -5.719449552600848 + ], + [ + 39.29447793731705, + -5.720327856151281 + ], + [ + 39.29566180023131, + -5.720120479955832 + ], + [ + 39.29563842688224, + -5.719571681995611 + ], + [ + 39.29681400855618, + -5.719592803370352 + ], + [ + 39.29682521096831, + -5.7196054138976775 + ], + [ + 39.29732832640864, + -5.719603751672776 + ], + [ + 39.29734023364275, + -5.719626174517702 + ], + [ + 39.297709699002866, + -5.7196266839006 + ], + [ + 39.297720125955514, + -5.719492805322403 + ], + [ + 39.29773201073462, + -5.719471771958087 + ], + [ + 39.297743206640426, + -5.719471766150352 + ], + [ + 39.29900146876119, + -5.71970661665732 + ], + [ + 39.29903085693057, + -5.719704498626807 + ], + [ + 39.2990308274491, + -5.719647725210644 + ], + [ + 39.2991875701586, + -5.719647643525276 + ], + [ + 39.29918827572937, + -5.719658857662318 + ], + [ + 39.2993443186954, + -5.719658776298749 + ], + [ + 39.29951086985279, + -5.719681819324766 + ], + [ + 39.299517826100136, + -5.719602613256507 + ], + [ + 39.29961229156103, + -5.719602563951844 + ], + [ + 39.299615796116605, + -5.71961377662683 + ], + [ + 39.29974665411206, + -5.719624922805369 + ], + [ + 39.29974805943782, + -5.719636136575718 + ], + [ + 39.299847423111444, + -5.719636084673931 + ], + [ + 39.299851627415954, + -5.719647296981645 + ], + [ + 39.29994819795517, + -5.719658461025615 + ], + [ + 39.299925719258574, + -5.719491657023776 + ], + [ + 39.299914522989596, + -5.719490961967785 + ], + [ + 39.29991514680767, + -5.719345173087227 + ], + [ + 39.3000830849578, + -5.7193443844051135 + ], + [ + 39.30009354641723, + -5.719277792817777 + ], + [ + 39.300127096127426, + -5.719204880999829 + ], + [ + 39.300138941725315, + -5.719108850614782 + ], + [ + 39.3015986183503, + -5.719130514515799 + ], + [ + 39.301606321401344, + -5.7191417249709 + ], + [ + 39.30197509796063, + -5.71916396008395 + ], + [ + 39.30197650332791, + -5.719175173846801 + ], + [ + 39.30209755894922, + -5.719175110147159 + ], + [ + 39.302103862523296, + -5.719186321331581 + ], + [ + 39.30408422522446, + -5.719353493113251 + ], + [ + 39.30409122895187, + -5.7193654048089595 + ], + [ + 39.30421508356364, + -5.719365339177715 + ], + [ + 39.30421998732256, + -5.719375850172232 + ], + [ + 39.30433754424296, + -5.719375787852325 + ], + [ + 39.304338249542674, + -5.719386301071832 + ], + [ + 39.304460005295915, + -5.719386937406598 + ], + [ + 39.30446071059788, + -5.71939745062583 + ], + [ + 39.304583865839405, + -5.719398086191834 + ], + [ + 39.30458457114364, + -5.719408599410791 + ], + [ + 39.3047063268991, + -5.719409235693033 + ], + [ + 39.304707731949186, + -5.719419748540238 + ], + [ + 39.30483018744935, + -5.719420384424596 + ], + [ + 39.30483509159077, + -5.719431596319216 + ], + [ + 39.304964544156945, + -5.719431527552395 + ], + [ + 39.304965249839356, + -5.719442741676491 + ], + [ + 39.30570350908857, + -5.719498421435598 + ], + [ + 39.305737803224545, + -5.719511019479368 + ], + [ + 39.30571619973147, + -5.7196778466475715 + ], + [ + 39.30605068324513, + -5.719688882882785 + ], + [ + 39.30610737404596, + -5.7197105807395285 + ], + [ + 39.30641456569894, + -5.719718126786076 + ], + [ + 39.30643057887232, + -5.719566021615056 + ], + [ + 39.30644737234725, + -5.7195653117428185 + ], + [ + 39.30658243481406, + -5.71958766860887 + ], + [ + 39.30755791938568, + -5.71966564819061 + ], + [ + 39.30756702204364, + -5.719676857809007 + ], + [ + 39.30805965961166, + -5.719710237089115 + ], + [ + 39.308066663423446, + -5.719722148731534 + ], + [ + 39.308189818321985, + -5.719722082614072 + ], + [ + 39.308312279104385, + -5.719732530432556 + ], + [ + 39.308312284733425, + -5.719743044019035 + ], + [ + 39.30843544000899, + -5.719743678754385 + ], + [ + 39.30843544564029, + -5.719754192340735 + ], + [ + 39.30855860091696, + -5.7197548270495195 + ], + [ + 39.30855930629429, + -5.7197653402595465 + ], + [ + 39.308681761828296, + -5.719765975317966 + ], + [ + 39.30868246720789, + -5.719776488527715 + ], + [ + 39.308804922742986, + -5.719777123559718 + ], + [ + 39.308805628124844, + -5.71978763676919 + ], + [ + 39.308927383917265, + -5.719788272151425 + ], + [ + 39.308928789045154, + -5.719798784983975 + ], + [ + 39.30905124458244, + -5.71979941996315 + ], + [ + 39.309056148807734, + -5.719810631816969 + ], + [ + 39.309923167539615, + -5.719877451244321 + ], + [ + 39.31063422293008, + -5.720091544007215 + ], + [ + 39.31065381500645, + -5.720090131590763 + ], + [ + 39.310662899577814, + -5.720067697687457 + ], + [ + 39.31067404178256, + -5.7199681630314965 + ], + [ + 39.31074121076393, + -5.719956211268805 + ], + [ + 39.31163696137919, + -5.720100813064275 + ], + [ + 39.311699250733554, + -5.720123208220514 + ], + [ + 39.31180561219929, + -5.720123851348939 + ], + [ + 39.311906387102475, + -5.720145524665541 + ], + [ + 39.311906393177225, + -5.720156739153791 + ], + [ + 39.31197216911476, + -5.720156703401592 + ], + [ + 39.31197427442288, + -5.720167916748612 + ], + [ + 39.31210233973633, + -5.720190276099752 + ], + [ + 39.31212192953011, + -5.720184658200196 + ], + [ + 39.3121295815013, + -5.720101246259322 + ], + [ + 39.31215196266817, + -5.720081608726603 + ], + [ + 39.31337307522748, + -5.720190284531242 + ], + [ + 39.3133506857093, + -5.720194502190286 + ], + [ + 39.31358511825828, + -5.720228017609581 + ], + [ + 39.313602918515315, + -5.740028584511519 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/33cae6/33cae6.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/33cae6/33cae6.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.287711527683925, + -5.743060299502631, + 39.31360456846548, + -5.719012276837555 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json b/tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json index de52a0fa0..32b53802c 100644 --- a/tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json @@ -1,92 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "3b20d4-labels", - "properties": { - "label:description": "Geojson building labels for scene 3b20d4", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 702 - } - ] - } - ], - "datetime": "2016-10-05T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.313582732137256, - -5.824334109106769 - ], - [ - 39.34087084324396, - -5.824386185654682 - ], - [ - 39.34092291979187, - -5.8517263733093 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3b20d4-labels", + "properties": { + "label:description": "Geojson building labels for scene 3b20d4", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.31347857904143, - -5.851778449857214 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 702 + } + ] + } ], - [ - 39.313582732137256, - -5.824334109106769 + "datetime": "2016-10-05T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ] - }, - "bbox": [ - 39.31347857904143, - -5.851778449857214, - 39.34092291979187, - -5.824334109106769 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.313582732137256, + -5.824334109106769 + ], + [ + 39.34087084324396, + -5.824386185654682 + ], + [ + 39.34092291979187, + -5.8517263733093 + ], + [ + 39.31347857904143, + -5.851778449857214 + ], + [ + 39.313582732137256, + -5.824334109106769 + ] + ] + ] + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3b20d4-labels/3b20d4.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3b20d4-labels/3b20d4.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.31347857904143, + -5.851778449857214, + 39.34092291979187, + -5.824334109106769 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json b/tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json index df27f835a..9a385cf3c 100644 --- a/tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json +++ b/tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json @@ -1,67 +1,68 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "3b20d4", - "properties": { - "area": "znz", - "datetime": "2016-10-05T00:00:00Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 39.313582732137256, - -5.824334109106769 - ], - [ - 39.34087084324396, - -5.824386185654682 - ], - [ - 39.34092291979187, - -5.8517263733093 - ], - [ - 39.31347857904143, - -5.851778449857214 - ], - [ - 39.313582732137256, - -5.824334109106769 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3b20d4", + "properties": { + "area": "znz", + "datetime": "2016-10-05T00:00:00Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 39.313582732137256, + -5.824334109106769 + ], + [ + 39.34087084324396, + -5.824386185654682 + ], + [ + 39.34092291979187, + -5.8517263733093 + ], + [ + 39.31347857904143, + -5.851778449857214 + ], + [ + 39.313582732137256, + -5.824334109106769 + ] + ] ] - ] - ] - }, - "bbox": [ - 39.31347857904143, - -5.851778449857214, - 39.34092291979187, - -5.824334109106769 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3b20d4/3b20d4.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3b20d4/3b20d4.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.31347857904143, + -5.851778449857214, + 39.34092291979187, + -5.824334109106769 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json b/tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json index 597f03962..4a0d84832 100644 --- a/tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "3f8360-labels", - "properties": { - "label:description": "Geojson building labels for scene 3f8360", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1441 - } - ] - } - ], - "datetime": "2016-10-08T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36446538964943, - -5.9307731675787885 - ], - [ - 39.36446684258708, - -5.932986095454288 - ], - [ - 39.34081351509101, - -5.933001162750248 - ], - [ - 39.34079687232617, - -5.905835680371048 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3f8360-labels", + "properties": { + "label:description": "Geojson building labels for scene 3f8360", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.364449044832185, - -5.905820682536527 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1441 + } + ] + } ], - [ - 39.36446538964943, - -5.9307731675787885 + "datetime": "2016-10-08T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34079687232617, - -5.933001162750248, - 39.36446684258708, - -5.905820682536527 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.36446538964943, + -5.9307731675787885 + ], + [ + 39.36446684258708, + -5.932986095454288 + ], + [ + 39.34081351509101, + -5.933001162750248 + ], + [ + 39.34079687232617, + -5.905835680371048 + ], + [ + 39.364449044832185, + -5.905820682536527 + ], + [ + 39.36446538964943, + -5.9307731675787885 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3f8360-labels/3f8360.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3f8360-labels/3f8360.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.34079687232617, + -5.933001162750248, + 39.36446684258708, + -5.905820682536527 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json b/tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json index f80e1ad34..e002c85c8 100644 --- a/tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json +++ b/tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "3f8360", - "properties": { - "area": "znz", - "datetime": "2016-10-08T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36446538964943, - -5.9307731675787885 - ], - [ - 39.36446684258708, - -5.932986095454288 - ], - [ - 39.34081351509101, - -5.933001162750248 - ], - [ - 39.34079687232617, - -5.905835680371048 - ], - [ - 39.364449044832185, - -5.905820682536527 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3f8360", + "properties": { + "area": "znz", + "datetime": "2016-10-08T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.36446538964943, + -5.9307731675787885 + ], + [ + 39.36446684258708, + -5.932986095454288 + ], + [ + 39.34081351509101, + -5.933001162750248 + ], + [ + 39.34079687232617, + -5.905835680371048 + ], + [ + 39.364449044832185, + -5.905820682536527 + ], + [ + 39.36446538964943, + -5.9307731675787885 + ] + ] ], - [ - 39.36446538964943, - -5.9307731675787885 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34079687232617, - -5.933001162750248, - 39.36446684258708, - -5.905820682536527 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3f8360/3f8360.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/3f8360/3f8360.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.34079687232617, + -5.933001162750248, + 39.36446684258708, + -5.905820682536527 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json b/tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json index 34f6488e1..336ba134f 100644 --- a/tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json @@ -1,92 +1,93 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "425403-labels", - "properties": { - "label:description": "Geojson building labels for scene 425403", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 224 - } - ] - } - ], - "datetime": "2016-10-10T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.34082996500427, - -5.960158147553525 - ], - [ - 39.340813249870706, - -5.933001378219889 - ], - [ - 39.31371005982263, - -5.933017400605379 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "425403-labels", + "properties": { + "label:description": "Geojson building labels for scene 425403", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.313725445739834, - -5.960174243786799 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 224 + } + ] + } ], - [ - 39.34082996500427, - -5.960158147553525 + "datetime": "2016-10-10T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31371005982263, - -5.960174243786799, - 39.34082996500427, - -5.933001378219889 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.34082996500427, + -5.960158147553525 + ], + [ + 39.340813249870706, + -5.933001378219889 + ], + [ + 39.31371005982263, + -5.933017400605379 + ], + [ + 39.313725445739834, + -5.960174243786799 + ], + [ + 39.34082996500427, + -5.960158147553525 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/425403-labels/425403.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/425403-labels/425403.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.31371005982263, + -5.960174243786799, + 39.34082996500427, + -5.933001378219889 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/425403/425403.json b/tests/data-files/catalogs/test-case-4/znz/425403/425403.json index 8109f3315..d15a6f17c 100644 --- a/tests/data-files/catalogs/test-case-4/znz/425403/425403.json +++ b/tests/data-files/catalogs/test-case-4/znz/425403/425403.json @@ -1,67 +1,68 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "425403", - "properties": { - "area": "znz", - "datetime": "2016-10-10T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.34082996500427, - -5.960158147553525 - ], - [ - 39.340813249870706, - -5.933001378219889 - ], - [ - 39.31371005982263, - -5.933017400605379 - ], - [ - 39.313725445739834, - -5.960174243786799 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "425403", + "properties": { + "area": "znz", + "datetime": "2016-10-10T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.34082996500427, + -5.960158147553525 + ], + [ + 39.340813249870706, + -5.933001378219889 + ], + [ + 39.31371005982263, + -5.933017400605379 + ], + [ + 39.313725445739834, + -5.960174243786799 + ], + [ + 39.34082996500427, + -5.960158147553525 + ] + ] ], - [ - 39.34082996500427, - -5.960158147553525 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31371005982263, - -5.960174243786799, - 39.34082996500427, - -5.933001378219889 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/425403/425403.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/425403/425403.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.31371005982263, + -5.960174243786799, + 39.34082996500427, + -5.933001378219889 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json b/tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json index afa23c322..967f03394 100644 --- a/tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "75cdfa-labels", - "properties": { - "label:description": "Geojson building labels for scene 75cdfa", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 566 - } - ] - } - ], - "datetime": "2016-10-05T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.31640547357606, - -5.878738481386144 - ], - [ - 39.31367952288209, - -5.8787400187081476 - ], - [ - 39.31366435148484, - -5.851583330154158 - ], - [ - 39.34076386428035, - -5.851567529006198 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "75cdfa-labels", + "properties": { + "label:description": "Geojson building labels for scene 75cdfa", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.34078034637437, - -5.878724143732914 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 566 + } + ] + } ], - [ - 39.31640547357606, - -5.878738481386144 + "datetime": "2016-10-05T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31366435148484, - -5.8787400187081476, - 39.34078034637437, - -5.851567529006198 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.31640547357606, + -5.878738481386144 + ], + [ + 39.31367952288209, + -5.8787400187081476 + ], + [ + 39.31366435148484, + -5.851583330154158 + ], + [ + 39.34076386428035, + -5.851567529006198 + ], + [ + 39.34078034637437, + -5.878724143732914 + ], + [ + 39.31640547357606, + -5.878738481386144 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/75cdfa-labels/75cdfa.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/75cdfa-labels/75cdfa.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.31366435148484, + -5.8787400187081476, + 39.34078034637437, + -5.851567529006198 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json b/tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json index d107f3524..567f22d8d 100644 --- a/tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json +++ b/tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "75cdfa", - "properties": { - "area": "znz", - "datetime": "2016-10-05T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.31640547357606, - -5.878738481386144 - ], - [ - 39.31367952288209, - -5.8787400187081476 - ], - [ - 39.31366435148484, - -5.851583330154158 - ], - [ - 39.34076386428035, - -5.851567529006198 - ], - [ - 39.34078034637437, - -5.878724143732914 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "75cdfa", + "properties": { + "area": "znz", + "datetime": "2016-10-05T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.31640547357606, + -5.878738481386144 + ], + [ + 39.31367952288209, + -5.8787400187081476 + ], + [ + 39.31366435148484, + -5.851583330154158 + ], + [ + 39.34076386428035, + -5.851567529006198 + ], + [ + 39.34078034637437, + -5.878724143732914 + ], + [ + 39.31640547357606, + -5.878738481386144 + ] + ] ], - [ - 39.31640547357606, - -5.878738481386144 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31366435148484, - -5.8787400187081476, - 39.34078034637437, - -5.851567529006198 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/75cdfa/75cdfa.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/75cdfa/75cdfa.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.31366435148484, + -5.8787400187081476, + 39.34078034637437, + -5.851567529006198 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json b/tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json index 1a925a142..2483ad14e 100644 --- a/tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json @@ -1,148 +1,149 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "9b8638-labels", - "properties": { - "label:description": "Geojson building labels for scene 9b8638", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 1612 - } - ] - } - ], - "datetime": "2016-10-04T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "9b8638-labels", + "properties": { + "label:description": "Geojson building labels for scene 9b8638", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" + ], + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 1612 + } + ] + } + ], + "datetime": "2016-10-04T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36017861532632, - -5.878613910758856 - ], - [ - 39.360158862698874, - -5.878711935371034 - ], - [ - 39.340780340086106, - -5.878724092981988 - ], - [ - 39.34076385802326, - -5.851567529009997 - ], - [ - 39.362418331025296, - -5.8515539621204695 - ], - [ - 39.362288479204956, - -5.858769648634405 - ], - [ - 39.3622848764889, - -5.858769650963058 - ], - [ - 39.362284870287944, - -5.858760030164733 - ], - [ - 39.362282468089724, - -5.858759430417258 - ], - [ - 39.36185063775144, - -5.864190652017378 - ], - [ - 39.36154486752859, - -5.867707853453685 - ], - [ - 39.36154424537411, - -5.867674181055383 - ], - [ - 39.361538262830855, - -5.867708459020328 - ], - [ - 39.36057452042873, - -5.86770847946345 - ], - [ - 39.36057837987134, - -5.8737124575003525 - ], - [ - 39.36102032449061, - -5.87371157100246 - ], - [ - 39.36102099271992, - -5.8738167980549205 - ], - [ - 39.360801036431035, - -5.876328569704563 - ], - [ - 39.36017861532632, - -5.878613910758856 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34076385802326, - -5.878724092981988, - 39.362419204085, - -5.8515539621204695 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.36017861532632, + -5.878613910758856 + ], + [ + 39.360158862698874, + -5.878711935371034 + ], + [ + 39.340780340086106, + -5.878724092981988 + ], + [ + 39.34076385802326, + -5.851567529009997 + ], + [ + 39.362418331025296, + -5.8515539621204695 + ], + [ + 39.362288479204956, + -5.858769648634405 + ], + [ + 39.3622848764889, + -5.858769650963058 + ], + [ + 39.362284870287944, + -5.858760030164733 + ], + [ + 39.362282468089724, + -5.858759430417258 + ], + [ + 39.36185063775144, + -5.864190652017378 + ], + [ + 39.36154486752859, + -5.867707853453685 + ], + [ + 39.36154424537411, + -5.867674181055383 + ], + [ + 39.361538262830855, + -5.867708459020328 + ], + [ + 39.36057452042873, + -5.86770847946345 + ], + [ + 39.36057837987134, + -5.8737124575003525 + ], + [ + 39.36102032449061, + -5.87371157100246 + ], + [ + 39.36102099271992, + -5.8738167980549205 + ], + [ + 39.360801036431035, + -5.876328569704563 + ], + [ + 39.36017861532632, + -5.878613910758856 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/9b8638-labels/9b8638.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/9b8638-labels/9b8638.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.34076385802326, + -5.878724092981988, + 39.362419204085, + -5.8515539621204695 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json b/tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json index ec0746e74..421b75f92 100644 --- a/tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json +++ b/tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json @@ -1,123 +1,124 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "9b8638", - "properties": { - "area": "znz", - "datetime": "2016-10-04T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36017861532632, - -5.878613910758856 - ], - [ - 39.360158862698874, - -5.878711935371034 - ], - [ - 39.340780340086106, - -5.878724092981988 - ], - [ - 39.34076385802326, - -5.851567529009997 - ], - [ - 39.362418331025296, - -5.8515539621204695 - ], - [ - 39.362288479204956, - -5.858769648634405 - ], - [ - 39.3622848764889, - -5.858769650963058 - ], - [ - 39.362284870287944, - -5.858760030164733 - ], - [ - 39.362282468089724, - -5.858759430417258 - ], - [ - 39.36185063775144, - -5.864190652017378 - ], - [ - 39.36154486752859, - -5.867707853453685 - ], - [ - 39.36154424537411, - -5.867674181055383 - ], - [ - 39.361538262830855, - -5.867708459020328 - ], - [ - 39.36057452042873, - -5.86770847946345 - ], - [ - 39.36057837987134, - -5.8737124575003525 - ], - [ - 39.36102032449061, - -5.87371157100246 - ], - [ - 39.36102099271992, - -5.8738167980549205 - ], - [ - 39.360801036431035, - -5.876328569704563 - ], - [ - 39.36017861532632, - -5.878613910758856 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34076385802326, - -5.878724092981988, - 39.362419204085, - -5.8515539621204695 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "9b8638", + "properties": { + "area": "znz", + "datetime": "2016-10-04T00:00:00Z" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.36017861532632, + -5.878613910758856 + ], + [ + 39.360158862698874, + -5.878711935371034 + ], + [ + 39.340780340086106, + -5.878724092981988 + ], + [ + 39.34076385802326, + -5.851567529009997 + ], + [ + 39.362418331025296, + -5.8515539621204695 + ], + [ + 39.362288479204956, + -5.858769648634405 + ], + [ + 39.3622848764889, + -5.858769650963058 + ], + [ + 39.362284870287944, + -5.858760030164733 + ], + [ + 39.362282468089724, + -5.858759430417258 + ], + [ + 39.36185063775144, + -5.864190652017378 + ], + [ + 39.36154486752859, + -5.867707853453685 + ], + [ + 39.36154424537411, + -5.867674181055383 + ], + [ + 39.361538262830855, + -5.867708459020328 + ], + [ + 39.36057452042873, + -5.86770847946345 + ], + [ + 39.36057837987134, + -5.8737124575003525 + ], + [ + 39.36102032449061, + -5.87371157100246 + ], + [ + 39.36102099271992, + -5.8738167980549205 + ], + [ + 39.360801036431035, + -5.876328569704563 + ], + [ + 39.36017861532632, + -5.878613910758856 + ] + ] + ], + "type": "Polygon" }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/9b8638/9b8638.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/9b8638/9b8638.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } + }, + "bbox": [ + 39.34076385802326, + -5.878724092981988, + 39.362419204085, + -5.8515539621204695 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json b/tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json index 8a45872d3..4db538c37 100644 --- a/tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "aee7fd-labels", - "properties": { - "label:description": "Geojson building labels for scene aee7fd", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 551 - } - ] - } - ], - "datetime": "2016-10-08T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.31705255429268, - -5.933015462962362 - ], - [ - 39.31371062447159, - -5.933017366868187 - ], - [ - 39.31369530504979, - -5.905851628735563 - ], - [ - 39.340797668473954, - -5.905835679883141 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "aee7fd-labels", + "properties": { + "label:description": "Geojson building labels for scene aee7fd", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.340814311389366, - -5.933001344149245 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 551 + } + ] + } ], - [ - 39.31705255429268, - -5.933015462962362 + "datetime": "2016-10-08T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31369530504979, - -5.933017366868187, - 39.340814311389366, - -5.905835679883141 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.31705255429268, + -5.933015462962362 + ], + [ + 39.31371062447159, + -5.933017366868187 + ], + [ + 39.31369530504979, + -5.905851628735563 + ], + [ + 39.340797668473954, + -5.905835679883141 + ], + [ + 39.340814311389366, + -5.933001344149245 + ], + [ + 39.31705255429268, + -5.933015462962362 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/aee7fd-labels/aee7fd.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/aee7fd-labels/aee7fd.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.31369530504979, + -5.933017366868187, + 39.340814311389366, + -5.905835679883141 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json b/tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json index 8aab01c82..fc130af26 100644 --- a/tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json +++ b/tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "aee7fd", - "properties": { - "area": "znz", - "datetime": "2016-10-08T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.31705255429268, - -5.933015462962362 - ], - [ - 39.31371062447159, - -5.933017366868187 - ], - [ - 39.31369530504979, - -5.905851628735563 - ], - [ - 39.340797668473954, - -5.905835679883141 - ], - [ - 39.340814311389366, - -5.933001344149245 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "aee7fd", + "properties": { + "area": "znz", + "datetime": "2016-10-08T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.31705255429268, + -5.933015462962362 + ], + [ + 39.31371062447159, + -5.933017366868187 + ], + [ + 39.31369530504979, + -5.905851628735563 + ], + [ + 39.340797668473954, + -5.905835679883141 + ], + [ + 39.340814311389366, + -5.933001344149245 + ], + [ + 39.31705255429268, + -5.933015462962362 + ] + ] ], - [ - 39.31705255429268, - -5.933015462962362 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.31369530504979, - -5.933017366868187, - 39.340814311389366, - -5.905835679883141 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/aee7fd/aee7fd.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/aee7fd/aee7fd.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.31369530504979, + -5.933017366868187, + 39.340814311389366, + -5.905835679883141 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json b/tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json index 07e7a2efc..44efd7d87 100644 --- a/tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "bc32f1-labels", - "properties": { - "label:description": "Geojson building labels for scene bc32f1", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 97 - } - ] - } - ], - "datetime": "2016-10-12T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.344219970219626, - -5.987276061137261 - ], - [ - 39.340847000464755, - -5.987278166971163 - ], - [ - 39.34083023028068, - -5.960158002925742 - ], - [ - 39.36793404669621, - -5.960140574104503 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "bc32f1-labels", + "properties": { + "label:description": "Geojson building labels for scene bc32f1", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.36795215042373, - -5.987260658288568 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 97 + } + ] + } ], - [ - 39.344219970219626, - -5.987276061137261 + "datetime": "2016-10-12T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34083023028068, - -5.987278166971163, - 39.36795215042373, - -5.960140574104503 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.344219970219626, + -5.987276061137261 + ], + [ + 39.340847000464755, + -5.987278166971163 + ], + [ + 39.34083023028068, + -5.960158002925742 + ], + [ + 39.36793404669621, + -5.960140574104503 + ], + [ + 39.36795215042373, + -5.987260658288568 + ], + [ + 39.344219970219626, + -5.987276061137261 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bc32f1-labels/bc32f1.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bc32f1-labels/bc32f1.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.34083023028068, + -5.987278166971163, + 39.36795215042373, + -5.960140574104503 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json b/tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json index 726b4c15c..1d2709183 100644 --- a/tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json +++ b/tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "bc32f1", - "properties": { - "area": "znz", - "datetime": "2016-10-12T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.344219970219626, - -5.987276061137261 - ], - [ - 39.340847000464755, - -5.987278166971163 - ], - [ - 39.34083023028068, - -5.960158002925742 - ], - [ - 39.36793404669621, - -5.960140574104503 - ], - [ - 39.36795215042373, - -5.987260658288568 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "bc32f1", + "properties": { + "area": "znz", + "datetime": "2016-10-12T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.344219970219626, + -5.987276061137261 + ], + [ + 39.340847000464755, + -5.987278166971163 + ], + [ + 39.34083023028068, + -5.960158002925742 + ], + [ + 39.36793404669621, + -5.960140574104503 + ], + [ + 39.36795215042373, + -5.987260658288568 + ], + [ + 39.344219970219626, + -5.987276061137261 + ] + ] ], - [ - 39.344219970219626, - -5.987276061137261 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34083023028068, - -5.987278166971163, - 39.36795215042373, - -5.960140574104503 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bc32f1/bc32f1.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bc32f1/bc32f1.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.34083023028068, + -5.987278166971163, + 39.36795215042373, + -5.960140574104503 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json b/tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json index 8c46a71d5..766476c23 100644 --- a/tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "bd5c14-labels", - "properties": { - "label:description": "Geojson building labels for scene bd5c14", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 781 - } - ] - } - ], - "datetime": "2016-10-11T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.367932205551796, - -5.957816165919622 - ], - [ - 39.367933753210345, - -5.960140452755652 - ], - [ - 39.3408296655117, - -5.960157881729879 - ], - [ - 39.34081295055692, - -5.9330013784041595 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "bd5c14-labels", + "properties": { + "label:description": "Geojson building labels for scene bd5c14", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.36791570908837, - -5.932984029391401 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 781 + } + ] + } ], - [ - 39.367932205551796, - -5.957816165919622 + "datetime": "2016-10-11T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34081295055692, - -5.960157881729879, - 39.367933753210345, - -5.932984029391401 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.367932205551796, + -5.957816165919622 + ], + [ + 39.367933753210345, + -5.960140452755652 + ], + [ + 39.3408296655117, + -5.960157881729879 + ], + [ + 39.34081295055692, + -5.9330013784041595 + ], + [ + 39.36791570908837, + -5.932984029391401 + ], + [ + 39.367932205551796, + -5.957816165919622 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bd5c14-labels/bd5c14.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bd5c14-labels/bd5c14.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.34081295055692, + -5.960157881729879, + 39.367933753210345, + -5.932984029391401 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json b/tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json index 659a087d5..19cad7c97 100644 --- a/tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json +++ b/tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "bd5c14", - "properties": { - "area": "znz", - "datetime": "2016-10-11T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.367932205551796, - -5.957816165919622 - ], - [ - 39.367933753210345, - -5.960140452755652 - ], - [ - 39.3408296655117, - -5.960157881729879 - ], - [ - 39.34081295055692, - -5.9330013784041595 - ], - [ - 39.36791570908837, - -5.932984029391401 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "bd5c14", + "properties": { + "area": "znz", + "datetime": "2016-10-11T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.367932205551796, + -5.957816165919622 + ], + [ + 39.367933753210345, + -5.960140452755652 + ], + [ + 39.3408296655117, + -5.960157881729879 + ], + [ + 39.34081295055692, + -5.9330013784041595 + ], + [ + 39.36791570908837, + -5.932984029391401 + ], + [ + 39.367932205551796, + -5.957816165919622 + ] + ] ], - [ - 39.367932205551796, - -5.957816165919622 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.34081295055692, - -5.960157881729879, - 39.367933753210345, - -5.932984029391401 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bd5c14/bd5c14.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/bd5c14/bd5c14.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.34081295055692, + -5.960157881729879, + 39.367933753210345, + -5.932984029391401 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json b/tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json index e658a7245..08497f0e7 100644 --- a/tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "c7415c-labels", - "properties": { - "label:description": "Geojson building labels for scene c7415c", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 949 - } - ] - } - ], - "datetime": "2016-09-21T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36029029677782, - -5.904972600767203 - ], - [ - 39.360290846530575, - -5.9058234437561135 - ], - [ - 39.34079687235772, - -5.9058357319970805 - ], - [ - 39.340780340171506, - -5.878724233360191 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "c7415c-labels", + "properties": { + "label:description": "Geojson building labels for scene c7415c", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.36027336873274, - -5.878712001912929 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 949 + } + ] + } ], - [ - 39.36029029677782, - -5.904972600767203 + "datetime": "2016-09-21T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.340780340171506, - -5.9058357319970805, - 39.360290846530575, - -5.878712001912929 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.36029029677782, + -5.904972600767203 + ], + [ + 39.360290846530575, + -5.9058234437561135 + ], + [ + 39.34079687235772, + -5.9058357319970805 + ], + [ + 39.340780340171506, + -5.878724233360191 + ], + [ + 39.36027336873274, + -5.878712001912929 + ], + [ + 39.36029029677782, + -5.904972600767203 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/c7415c-labels/c7415c.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/c7415c-labels/c7415c.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.340780340171506, + -5.9058357319970805, + 39.360290846530575, + -5.878712001912929 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json b/tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json index 5a694d191..9c7991b75 100644 --- a/tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json +++ b/tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "c7415c", - "properties": { - "area": "znz", - "datetime": "2016-09-21T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.36029029677782, - -5.904972600767203 - ], - [ - 39.360290846530575, - -5.9058234437561135 - ], - [ - 39.34079687235772, - -5.9058357319970805 - ], - [ - 39.340780340171506, - -5.878724233360191 - ], - [ - 39.36027336873274, - -5.878712001912929 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "c7415c", + "properties": { + "area": "znz", + "datetime": "2016-09-21T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.36029029677782, + -5.904972600767203 + ], + [ + 39.360290846530575, + -5.9058234437561135 + ], + [ + 39.34079687235772, + -5.9058357319970805 + ], + [ + 39.340780340171506, + -5.878724233360191 + ], + [ + 39.36027336873274, + -5.878712001912929 + ], + [ + 39.36029029677782, + -5.904972600767203 + ] + ] ], - [ - 39.36029029677782, - -5.904972600767203 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.340780340171506, - -5.9058357319970805, - 39.360290846530575, - -5.878712001912929 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/c7415c/c7415c.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/c7415c/c7415c.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.340780340171506, + -5.9058357319970805, + 39.360290846530575, + -5.878712001912929 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/collection.json b/tests/data-files/catalogs/test-case-4/znz/collection.json index 13c466783..a974a7e9a 100644 --- a/tests/data-files/catalogs/test-case-4/znz/collection.json +++ b/tests/data-files/catalogs/test-case-4/znz/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "znz", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Tier 1 training data from znz", "links": [ { @@ -144,6 +145,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ @@ -164,8 +166,5 @@ ] } }, - "license": "CC-BY-4.0", - "stac_extensions": [ - "label" - ] + "license": "CC-BY-4.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json b/tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json index 56d1953b0..a8437dbc7 100644 --- a/tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json +++ b/tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json @@ -1,96 +1,97 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "e52478-labels", - "properties": { - "label:description": "Geojson building labels for scene e52478", - "area": "znz", - "label:type": "vector", - "label:properties": [ - "building" - ], - "label:overviews": [ - { - "property_key": "building", - "counts": [ - { - "name": "yes", - "count": 21 - } - ] - } - ], - "datetime": "2016-11-05T00:00:00Z", - "label:classes": [ - { - "name": "building", - "classes": [ - "yes" - ] - } - ] - }, - "geometry": { - "coordinates": [ - [ - [ - 39.37891355673581, - -5.959065689521422 - ], - [ - 39.378914288970435, - -5.960133384034927 - ], - [ - 39.367933808083876, - -5.9601408243123455 - ], - [ - 39.36791576371174, - -5.932984029355098 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "e52478-labels", + "properties": { + "label:description": "Geojson building labels for scene e52478", + "area": "znz", + "label:type": "vector", + "label:properties": [ + "building" ], - [ - 39.37889570611965, - -5.9329766232129995 + "label:overviews": [ + { + "property_key": "building", + "counts": [ + { + "name": "yes", + "count": 21 + } + ] + } ], - [ - 39.37891355673581, - -5.959065689521422 + "datetime": "2016-11-05T00:00:00Z", + "label:classes": [ + { + "name": "building", + "classes": [ + "yes" + ] + } ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.36791576371174, - -5.9601408243123455, - 39.378914288970435, - -5.9329766232129995 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "geometry": { + "coordinates": [ + [ + [ + 39.37891355673581, + -5.959065689521422 + ], + [ + 39.378914288970435, + -5.960133384034927 + ], + [ + 39.367933808083876, + -5.9601408243123455 + ], + [ + 39.36791576371174, + -5.932984029355098 + ], + [ + 39.37889570611965, + -5.9329766232129995 + ], + [ + 39.37891355673581, + -5.959065689521422 + ] + ] + ], + "type": "Polygon" + }, + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "labels": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/e52478-labels/e52478.geojson", + "type": "application/geo+json" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "labels": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/e52478-labels/e52478.geojson", - "type": "application/geo+json" - } - }, - "stac_extensions": [ - "label" - ] + "bbox": [ + 39.36791576371174, + -5.9601408243123455, + 39.378914288970435, + -5.9329766232129995 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json b/tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json index e1a89b2fa..1632da701 100644 --- a/tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json +++ b/tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json @@ -1,71 +1,72 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "e52478", - "properties": { - "area": "znz", - "datetime": "2016-11-05T00:00:00Z" - }, - "geometry": { - "coordinates": [ - [ - [ - 39.37891355673581, - -5.959065689521422 - ], - [ - 39.378914288970435, - -5.960133384034927 - ], - [ - 39.367933808083876, - -5.9601408243123455 - ], - [ - 39.36791576371174, - -5.932984029355098 - ], - [ - 39.37889570611965, - -5.9329766232129995 + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "e52478", + "properties": { + "area": "znz", + "datetime": "2016-11-05T00:00:00Z" + }, + "geometry": { + "coordinates": [ + [ + [ + 39.37891355673581, + -5.959065689521422 + ], + [ + 39.378914288970435, + -5.960133384034927 + ], + [ + 39.367933808083876, + -5.9601408243123455 + ], + [ + 39.36791576371174, + -5.932984029355098 + ], + [ + 39.37889570611965, + -5.9329766232129995 + ], + [ + 39.37891355673581, + -5.959065689521422 + ] + ] ], - [ - 39.37891355673581, - -5.959065689521422 - ] - ] - ], - "type": "Polygon" - }, - "bbox": [ - 39.36791576371174, - -5.9601408243123455, - 39.378914288970435, - -5.9329766232129995 - ], - "links": [ - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" + "type": "Polygon" }, - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" + "links": [ + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "image": { + "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/e52478/e52478.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "GeoTIFF" + } }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": { - "image": { - "href": "https://drivendata-competition-building-segmentation.s3-us-west-1.amazonaws.com/train_tier_1/znz/e52478/e52478.tif", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", - "title": "GeoTIFF" - } - }, - "collection": "znz" + "bbox": [ + 39.36791576371174, + -5.9601408243123455, + 39.378914288970435, + -5.9329766232129995 + ], + "stac_extensions": [], + "collection": "znz" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json index 7e90bee86..6c1adf870 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json @@ -1,108 +1,146 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "CBERS_4_MUX_20190510_027_069_L2", - "properties": { - "datetime": "2019-05-10T04:29:23Z", - "cbers:data_type": "L2", - "cbers:path": 27, - "cbers:row": 69, - "eo:epsg": 32687, - "eo:off_nadir": 0.00239349, - "eo:sun_azimuth": 110.943, - "eo:sun_elevation": 66.3097 - }, - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - 87.532508, - 27.297172 - ], - [ - 88.734927, - 27.111523 - ], - [ - 89.018294, - 28.168944 - ], - [ - 87.804272, - 28.356352 - ], - [ - 87.532508, - 27.297172 - ] - ] - ] - ] - }, - "bbox": [ - 87.52967, - 27.111179, - 89.025681, - 28.3637 - ], - "links": [ - { - "rel": "collection", - "href": "../../collection.json" - }, - { - "rel": "root", - "href": "../../../../../../catalog.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" - } - ], - "assets": { - "thumbnail": { - "href": "https://s3.amazonaws.com/cbers-meta-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069.jpg", - "type": "image/jpeg" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "CBERS_4_MUX_20190510_027_069_L2", + "properties": { + "gsd": 20.0, + "platform": "CBERS-4", + "instruments": [ + "MUX" + ], + "eo:bands": [ + { + "name": "B5", + "common_name": "blue" + }, + { + "name": "B6", + "common_name": "green" + }, + { + "name": "B7", + "common_name": "red" + }, + { + "name": "B8", + "common_name": "nir" + } + ], + "datetime": "2019-05-10T04:29:23Z", + "cbers:data_type": "L2", + "cbers:path": 27, + "cbers:row": 69, + "proj:epsg": 32687, + "view:off_nadir": 0.00239349, + "view:sun_azimuth": 110.943, + "view:sun_elevation": 66.3097 }, - "metadata": { - "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND6.xml", - "type": "text/xml", - "title": "INPE original metadata" - }, - "B5": { - "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND5.tif", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "eo:bands": [ - 0 - ] - }, - "B6": { - "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND6.tif", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "eo:bands": [ - 1 - ] + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 87.532508, + 27.297172 + ], + [ + 88.734927, + 27.111523 + ], + [ + 89.018294, + 28.168944 + ], + [ + 87.804272, + 28.356352 + ], + [ + 87.532508, + 27.297172 + ] + ] + ] + ] }, - "B7": { - "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND7.tif", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "eo:bands": [ - 2 - ] + "links": [ + { + "rel": "collection", + "href": "../../collection.json" + }, + { + "rel": "root", + "href": "../../../../../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "assets": { + "thumbnail": { + "href": "https://s3.amazonaws.com/cbers-meta-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069.jpg", + "type": "image/jpeg" + }, + "metadata": { + "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND6.xml", + "type": "text/xml", + "title": "INPE original metadata" + }, + "B5": { + "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND5.tif", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "eo:bands": [ + { + "name": "B5", + "common_name": "blue" + } + ] + }, + "B6": { + "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND6.tif", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "eo:bands": [ + { + "name": "B6", + "common_name": "green" + } + ] + }, + "B7": { + "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND7.tif", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "eo:bands": [ + { + "name": "B7", + "common_name": "red" + } + ] + }, + "B8": { + "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND8.tif", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "eo:bands": [ + { + "name": "B8", + "common_name": "nir" + } + ] + } }, - "B8": { - "href": "s3://cbers-pds/CBERS4/MUX/027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2_BAND8.tif", - "type": "image/vnd.stac.geotiff; cloud-optimized=true", - "eo:bands": [ - 3 - ] - } - }, - "stac_extensions": [ - "eo" - ] + "bbox": [ + 87.52967, + 27.111179, + 89.025681, + 28.3637 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "collection": "CBERS4MUX" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json index a1b066b80..71471f1de 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "CBERS4 MUX 027/069", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "CBERS4 MUX camera path 027 row 069 catalog", "links": [ { @@ -18,5 +19,6 @@ "href": "../../catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json index 3501cd2c6..c3824daa2 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json @@ -1,6 +1,7 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", "id": "CBERS4MUX", + "stac_version": "1.0.0-rc.3", "description": "CBERS4 MUX camera catalog", "links": [ { @@ -16,7 +17,7 @@ "href": "069/catalog.json" } ], - "license": "CC-BY-SA-3.0", + "stac_extensions": [], "providers": [ { "name": "Instituto Nacional de Pesquisas Espaciais, INPE", @@ -27,10 +28,10 @@ }, { "name": "AMS Kepler", + "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", "roles": [ "processor" ], - "description": "Convert INPE's original TIFF to COG and copy to Amazon Web Services", "url": "https://github.com/fredliporace/cbers-on-aws" }, { @@ -41,18 +42,6 @@ "url": "https://registry.opendata.aws/cbers/" } ], - "extent": { - "spatial": [ - -180.0, - -83.0, - 180.0, - 83.0 - ], - "temporal": [ - "2014-12-08T00:00:00Z", - null - ] - }, "properties": { "eo:gsd": 20.0, "eo:platform": "CBERS-4", @@ -75,5 +64,26 @@ "common_name": "nir" } ] - } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json index 1451bdd28..9f4061521 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "CBERS4 MUX 027", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "CBERS4 MUX camera path 027 catalog", "links": [ { @@ -18,5 +19,6 @@ "href": "../collection.json", "type": "application/json" } - ] -} + ], + "stac_extensions": [] +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json index 2315bd5ab..574ffdf77 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json @@ -1,6 +1,7 @@ { + "type": "Collection", "id": "CBERS4MUX", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "CBERS4 MUX camera catalog", "links": [ { @@ -19,27 +20,6 @@ "type": "application/json" } ], - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -83.0, - 180.0, - 83.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2014-12-08T00:00:00Z", - null - ] - ] - } - }, - "license": "CC-BY-SA-3.0", "stac_extensions": [], "providers": [ { @@ -65,27 +45,25 @@ "url": "https://registry.opendata.aws/cbers/" } ], - "properties": { - "eo:gsd": 20.0, - "eo:platform": "CBERS-4", - "eo:instrument": "MUX", - "eo:bands": [ - { - "name": "B5", - "common_name": "blue" - }, - { - "name": "B6", - "common_name": "green" - }, - { - "name": "B7", - "common_name": "red" - }, - { - "name": "B8", - "common_name": "nir" - } - ] - } -} + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -83.0, + 180.0, + 83.0 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2014-12-08T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-SA-3.0" +} \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/catalog.json b/tests/data-files/catalogs/test-case-5/CBERS4/catalog.json index 303b0a8d7..1992938ae 100644 --- a/tests/data-files/catalogs/test-case-5/CBERS4/catalog.json +++ b/tests/data-files/catalogs/test-case-5/CBERS4/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "CBERS4", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Catalogs of CBERS-4 mission's imagery on AWS", "links": [ { @@ -18,5 +19,6 @@ "href": "../catalog.json", "type": "application/json" } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-5/catalog.json b/tests/data-files/catalogs/test-case-5/catalog.json index 3c4b838fb..ddf80e8d9 100644 --- a/tests/data-files/catalogs/test-case-5/catalog.json +++ b/tests/data-files/catalogs/test-case-5/catalog.json @@ -1,6 +1,7 @@ { + "type": "Catalog", "id": "CBERS", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "Catalogs of CBERS mission's imagery on AWS", "links": [ { @@ -14,5 +15,6 @@ "type": "application/json" } ], + "stac_extensions": [], "title": "CBERS 4 on AWS" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json index 14876501e..b22202b1b 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json @@ -1,10 +1,50 @@ { - "id": "3c67b59c-2e6f-47fb-ba3c-0dd106941096", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [ - "label" - ], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3c67b59c-2e6f-47fb-ba3c-0dd106941096", + "properties": { + "label:description": "Labels in layer", + "label:type": "vector", + "datetime": "2019-08-21T15:51:17.881000Z", + "label:method": [ + "manual" + ], + "label:classes": [ + { + "name": "Coverage", + "classes": [ + "Obstructed", + "0-10%", + "11-50%", + "51-90%", + "91-100%" + ] + }, + { + "name": "Data Quality", + "classes": [ + "Bad", + "Good" + ] + }, + { + "name": "Alignment", + "classes": [ + "Obstructed/Unknown", + "< 90% overlap", + "\u2265 90% overlap" + ] + } + ], + "label:task": [ + "classification" + ], + "label:property": [ + "Coverage", + "Data Quality", + "Alignment" + ] + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -4002,37 +4042,31 @@ ] ] }, - "bbox": [ - 39.191417450329524, - -6.892441704534412, - 39.34885007331245, - -6.729678190172387 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Label Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Label Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4040,8 +4074,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4049,8 +4083,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4058,8 +4092,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4067,8 +4101,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4076,8 +4110,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4085,8 +4119,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4094,8 +4128,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4103,8 +4137,8 @@ ] }, { - "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json", "rel": "source", + "href": "../9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "title": "Source image STAC item for the label item", "label:assets": [ @@ -4115,52 +4149,18 @@ "assets": { "3c67b59c-2e6f-47fb-ba3c-0dd106941096": { "href": "./data.geojson", - "title": "Label Data Feature Collection", - "type": "application/geo+json" + "type": "application/geo+json", + "title": "Label Data Feature Collection" } }, - "collection": "400c22e3-5b54-438b-b600-5e9bd6d0a498", - "properties": { - "label:description": "Labels in layer", - "label:type": "vector", - "datetime": "2019-08-21T15:51:17.881Z", - "label:method": [ - "manual" - ], - "label:classes": [ - { - "name": "Coverage", - "classes": [ - "Obstructed", - "0-10%", - "11-50%", - "51-90%", - "91-100%" - ] - }, - { - "name": "Data Quality", - "classes": [ - "Bad", - "Good" - ] - }, - { - "name": "Alignment", - "classes": [ - "Obstructed/Unknown", - "< 90% overlap", - "\u2265 90% overlap" - ] - } - ], - "label:task": [ - "classification" - ], - "label:property": [ - "Coverage", - "Data Quality", - "Alignment" - ] - } + "bbox": [ + 39.191417450329524, + -6.892441704534412, + 39.34885007331245, + -6.729678190172387 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/label/v1.0.0/schema.json" + ], + "collection": "400c22e3-5b54-438b-b600-5e9bd6d0a498" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json index 1c353b1bb..b9c746c92 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json @@ -1,12 +1,39 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", "id": "400c22e3-5b54-438b-b600-5e9bd6d0a498", - "title": "Label Collection", + "stac_version": "1.0.0-rc.3", "description": "Label Collection in layer 12cb17a8-ae08-469c-a2be-4d0619240014", + "links": [ + { + "rel": "item", + "href": "./3c67b59c-2e6f-47fb-ba3c-0dd106941096.json", + "type": "application/json", + "label:assets": [] + }, + { + "rel": "license", + "href": "http://www.apache.org/licenses/LICENSE-2.0", + "label:assets": [] + }, + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json", + "title": "Layer Collection", + "label:assets": [] + } + ], + "stac_extensions": [], + "title": "Label Collection", "keywords": [], "version": "1", - "license": "Apache-2.0", "providers": [], + "properties": {}, "extent": { "spatial": { "bbox": [ @@ -21,38 +48,11 @@ "temporal": { "interval": [ [ - "2019-08-21T15:51:17.881Z", - "2019-08-21T15:51:17.881Z" + "2019-08-21T15:51:17.881000Z", + "2019-08-21T15:51:17.881000Z" ] ] } }, - "properties": {}, - "links": [ - { - "href": "./3c67b59c-2e6f-47fb-ba3c-0dd106941096.json", - "rel": "item", - "type": "application/json", - "label:assets": [] - }, - { - "href": "http://www.apache.org/licenses/LICENSE-2.0", - "rel": "license", - "label:assets": [] - }, - { - "href": "../../catalog.json", - "rel": "root", - "type": "application/json", - "title": "Root", - "label:assets": [] - }, - { - "href": "../collection.json", - "rel": "parent", - "type": "application/json", - "title": "Layer Collection", - "label:assets": [] - } - ] + "license": "Apache-2.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json index 31fa600f6..9e45044ca 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json @@ -1,8 +1,10 @@ { - "id": "3bfb5bd3-5740-4ed0-92c9-968cc532dbc5", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "3bfb5bd3-5740-4ed0-92c9-968cc532dbc5", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.191471485924936, - -6.873418639717923, - 39.26232911919128, - -6.8025353342930215 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "3bfb5bd3-5740-4ed0-92c9-968cc532dbc5": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.191471485924936, + -6.873418639717923, + 39.26232911919128, + -6.8025353342930215 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json index 1cc1791d5..fc75910e4 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json @@ -1,8 +1,10 @@ { - "id": "41c57cea-50ba-495c-9ee7-17ddba837380", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "41c57cea-50ba-495c-9ee7-17ddba837380", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.26232911919128, - -6.88620244952769, - 39.33324512902654, - -6.873418639717923 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "41c57cea-50ba-495c-9ee7-17ddba837380": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/41c57cea-50ba-495c-9ee7-17ddba837380/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.26232911919128, + -6.88620244952769, + 39.33324512902654, + -6.873418639717923 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json index 100a2a41a..da107ace6 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json @@ -1,8 +1,10 @@ { - "id": "4514bbc6-cd17-4c14-816d-1709dfc61079", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "4514bbc6-cd17-4c14-816d-1709dfc61079", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.33324512902654, - -6.8025353342930215, - 39.3484066149445, - -6.731655507762342 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "4514bbc6-cd17-4c14-816d-1709dfc61079": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/4514bbc6-cd17-4c14-816d-1709dfc61079/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.33324512902654, + -6.8025353342930215, + 39.3484066149445, + -6.731655507762342 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json index 5d1047ce8..97e5024b9 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json @@ -1,8 +1,10 @@ { - "id": "52072b95-0275-4255-be5e-c567006f80cb", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "52072b95-0275-4255-be5e-c567006f80cb", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.19141745032953, - -6.892441704534412, - 39.26232911919128, - -6.873418639717923 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "52072b95-0275-4255-be5e-c567006f80cb": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/52072b95-0275-4255-be5e-c567006f80cb/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.19141745032953, + -6.892441704534412, + 39.26232911919128, + -6.873418639717923 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json index 32a36ec77..e5d95103c 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json @@ -1,8 +1,10 @@ { - "id": "84fe42c0-9d23-404a-bd0f-1406112cca8c", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "84fe42c0-9d23-404a-bd0f-1406112cca8c", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.33324512902654, - -6.873418639717923, - 39.34866053761873, - -6.8025353342930215 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "84fe42c0-9d23-404a-bd0f-1406112cca8c": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/84fe42c0-9d23-404a-bd0f-1406112cca8c/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.33324512902654, + -6.873418639717923, + 39.34866053761873, + -6.8025353342930215 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json index 0b0be1415..7805c1d82 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json @@ -1,8 +1,10 @@ { - "id": "884a939d-1d73-4351-9601-f9ee89a980b0", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "884a939d-1d73-4351-9601-f9ee89a980b0", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.26232911919128, - -6.873418639717923, - 39.33324512902654, - -6.8025353342930215 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "884a939d-1d73-4351-9601-f9ee89a980b0": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/884a939d-1d73-4351-9601-f9ee89a980b0/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.26232911919128, + -6.873418639717923, + 39.33324512902654, + -6.8025353342930215 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json index a4005fffe..da0d5ad1a 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json @@ -1,8 +1,10 @@ { - "id": "a6bd2ff9-3805-40ab-96fa-b7f112b18973", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "a6bd2ff9-3805-40ab-96fa-b7f112b18973", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.19167311569687, - -6.8025353342930215, - 39.26232911919128, - -6.738583593313782 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "a6bd2ff9-3805-40ab-96fa-b7f112b18973": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/a6bd2ff9-3805-40ab-96fa-b7f112b18973/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.19167311569687, + -6.8025353342930215, + 39.26232911919128, + -6.738583593313782 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json index 116805eaf..46b2b2c38 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json @@ -1,106 +1,106 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", "id": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "title": "Scene Collection", + "stac_version": "1.0.0-rc.3", "description": "Scene collection in layer 12cb17a8-ae08-469c-a2be-4d0619240014", - "keywords": [], - "version": "1", - "license": "Apache-2.0", - "providers": [], - "extent": { - "spatial": { - "bbox": [ - [ - 39.191417450329524, - -6.892441704534412, - 39.34867907353046, - -6.73165550776233 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2019-08-21T15:51:17.881Z", - "2019-08-21T15:51:17.881Z" - ] - ] - } - }, - "properties": {}, "links": [ { - "href": "http://www.apache.org/licenses/LICENSE-2.0", "rel": "license", + "href": "http://www.apache.org/licenses/LICENSE-2.0", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", - "type": "application/json", - "title": "Root", - "label:assets": [] + "href": "../../catalog.json", + "type": "application/json" }, { - "href": "../collection.json", "rel": "parent", + "href": "../collection.json", "type": "application/json", "title": "Layer Collection", "label:assets": [] }, { - "href": "./3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json", "rel": "item", + "href": "./3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json", "type": "application/json", "label:assets": [] }, { - "href": "./41c57cea-50ba-495c-9ee7-17ddba837380.json", "rel": "item", + "href": "./41c57cea-50ba-495c-9ee7-17ddba837380.json", "type": "application/json", "label:assets": [] }, { - "href": "./4514bbc6-cd17-4c14-816d-1709dfc61079.json", "rel": "item", + "href": "./4514bbc6-cd17-4c14-816d-1709dfc61079.json", "type": "application/json", "label:assets": [] }, { - "href": "./52072b95-0275-4255-be5e-c567006f80cb.json", "rel": "item", + "href": "./52072b95-0275-4255-be5e-c567006f80cb.json", "type": "application/json", "label:assets": [] }, { - "href": "./84fe42c0-9d23-404a-bd0f-1406112cca8c.json", "rel": "item", + "href": "./84fe42c0-9d23-404a-bd0f-1406112cca8c.json", "type": "application/json", "label:assets": [] }, { - "href": "./884a939d-1d73-4351-9601-f9ee89a980b0.json", "rel": "item", + "href": "./884a939d-1d73-4351-9601-f9ee89a980b0.json", "type": "application/json", "label:assets": [] }, { - "href": "./a6bd2ff9-3805-40ab-96fa-b7f112b18973.json", "rel": "item", + "href": "./a6bd2ff9-3805-40ab-96fa-b7f112b18973.json", "type": "application/json", "label:assets": [] }, { - "href": "./e40bb30d-0295-42ed-ba04-6cf563b057f9.json", "rel": "item", + "href": "./e40bb30d-0295-42ed-ba04-6cf563b057f9.json", "type": "application/json", "label:assets": [] }, { - "href": "./eb1124cc-3b45-4487-bc2c-4855ac877ad9.json", "rel": "item", + "href": "./eb1124cc-3b45-4487-bc2c-4855ac877ad9.json", "type": "application/json", "label:assets": [] } - ] + ], + "stac_extensions": [], + "title": "Scene Collection", + "keywords": [], + "version": "1", + "providers": [], + "properties": {}, + "extent": { + "spatial": { + "bbox": [ + [ + 39.191417450329524, + -6.892441704534412, + 39.34867907353046, + -6.73165550776233 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2019-08-21T15:51:17.881000Z", + "2019-08-21T15:51:17.881000Z" + ] + ] + } + }, + "license": "Apache-2.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json index a09a16df7..b53852c38 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json @@ -1,8 +1,10 @@ { - "id": "e40bb30d-0295-42ed-ba04-6cf563b057f9", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "e40bb30d-0295-42ed-ba04-6cf563b057f9", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.26232911919128, - -6.8025353342930215, - 39.33324512902654, - -6.732860045871766 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "e40bb30d-0295-42ed-ba04-6cf563b057f9": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/e40bb30d-0295-42ed-ba04-6cf563b057f9/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.26232911919128, + -6.8025353342930215, + 39.33324512902654, + -6.732860045871766 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json index 60bf637f9..c61f90bc2 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json @@ -1,8 +1,10 @@ { - "id": "eb1124cc-3b45-4487-bc2c-4855ac877ad9", - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "eb1124cc-3b45-4487-bc2c-4855ac877ad9", + "properties": { + "datetime": "2019-08-21T15:51:17.881000Z" + }, "geometry": { "type": "MultiPolygon", "coordinates": [ @@ -32,30 +34,24 @@ ] ] }, - "bbox": [ - 39.33324512902654, - -6.879948848100303, - 39.34867907353046, - -6.873418639717923 - ], "links": [ { - "href": "./collection.json", "rel": "parent", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "./collection.json", "rel": "collection", + "href": "./collection.json", "type": "application/json", "title": "Scene Collection", "label:assets": [] }, { - "href": "../../catalog.json", "rel": "root", + "href": "../../catalog.json", "type": "application/json", "title": "Root", "label:assets": [] @@ -64,12 +60,16 @@ "assets": { "eb1124cc-3b45-4487-bc2c-4855ac877ad9": { "href": "s3://rasterfoundry-production-data-us-east-1/user-uploads/auth0|5c4201f36b7b7c0da8243313/eb1124cc-3b45-4487-bc2c-4855ac877ad9/cog.tif", - "title": "scene", - "type": "image/vnd.stac.geotiff; cloud-optimized=true" + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "scene" } }, - "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048", - "properties": { - "datetime": "2019-08-21T15:51:17.881" - } + "bbox": [ + 39.33324512902654, + -6.879948848100303, + 39.34867907353046, + -6.873418639717923 + ], + "stac_extensions": [], + "collection": "9420e0fd-3731-40e8-93ff-5b6ad0067048" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json index 71cda2fed..eb5514295 100644 --- a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json +++ b/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json @@ -1,66 +1,66 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", "id": "12cb17a8-ae08-469c-a2be-4d0619240014", - "title": "Layers", + "stac_version": "1.0.0-rc.3", "description": "Project Layer Collection", - "keywords": [], - "version": "1.0", - "license": "Apache-2.0", - "providers": [], - "extent": { - "spatial": { - "bbox": [ - [ - 39.191417450329524, - -6.892441704534412, - 39.34867907353046, - -6.73165550776233 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2019-08-21T15:51:17.881Z", - "2019-08-21T15:51:17.881Z" - ] - ] - } - }, - "properties": {}, "links": [ { - "href": "http://www.apache.org/licenses/LICENSE-2.0", "rel": "license", + "href": "http://www.apache.org/licenses/LICENSE-2.0", "label:assets": [] }, { - "href": "../catalog.json", "rel": "parent", + "href": "../catalog.json", "type": "application/json", "title": "Catalog fd478c2b-3f71-41e4-a87b-e97a8a0d0afa", "label:assets": [] }, { - "href": "../catalog.json", "rel": "root", - "type": "application/json", - "title": "Root Catalog", - "label:assets": [] + "href": "../catalog.json", + "type": "application/json" }, { - "href": "./9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json", "rel": "child", + "href": "./9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json", "type": "application/json", "title": "Scene Collection: 9420e0fd-3731-40e8-93ff-5b6ad0067048", "label:assets": [] }, { - "href": "./400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json", "rel": "child", + "href": "./400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json", "type": "application/json", "title": "Label Collection: 400c22e3-5b54-438b-b600-5e9bd6d0a498", "label:assets": [] } - ] + ], + "stac_extensions": [], + "title": "Layers", + "keywords": [], + "version": "1.0", + "providers": [], + "properties": {}, + "extent": { + "spatial": { + "bbox": [ + [ + 39.191417450329524, + -6.892441704534412, + 39.34867907353046, + -6.73165550776233 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2019-08-21T15:51:17.881000Z", + "2019-08-21T15:51:17.881000Z" + ] + ] + } + }, + "license": "Apache-2.0" } \ No newline at end of file diff --git a/tests/data-files/catalogs/test-case-6/catalog.json b/tests/data-files/catalogs/test-case-6/catalog.json index e7b8f520d..a78e5fbe8 100644 --- a/tests/data-files/catalogs/test-case-6/catalog.json +++ b/tests/data-files/catalogs/test-case-6/catalog.json @@ -1,21 +1,23 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Catalog", "id": "fd478c2b-3f71-41e4-a87b-e97a8a0d0afa", + "stac_version": "1.0.0-rc.3", "description": "Exported from Raster Foundry 2020-01-09 21:17:20.186", "links": [ { - "href": "./catalog.json", "rel": "root", + "href": "./catalog.json", "type": "application/json", "title": "Catalog fd478c2b-3f71-41e4-a87b-e97a8a0d0afa", "label:assets": [] }, { - "href": "./12cb17a8-ae08-469c-a2be-4d0619240014/collection.json", "rel": "child", + "href": "./12cb17a8-ae08-469c-a2be-4d0619240014/collection.json", "type": "application/json", "title": "Layer Collection 12cb17a8-ae08-469c-a2be-4d0619240014", "label:assets": [] } - ] + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index 4c4e57fb7..83afa0d52 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -21,13 +21,14 @@ def migrate(path: str) -> None: if 'stac_version' in stac_json: cur_ver = stac_json['stac_version'] - if not cur_ver == TARGET_VERSION: + #if not cur_ver == TARGET_VERSION: + if True: print(' - Migrating {} from {} to {}...'.format( path, cur_ver, TARGET_VERSION)) obj = ps.read_dict(stac_json, href=path) - migrated = obj.to_dict() + migrated = obj.to_dict(include_self_link=False) with open(path, 'w') as f: - json.dump(migrated, f, indent=2) + json.dump(migrated, f, indent=2) if __name__ == '__main__': diff --git a/tests/data-files/collections/multi-extent.json b/tests/data-files/collections/multi-extent.json index afcdb1a81..e7b272121 100644 --- a/tests/data-files/collections/multi-extent.json +++ b/tests/data-files/collections/multi-extent.json @@ -1,8 +1,15 @@ { + "type": "Collection", "id": "area-1-1", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "description": "test collection country-1", - "links": [{ + "links": [ + { + "rel": "root", + "href": "./multi-extent.json", + "type": "application/json" + }, + { "rel": "item", "href": "./area-1-1-imagery/area-1-1-imagery.json", "type": "application/json" @@ -18,6 +25,7 @@ "type": "application/json" } ], + "stac_extensions": [], "extent": { "spatial": { "bbox": [ diff --git a/tests/data-files/commons/example-collection-with-commons.json b/tests/data-files/commons/example-collection-with-commons.json index 76e3b7a34..caccd227a 100644 --- a/tests/data-files/commons/example-collection-with-commons.json +++ b/tests/data-files/commons/example-collection-with-commons.json @@ -1,68 +1,62 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", + "id": "collection-with", + "stac_version": "1.0.0-rc.3", + "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", + "links": [ + { + "rel": "parent", + "href": "./example-collection-with-commons.json" + }, + { + "rel": "root", + "href": "./example-collection-with-commons.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./example-item-with-commons.json" + } + ], "stac_extensions": [ - "commons", "view", "eo" ], - "id": "collection-with", "title": "Landsat 8 L1", - "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", "keywords": [ "landsat" ], - "extent": { - "spatial": { - "bbox": [ - [ - -180, - -90, - 180, - 90 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2013-06-01T00:00:00Z", - null - ] - ] - } - }, "providers": [ { "name": "USGS", - "url": "https://landsat.usgs.gov/", "roles": [ "producer", "licensor" - ] + ], + "url": "https://landsat.usgs.gov/" }, { "name": "Planet Labs", - "url": "https://github.com/landsat-pds/landsat_ingestor", "roles": [ "processor" - ] + ], + "url": "https://github.com/landsat-pds/landsat_ingestor" }, { "name": "AWS", - "url": "https://landsatonaws.com/", "roles": [ "host" - ] + ], + "url": "https://landsatonaws.com/" }, { "name": "Development Seed", - "url": "https://developmentseed.org/", "roles": [ "processor" - ] + ], + "url": "https://developmentseed.org/" } ], - "license": "PDDL-1.0", "properties": { "platform": "landsat-8", "instruments": [ @@ -140,18 +134,25 @@ } ] }, - "links": [ - { - "rel": "parent", - "href": "./example-collection-with-commons.json" - }, - { - "rel": "root", - "href": "./example-collection-with-commons.json" + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ] }, - { - "rel": "item", - "href": "./example-item-with-commons.json" + "temporal": { + "interval": [ + [ + "2013-06-01T00:00:00Z", + null + ] + ] } - ] + }, + "license": "PDDL-1.0" } \ No newline at end of file diff --git a/tests/data-files/commons/example-collection-without-commons.json b/tests/data-files/commons/example-collection-without-commons.json index ae6756aff..b5e1211c6 100644 --- a/tests/data-files/commons/example-collection-without-commons.json +++ b/tests/data-files/commons/example-collection-without-commons.json @@ -1,67 +1,62 @@ { - "stac_version": "1.0.0-beta.2", + "type": "Collection", + "id": "collection-without", + "stac_version": "1.0.0-rc.3", + "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", + "links": [ + { + "rel": "parent", + "href": "./example-collection-without-commons.json" + }, + { + "rel": "root", + "href": "./example-collection-without-commons.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./example-item-without-commons.json" + } + ], "stac_extensions": [ "view", "eo" ], - "id": "collection-without", "title": "Landsat 8 L1", - "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", "keywords": [ "landsat" ], - "extent": { - "spatial": { - "bbox": [ - [ - -180, - -90, - 180, - 90 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2013-06-01T00:00:00Z", - null - ] - ] - } - }, "providers": [ { "name": "USGS", - "url": "https://landsat.usgs.gov/", "roles": [ "producer", "licensor" - ] + ], + "url": "https://landsat.usgs.gov/" }, { "name": "Planet Labs", - "url": "https://github.com/landsat-pds/landsat_ingestor", "roles": [ "processor" - ] + ], + "url": "https://github.com/landsat-pds/landsat_ingestor" }, { "name": "AWS", - "url": "https://landsatonaws.com/", "roles": [ "host" - ] + ], + "url": "https://landsatonaws.com/" }, { "name": "Development Seed", - "url": "https://developmentseed.org/", "roles": [ "processor" - ] + ], + "url": "https://developmentseed.org/" } ], - "license": "PDDL-1.0", "properties": { "platform": "landsat-8", "instruments": [ @@ -139,18 +134,25 @@ } ] }, - "links": [ - { - "rel": "parent", - "href": "./example-collection-without-commons.json" - }, - { - "rel": "root", - "href": "./example-collection-without-commons.json" + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ] }, - { - "rel": "item", - "href": "./example-item-without-commons.json" + "temporal": { + "interval": [ + [ + "2013-06-01T00:00:00Z", + null + ] + ] } - ] + }, + "license": "PDDL-1.0" } \ No newline at end of file diff --git a/tests/data-files/commons/example-item-with-commons.json b/tests/data-files/commons/example-item-with-commons.json index 0e1cc197a..02e8cd3ac 100644 --- a/tests/data-files/commons/example-item-with-commons.json +++ b/tests/data-files/commons/example-item-with-commons.json @@ -1,19 +1,15 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [ - "commons", - "eo", - "view", - "https://example.com/stac/landsat-extension/1.0/schema.json" - ], - "id": "item-with", "type": "Feature", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], + "stac_version": "1.0.0-rc.3", + "id": "item-with", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "eo:cloud_cover": 78, + "view:sun_azimuth": 168.8989761, + "view:sun_elevation": 26.32596431, + "landsat:path": 107, + "landsat:row": 18 + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -41,123 +37,128 @@ ] ] }, - "collection": "collection-with", - "properties": { - "datetime": "2018-10-01T01:08:32.033Z", - "eo:cloud_cover": 78, - "view:sun_azimuth": 168.8989761, - "view:sun_elevation": 26.32596431, - "landsat:path": 107, - "landsat:row": 18 - }, + "links": [ + { + "rel": "collection", + "href": "./example-collection-with-commons.json" + }, + { + "rel": "parent", + "href": "./example-collection-with-commons.json" + }, + { + "rel": "root", + "href": "./example-collection-with-commons.json" + } + ], "assets": { "ANG": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt", - "title": "Angle coefficients file", - "type": "text/plain" + "type": "text/plain", + "title": "Angle coefficients file" }, "B1": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", "eo:bands": [ 0 - ], - "title": "Band 1 (coastal)" + ] }, "B2": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 2 (blue)", "eo:bands": [ 1 - ], - "title": "Band 2 (blue)" + ] }, "B3": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 3 (green)", "eo:bands": [ 2 - ], - "title": "Band 3 (green)" + ] }, "B4": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 4 (red)", "eo:bands": [ 3 - ], - "title": "Band 4 (red)" + ] }, "B5": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B5.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 5 (nir)", "eo:bands": [ 4 - ], - "title": "Band 5 (nir)" + ] }, "B6": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B6.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 6 (swir16)", "eo:bands": [ 5 - ], - "title": "Band 6 (swir16)" + ] }, "B7": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B7.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 7 (swir22)", "eo:bands": [ 6 - ], - "title": "Band 7 (swir22)" + ] }, "B8": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 8 (pan)", "eo:bands": [ 7 - ], - "title": "Band 8 (pan)" + ] }, "B9": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B9.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 9 (cirrus)", "eo:bands": [ 8 - ], - "title": "Band 9 (cirrus)" + ] }, "B10": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B10.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 10 (lwir)", "eo:bands": [ 9 - ], - "title": "Band 10 (lwir)" + ] }, "B11": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B11.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 11 (lwir)", "eo:bands": [ 10 - ], - "title": "Band 11 (lwir)" + ] }, "BQA": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_BQA.TIF", - "title": "Band quality data", - "type": "image/tiff; application=geotiff" + "type": "image/tiff; application=geotiff", + "title": "Band quality data" }, "MTL": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_MTL.txt", - "title": "original metadata file", - "type": "text/plain" + "type": "text/plain", + "title": "original metadata file" }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", - "title": "Thumbnail image", - "type": "image/jpeg" + "type": "image/jpeg", + "title": "Thumbnail image" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", @@ -165,18 +166,16 @@ "title": "HTML index page" } }, - "links": [ - { - "rel": "collection", - "href": "./example-collection-with-commons.json" - }, - { - "rel": "parent", - "href": "./example-collection-with-commons.json" - }, - { - "rel": "root", - "href": "./example-collection-with-commons.json" - } - ] + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://example.com/stac/landsat-extension/1.0/schema.json" + ], + "collection": "collection-with" } \ No newline at end of file diff --git a/tests/data-files/commons/example-item-without-commons.json b/tests/data-files/commons/example-item-without-commons.json index b2e486b69..cc042d460 100644 --- a/tests/data-files/commons/example-item-without-commons.json +++ b/tests/data-files/commons/example-item-without-commons.json @@ -1,18 +1,15 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [ - "eo", - "view", - "https://example.com/stac/landsat-extension/1.0/schema.json" - ], - "id": "item-without", "type": "Feature", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], + "stac_version": "1.0.0-rc.3", + "id": "item-without", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "eo:cloud_cover": 78, + "view:sun_azimuth": 168.8989761, + "view:sun_elevation": 26.32596431, + "landsat:path": 107, + "landsat:row": 18 + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -40,123 +37,124 @@ ] ] }, - "collection": "collection-without", - "properties": { - "datetime": "2018-10-01T01:08:32.033Z", - "eo:cloud_cover": 78, - "view:sun_azimuth": 168.8989761, - "view:sun_elevation": 26.32596431, - "landsat:path": 107, - "landsat:row": 18 - }, + "links": [ + { + "rel": "parent", + "href": "./example-collection-without-commons.json" + }, + { + "rel": "root", + "href": "./example-collection-without-commons.json" + } + ], "assets": { "ANG": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt", - "title": "Angle coefficients file", - "type": "text/plain" + "type": "text/plain", + "title": "Angle coefficients file" }, "B1": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", "eo:bands": [ 0 - ], - "title": "Band 1 (coastal)" + ] }, "B2": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 2 (blue)", "eo:bands": [ 1 - ], - "title": "Band 2 (blue)" + ] }, "B3": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 3 (green)", "eo:bands": [ 2 - ], - "title": "Band 3 (green)" + ] }, "B4": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 4 (red)", "eo:bands": [ 3 - ], - "title": "Band 4 (red)" + ] }, "B5": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B5.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 5 (nir)", "eo:bands": [ 4 - ], - "title": "Band 5 (nir)" + ] }, "B6": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B6.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 6 (swir16)", "eo:bands": [ 5 - ], - "title": "Band 6 (swir16)" + ] }, "B7": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B7.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 7 (swir22)", "eo:bands": [ 6 - ], - "title": "Band 7 (swir22)" + ] }, "B8": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 8 (pan)", "eo:bands": [ 7 - ], - "title": "Band 8 (pan)" + ] }, "B9": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B9.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 9 (cirrus)", "eo:bands": [ 8 - ], - "title": "Band 9 (cirrus)" + ] }, "B10": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B10.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 10 (lwir)", "eo:bands": [ 9 - ], - "title": "Band 10 (lwir)" + ] }, "B11": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B11.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 11 (lwir)", "eo:bands": [ 10 - ], - "title": "Band 11 (lwir)" + ] }, "BQA": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_BQA.TIF", - "title": "Band quality data", - "type": "image/tiff; application=geotiff" + "type": "image/tiff; application=geotiff", + "title": "Band quality data" }, "MTL": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_MTL.txt", - "title": "original metadata file", - "type": "text/plain" + "type": "text/plain", + "title": "original metadata file" }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", - "title": "Thumbnail image", - "type": "image/jpeg" + "type": "image/jpeg", + "title": "Thumbnail image" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", @@ -164,14 +162,16 @@ "title": "HTML index page" } }, - "links": [ - { - "rel": "parent", - "href": "./example-collection-without-commons.json" - }, - { - "rel": "root", - "href": "./example-collection-without-commons.json" - } - ] + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://example.com/stac/landsat-extension/1.0/schema.json" + ], + "collection": "collection-without" } \ No newline at end of file diff --git a/tests/data-files/eo/eo-collection.json b/tests/data-files/eo/eo-collection.json index 181ecc4b4..0efb3cb36 100644 --- a/tests/data-files/eo/eo-collection.json +++ b/tests/data-files/eo/eo-collection.json @@ -1,154 +1,152 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [], - "id": "landsat-8-l1", - "title": "Landsat 8 L1", - "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", - "keywords": [ - "landsat" - ], - "extent": { - "spatial": { - "bbox": [ - [ - -180, - -90, - 180, - 90 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2013-06-01T00:00:00Z", - null - ] - ] - } + "type": "Collection", + "id": "landsat-8-l1", + "stac_version": "1.0.0-rc.3", + "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", + "links": [ + { + "rel": "parent", + "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" + }, + { + "rel": "root", + "href": "https://landsat-stac.s3.amazonaws.com/catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/paths/catalog.json" + } + ], + "stac_extensions": [], + "title": "Landsat 8 L1", + "keywords": [ + "landsat" + ], + "providers": [ + { + "name": "USGS", + "roles": [ + "producer", + "licensor" + ], + "url": "https://landsat.usgs.gov/" }, - "providers": [ - { - "name": "USGS", - "url": "https://landsat.usgs.gov/", - "roles": [ - "producer", - "licensor" - ] - }, - { - "name": "Planet Labs", - "url": "https://github.com/landsat-pds/landsat_ingestor", - "roles": [ - "processor" - ] - }, - { - "name": "AWS", - "url": "https://landsatonaws.com/", - "roles": [ - "host" - ] - }, - { - "name": "Development Seed", - "url": "https://developmentseed.org/", - "roles": [ - "processor" - ] - } + { + "name": "Planet Labs", + "roles": [ + "processor" + ], + "url": "https://github.com/landsat-pds/landsat_ingestor" + }, + { + "name": "AWS", + "roles": [ + "host" + ], + "url": "https://landsatonaws.com/" + }, + { + "name": "Development Seed", + "roles": [ + "processor" + ], + "url": "https://developmentseed.org/" + } + ], + "summaries": { + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 0.48, + "full_width_half_max": 0.06 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.06 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 0.65, + "full_width_half_max": 0.04 + }, + { + "name": "B5", + "common_name": "nir", + "center_wavelength": 0.86, + "full_width_half_max": 0.03 + }, + { + "name": "B6", + "common_name": "swir16", + "center_wavelength": 1.6, + "full_width_half_max": 0.08 + }, + { + "name": "B7", + "common_name": "swir22", + "center_wavelength": 2.2, + "full_width_half_max": 0.2 + }, + { + "name": "B8", + "common_name": "pan", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + }, + { + "name": "B9", + "common_name": "cirrus", + "center_wavelength": 1.37, + "full_width_half_max": 0.02 + }, + { + "name": "B10", + "common_name": "lwir11", + "center_wavelength": 10.9, + "full_width_half_max": 0.8 + }, + { + "name": "B11", + "common_name": "lwir12", + "center_wavelength": 12, + "full_width_half_max": 1 + } ], - "license": "PDDL-1.0", - "summaries": { - "eo:cloud_cover": { - "minimum": 0.0, - "maximum": 80.0 - }, - "eo:bands": [ - { - "name": "B1", - "common_name": "coastal", - "center_wavelength": 0.44, - "full_width_half_max": 0.02 - }, - { - "name": "B2", - "common_name": "blue", - "center_wavelength": 0.48, - "full_width_half_max": 0.06 - }, - { - "name": "B3", - "common_name": "green", - "center_wavelength": 0.56, - "full_width_half_max": 0.06 - }, - { - "name": "B4", - "common_name": "red", - "center_wavelength": 0.65, - "full_width_half_max": 0.04 - }, - { - "name": "B5", - "common_name": "nir", - "center_wavelength": 0.86, - "full_width_half_max": 0.03 - }, - { - "name": "B6", - "common_name": "swir16", - "center_wavelength": 1.6, - "full_width_half_max": 0.08 - }, - { - "name": "B7", - "common_name": "swir22", - "center_wavelength": 2.2, - "full_width_half_max": 0.2 - }, - { - "name": "B8", - "common_name": "pan", - "center_wavelength": 0.59, - "full_width_half_max": 0.18 - }, - { - "name": "B9", - "common_name": "cirrus", - "center_wavelength": 1.37, - "full_width_half_max": 0.02 - }, - { - "name": "B10", - "common_name": "lwir11", - "center_wavelength": 10.9, - "full_width_half_max": 0.8 - }, - { - "name": "B11", - "common_name": "lwir12", - "center_wavelength": 12, - "full_width_half_max": 1 - } + "eo:cloud_cover": { + "minimum": 0.0, + "maximum": 80.0 + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 ] + ] }, - "links": [ - { - "rel": "self", - "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" - }, - { - "rel": "parent", - "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" - }, - { - "rel": "root", - "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" - }, - { - "rel": "child", - "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/paths/catalog.json" - } - ] + "temporal": { + "interval": [ + [ + "2013-06-01T00:00:00Z", + null + ] + ] + } + }, + "license": "PDDL-1.0" } \ No newline at end of file diff --git a/tests/data-files/eo/eo-landsat-example.json b/tests/data-files/eo/eo-landsat-example.json index 06fdbccb5..824b42772 100644 --- a/tests/data-files/eo/eo-landsat-example.json +++ b/tests/data-files/eo/eo-landsat-example.json @@ -1,230 +1,235 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/eo/v1.0.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "LC08_L1TP_107018_20181001_20181001_01_RT", + "properties": { + "platform": "landsat-8", + "instrument": [ + "oli", + "tirs" ], - "id": "LC08_L1TP_107018_20181001_20181001_01_RT", - "collection": "landsat-8-l1", - "type": "Feature", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 152.52758, - 60.63437 - ], - [ - 149.1755, - 61.19016 - ], - [ - 148.13933, - 59.51584 - ], - [ - 151.33786, - 58.97792 - ], - [ - 152.52758, - 60.63437 - ] - ] + "datetime": "2018-10-01T01:08:32.033000Z", + "gsd": 30, + "view:sun_azimuth": 168.8989761, + "view:sun_elevation": 26.32596431, + "view:off_nadir": 0, + "landsat:path": 107, + "landsat:row": 18, + "eo:cloud_cover": 78 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 ] + ] + ] + }, + "links": [ + { + "rel": "collection", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json" + } + ], + "assets": { + "ANG": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt", + "type": "text/plain", + "title": "Angle coefficients file" }, - "properties": { - "platform": "landsat-8", - "instrument": [ - "oli", - "tirs" - ], - "datetime": "2018-10-01T01:08:32.033Z", - "gsd": 30, - "view:sun_azimuth": 168.8989761, - "view:sun_elevation": 26.32596431, - "view:off_nadir": 0, - "landsat:path": 107, - "landsat:row": 18, - "eo:cloud_cover": 78 + "B1": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + } + ] + }, + "B2": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 2 (blue)", + "eo:bands": [ + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 0.48, + "full_width_half_max": 0.06 + } + ] + }, + "B3": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 3 (green)", + "eo:bands": [ + { + "name": "B3", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.06 + } + ], + "eo:cloud_cover": 20 + }, + "B4": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 4 (red)", + "eo:bands": [ + { + "name": "B4", + "common_name": "red", + "center_wavelength": 0.65, + "full_width_half_max": 0.04 + } + ] + }, + "B5": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B5.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 5 (nir)", + "eo:bands": [ + { + "name": "B5", + "common_name": "nir", + "center_wavelength": 0.86, + "full_width_half_max": 0.03 + } + ] + }, + "B6": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B6.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 6 (swir16)", + "eo:bands": [ + { + "name": "B6", + "common_name": "swir16", + "center_wavelength": 1.6, + "full_width_half_max": 0.08 + } + ] + }, + "B7": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B7.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 7 (swir22)", + "eo:bands": [ + { + "name": "B7", + "common_name": "swir22", + "center_wavelength": 2.2, + "full_width_half_max": 0.2 + } + ] + }, + "B8": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 8 (pan)", + "eo:bands": [ + { + "name": "B8", + "common_name": "pan", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + } + ] }, - "assets": { - "ANG": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt", - "title": "Angle coefficients file", - "type": "text/plain" - }, - "B1": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B1", - "common_name": "coastal", - "center_wavelength": 0.44, - "full_width_half_max": 0.02 - } - ], - "title": "Band 1 (coastal)" - }, - "B2": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B2", - "common_name": "blue", - "center_wavelength": 0.48, - "full_width_half_max": 0.06 - } - ], - "title": "Band 2 (blue)" - }, - "B3": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B3", - "common_name": "green", - "center_wavelength": 0.56, - "full_width_half_max": 0.06 - } - ], - "title": "Band 3 (green)", - "eo:cloud_cover": 20 - }, - "B4": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B4", - "common_name": "red", - "center_wavelength": 0.65, - "full_width_half_max": 0.04 - } - ], - "title": "Band 4 (red)" - }, - "B5": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B5.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B5", - "common_name": "nir", - "center_wavelength": 0.86, - "full_width_half_max": 0.03 - } - ], - "title": "Band 5 (nir)" - }, - "B6": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B6.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B6", - "common_name": "swir16", - "center_wavelength": 1.6, - "full_width_half_max": 0.08 - } - ], - "title": "Band 6 (swir16)" - }, - "B7": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B7.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B7", - "common_name": "swir22", - "center_wavelength": 2.2, - "full_width_half_max": 0.2 - } - ], - "title": "Band 7 (swir22)" - }, - "B8": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B8", - "common_name": "pan", - "center_wavelength": 0.59, - "full_width_half_max": 0.18 - } - ], - "title": "Band 8 (pan)" - }, - "B9": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B9.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B9", - "common_name": "cirrus", - "center_wavelength": 1.37, - "full_width_half_max": 0.02 - } - ], - "title": "Band 9 (cirrus)" - }, - "B10": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B10.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B10", - "common_name": "lwir11", - "center_wavelength": 10.9, - "full_width_half_max": 0.8 - } - ], - "title": "Band 10 (lwir)" - }, - "B11": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B11.TIF", - "type": "image/tiff; application=geotiff", - "eo:bands": [ - { - "name": "B11", - "common_name": "lwir12", - "center_wavelength": 12, - "full_width_half_max": 1 - } - ], - "title": "Band 11 (lwir)" - }, - "BQA": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_BQA.TIF", - "title": "Band quality data", - "type": "image/tiff; application=geotiff" - }, - "MTL": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_MTL.txt", - "title": "original metadata file", - "type": "text/plain" - }, - "thumbnail": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", - "title": "Thumbnail image", - "type": "image/jpeg" - }, - "index": { - "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", - "type": "text/html", - "title": "HTML index page" + "B9": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B9.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 9 (cirrus)", + "eo:bands": [ + { + "name": "B9", + "common_name": "cirrus", + "center_wavelength": 1.37, + "full_width_half_max": 0.02 } + ] + }, + "B10": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B10.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 10 (lwir)", + "eo:bands": [ + { + "name": "B10", + "common_name": "lwir11", + "center_wavelength": 10.9, + "full_width_half_max": 0.8 + } + ] + }, + "B11": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B11.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 11 (lwir)", + "eo:bands": [ + { + "name": "B11", + "common_name": "lwir12", + "center_wavelength": 12, + "full_width_half_max": 1 + } + ] + }, + "BQA": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_BQA.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band quality data" + }, + "MTL": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_MTL.txt", + "type": "text/plain", + "title": "original metadata file" + }, + "thumbnail": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", + "type": "image/jpeg", + "title": "Thumbnail image" }, - "links": [] -} + "index": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", + "type": "text/html", + "title": "HTML index page" + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "collection": "landsat-8-l1" +} \ No newline at end of file diff --git a/tests/data-files/eo/sample-bands-in-item-properties.json b/tests/data-files/eo/sample-bands-in-item-properties.json index c3917e4c2..a4c7e0dce 100644 --- a/tests/data-files/eo/sample-bands-in-item-properties.json +++ b/tests/data-files/eo/sample-bands-in-item-properties.json @@ -1,24 +1,7 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/eo/v1.0.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json" - ], "type": "Feature", - "id" : "CS3-20160503_132131_05", - "bbox": [-122.59750209, 37.48803556, -122.2880486, 37.613537207], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [-122.308150179, 37.488035566], - [-122.597502109, 37.538869539], - [-122.576687533, 37.613537207], - [-122.288048600, 37.562818007], - [-122.308150179, 37.488035566] - ] - ] - }, + "stac_version": "1.0.0-rc.3", + "id": "CS3-20160503_132131_05", "properties": { "datetime": "2016-05-03T13:22:30Z", "title": "A CS3 item", @@ -39,7 +22,9 @@ "eo:cloud_cover": 0.12, "view:off_nadir": 1.4, "platform": "coolsat2", - "instruments": ["cool_sensor_v1"], + "instruments": [ + "cool_sensor_v1" + ], "eo:bands": [ { "name": "band1" @@ -58,16 +43,51 @@ "gsd": 0.512, "cs:type": "scene", "cs:anomalous_pixels": 0.14, - "cs:earth_sun_distance": 1.0141560, + "cs:earth_sun_distance": 1.014156, "cs:sat_id": "CS3", "cs:product_level": "LV1B" }, - "collection": "CS3", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.2880486, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, "links": [ - {"rel": "self", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.json"}, - {"rel": "parent", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json"}, - {"rel": "collection", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json"}, - {"rel": "alternate", "type": "text/html", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html"} + { + "rel": "parent", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json" + }, + { + "rel": "collection", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json" + }, + { + "rel": "alternate", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html", + "type": "text/html" + } ], "assets": { "analytic": { @@ -90,9 +110,11 @@ }, "thumbnail": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", - "title": "Thumbnail", "type": "image/png", - "roles": [ "thumbnail" ] + "title": "Thumbnail", + "roles": [ + "thumbnail" + ] }, "udm": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/UDM.tif", @@ -100,14 +122,26 @@ }, "json-metadata": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/extended-metadata.json", - "title": "Extended Metadata", "type": "application/json", - "roles": [ "metadata" ] + "title": "Extended Metadata", + "roles": [ + "metadata" + ] }, "ephemeris": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/S3-20160503_132130_04.EPH", "title": "Satellite Ephemeris Metadata" } - } - -} + }, + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "collection": "CS3" +} \ No newline at end of file diff --git a/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json b/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json index 7d965234e..1551ddea1 100644 --- a/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json +++ b/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json @@ -1 +1,216 @@ -{"type": "Feature", "id": "LC81560292015001LGN00", "bbox": [65.61297, 43.48724, 68.67079, 45.70204], "geometry": {"type": "Polygon", "coordinates": [[[65.73955965027932, 44.023633020813534], [65.7391, 44.0237], [65.75225096942373, 44.06283985206589], [65.76401104029125, 44.09784011852602], [66.21827897598023, 45.449830167949095], [66.2307828440451, 45.48704412005725], [66.2432, 45.524], [66.24367565592954, 45.5239306566934], [68.5514, 45.1875], [68.53817435040268, 45.152263090715735], [68.00329653164782, 43.72719575931885], [67.9914, 43.6955], [65.73955965027932, 44.023633020813534]]]}, "properties": {"collection": "landsat-8-l1", "datetime": "2015-01-01T06:16:17.836237+00:00", "eo:sun_azimuth": 160.8078976, "eo:sun_elevation": 20.22442041, "eo:cloud_cover": 94, "eo:row": "029", "eo:column": "156", "landsat:product_id": null, "landsat:scene_id": "LC81560292015001LGN00", "landsat:processing_level": "L1GT", "landsat:tier": "pre-collection", "eo:epsg": 32642}, "assets": {"index": {"type": "text/html", "title": "HTML index page", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/index.html"}, "thumbnail": {"title": "Thumbnail image", "type": "image/jpeg", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_thumb_large.jpg"}, "B1": {"type": "image/x.geotiff", "eo:bands": [0], "title": "Band 1 (coastal)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B1.TIF"}, "B2": {"type": "image/x.geotiff", "eo:bands": [1], "title": "Band 2 (blue)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B2.TIF"}, "B3": {"type": "image/x.geotiff", "eo:bands": [2], "title": "Band 3 (green)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B3.TIF"}, "B4": {"type": "image/x.geotiff", "eo:bands": [3], "title": "Band 4 (red)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B4.TIF"}, "B5": {"type": "image/x.geotiff", "eo:bands": [4], "title": "Band 5 (nir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B5.TIF"}, "B6": {"type": "image/x.geotiff", "eo:bands": [5], "title": "Band 6 (swir16)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B6.TIF"}, "B7": {"type": "image/x.geotiff", "eo:bands": [6], "title": "Band 7 (swir22)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B7.TIF"}, "B8": {"type": "image/x.geotiff", "eo:bands": [7], "title": "Band 8 (pan)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B8.TIF"}, "B9": {"type": "image/x.geotiff", "eo:bands": [8], "title": "Band 9 (cirrus)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B9.TIF"}, "B10": {"type": "image/x.geotiff", "eo:bands": [9], "title": "Band 10 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B10.TIF"}, "B11": {"type": "image/x.geotiff", "eo:bands": [10], "title": "Band 11 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B11.TIF"}, "ANG": {"title": "Angle coefficients file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_ANG.txt"}, "MTL": {"title": "original metadata file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_MTL.txt"}, "BQA": {"title": "Band quality data", "type": "image/x.geotiff", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_BQA.TIF"}}, "links": [{"rel": "self", "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/156/029/2015-01-01/LC81560292015001LGN00.json"}, {"rel": "root", "href": "../../../../catalog.json"}, {"rel": "parent", "href": "../catalog.json"}, {"rel": "collection", "href": "../../catalog.json"}]} +{ + "type": "Feature", + "id": "LC81560292015001LGN00", + "bbox": [ + 65.61297, + 43.48724, + 68.67079, + 45.70204 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 65.73955965027932, + 44.023633020813534 + ], + [ + 65.7391, + 44.0237 + ], + [ + 65.75225096942373, + 44.06283985206589 + ], + [ + 65.76401104029125, + 44.09784011852602 + ], + [ + 66.21827897598023, + 45.449830167949095 + ], + [ + 66.2307828440451, + 45.48704412005725 + ], + [ + 66.2432, + 45.524 + ], + [ + 66.24367565592954, + 45.5239306566934 + ], + [ + 68.5514, + 45.1875 + ], + [ + 68.53817435040268, + 45.152263090715735 + ], + [ + 68.00329653164782, + 43.72719575931885 + ], + [ + 67.9914, + 43.6955 + ], + [ + 65.73955965027932, + 44.023633020813534 + ] + ] + ] + }, + "properties": { + "collection": "landsat-8-l1", + "datetime": "2015-01-01T06:16:17.836237+00:00", + "eo:sun_azimuth": 160.8078976, + "eo:sun_elevation": 20.22442041, + "eo:cloud_cover": 94, + "eo:row": "029", + "eo:column": "156", + "landsat:product_id": null, + "landsat:scene_id": "LC81560292015001LGN00", + "landsat:processing_level": "L1GT", + "landsat:tier": "pre-collection", + "eo:epsg": 32642 + }, + "assets": { + "index": { + "type": "text/html", + "title": "HTML index page", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/index.html" + }, + "thumbnail": { + "title": "Thumbnail image", + "type": "image/jpeg", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_thumb_large.jpg" + }, + "B1": { + "type": "image/x.geotiff", + "eo:bands": [ + 0 + ], + "title": "Band 1 (coastal)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B1.TIF" + }, + "B2": { + "type": "image/x.geotiff", + "eo:bands": [ + 1 + ], + "title": "Band 2 (blue)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B2.TIF" + }, + "B3": { + "type": "image/x.geotiff", + "eo:bands": [ + 2 + ], + "title": "Band 3 (green)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B3.TIF" + }, + "B4": { + "type": "image/x.geotiff", + "eo:bands": [ + 3 + ], + "title": "Band 4 (red)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B4.TIF" + }, + "B5": { + "type": "image/x.geotiff", + "eo:bands": [ + 4 + ], + "title": "Band 5 (nir)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B5.TIF" + }, + "B6": { + "type": "image/x.geotiff", + "eo:bands": [ + 5 + ], + "title": "Band 6 (swir16)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B6.TIF" + }, + "B7": { + "type": "image/x.geotiff", + "eo:bands": [ + 6 + ], + "title": "Band 7 (swir22)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B7.TIF" + }, + "B8": { + "type": "image/x.geotiff", + "eo:bands": [ + 7 + ], + "title": "Band 8 (pan)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B8.TIF" + }, + "B9": { + "type": "image/x.geotiff", + "eo:bands": [ + 8 + ], + "title": "Band 9 (cirrus)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B9.TIF" + }, + "B10": { + "type": "image/x.geotiff", + "eo:bands": [ + 9 + ], + "title": "Band 10 (lwir)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B10.TIF" + }, + "B11": { + "type": "image/x.geotiff", + "eo:bands": [ + 10 + ], + "title": "Band 11 (lwir)", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_B11.TIF" + }, + "ANG": { + "title": "Angle coefficients file", + "type": "text/plain", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_ANG.txt" + }, + "MTL": { + "title": "original metadata file", + "type": "text/plain", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_MTL.txt" + }, + "BQA": { + "title": "Band quality data", + "type": "image/x.geotiff", + "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/L8/156/029/LC81560292015001LGN00/LC81560292015001LGN00_BQA.TIF" + } + }, + "links": [ + { + "rel": "self", + "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/156/029/2015-01-01/LC81560292015001LGN00.json" + }, + { + "rel": "root", + "href": "../../../../catalog.json" + }, + { + "rel": "parent", + "href": "../catalog.json" + }, + { + "rel": "collection", + "href": "../../catalog.json" + } + ] +} \ No newline at end of file diff --git a/tests/data-files/file/file-example.json b/tests/data-files/file/file-example.json index 4b107ac41..76b37e303 100644 --- a/tests/data-files/file/file-example.json +++ b/tests/data-files/file/file-example.json @@ -1,16 +1,10 @@ { - "id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616", "type": "Feature", - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/file/v1.0.0/schema.json" - ], - "bbox": [ - -70.275032, - -64.72924, - -65.087479, - -51.105831 - ], + "stac_version": "1.0.0-rc.3", + "id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616", + "properties": { + "datetime": "2018-11-03T23:58:55Z" + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -38,32 +32,30 @@ ] ] }, - "properties": { - "datetime": "2018-11-03T23:58:55Z" - }, + "links": [], "assets": { "noises": { "href": "./annotation/calibration/noise-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", - "title": "Calibration Schema", "type": "text/xml", + "title": "Calibration Schema", "file:checksum": "90e40210a30d1711e81a4b11ef67b28744321659" }, "calibrations": { "href": "./annotation/calibration/calibration-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", - "title": "Noise Schema", "type": "text/xml", + "title": "Noise Schema", "file:checksum": "90e402104fc5351af67db0b8f1746efe421a05e4" }, "products": { "href": "./annotation/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", - "title": "Product Schema", "type": "text/xml", + "title": "Product Schema", "file:checksum": "90e402107a7f2588a85362b9beea2a12d4514d45" }, "measurement": { "href": "./measurement/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.tiff", - "title": "Measurements", "type": "image/tiff", + "title": "Measurements", "file:byte_order": "little-endian", "file:data_type": "uint16", "file:size": 209715200, @@ -72,8 +64,8 @@ }, "thumbnail": { "href": "./preview/quick-look.png", - "title": "Thumbnail", "type": "image/png", + "title": "Thumbnail", "file:byte_order": "big-endian", "file:data_type": "uint8", "file:size": 146484, @@ -81,5 +73,13 @@ "file:nodata": [] } }, - "links": [] + "bbox": [ + -70.275032, + -64.72924, + -65.087479, + -51.105831 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/file/v1.0.0/schema.json" + ] } \ No newline at end of file diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index c95723957..6c640e7ab 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -7,7 +7,7 @@ import json from tempfile import TemporaryDirectory from subprocess import call -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from urllib.error import HTTPError import pystac as ps @@ -15,7 +15,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: - links: List[Dict[str, Any]] = js.get('links') + links: Optional[List[Dict[str, Any]]] = js.get('links') if links is not None: filtered_links: List[Dict[str, Any]] = [] for link in links: diff --git a/tests/data-files/invalid/shared-id/catalog.json b/tests/data-files/invalid/shared-id/catalog.json index 0fd03d984..ac8b6bb6b 100644 --- a/tests/data-files/invalid/shared-id/catalog.json +++ b/tests/data-files/invalid/shared-id/catalog.json @@ -1,17 +1,19 @@ { - "id": "test", - "stac_version": "1.0.0-beta.2", - "description": "test desc", - "links": [ - { - "rel": "root", - "href": "./catalog.json", - "type": "application/json" - }, - { - "rel": "child", - "href": "./test/collection.json", - "type": "application/json" - } - ] + "type": "Catalog", + "id": "test", + "stac_version": "1.0.0-rc.3", + "description": "test desc", + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./test/collection.json", + "type": "application/json" + } + ], + "stac_extensions": [] } \ No newline at end of file diff --git a/tests/data-files/invalid/shared-id/test/collection.json b/tests/data-files/invalid/shared-id/test/collection.json index 03455d70e..cbd2a55d9 100644 --- a/tests/data-files/invalid/shared-id/test/collection.json +++ b/tests/data-files/invalid/shared-id/test/collection.json @@ -1,43 +1,45 @@ { - "id": "test", - "stac_version": "1.0.0-beta.2", - "description": "test desc", - "links": [ - { - "rel": "root", - "href": "../catalog.json", - "type": "application/json" - }, - { - "rel": "item", - "href": "./test-item-1/test-item-1.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" - } - ], - "extent": { - "spatial": { - "bbox": [ - [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2020-03-14T16:32:00Z", - null - ] - ] - } + "type": "Collection", + "id": "test", + "stac_version": "1.0.0-rc.3", + "description": "test desc", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" }, - "license": "proprietary" + { + "rel": "item", + "href": "./test-item-1/test-item-1.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [], + "extent": { + "spatial": { + "bbox": [ + [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-03-14T16:32:00Z", + null + ] + ] + } + }, + "license": "proprietary" } \ No newline at end of file diff --git a/tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json b/tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json index 5afe56e3d..99a057ba4 100644 --- a/tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json +++ b/tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json @@ -1,60 +1,61 @@ { - "type": "Feature", - "stac_version": "1.0.0-beta.2", - "id": "test-item-1", - "properties": { - "datetime": "2020-03-14T16:32:00Z" - }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -2.5048828125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 3.8916575492899987 - ], - [ - -1.9610595703125, - 4.275202171119132 - ], - [ - -2.5048828125, - 4.275202171119132 - ], - [ - -2.5048828125, - 3.8916575492899987 - ] - ] + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "test-item-1", + "properties": { + "datetime": "2020-03-14T16:32:00Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.5048828125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 3.8916575492899987 + ], + [ + -1.9610595703125, + 4.275202171119132 + ], + [ + -2.5048828125, + 4.275202171119132 + ], + [ + -2.5048828125, + 3.8916575492899987 ] + ] + ] + }, + "links": [ + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" }, - "links": [ - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json" - }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json" - } - ], - "assets": {}, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], - "collection": "test" + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": {}, + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], + "stac_extensions": [], + "collection": "test" } \ No newline at end of file diff --git a/tests/data-files/item/sample-item-asset-properties.json b/tests/data-files/item/sample-item-asset-properties.json index 0d703c7ef..21dea398b 100644 --- a/tests/data-files/item/sample-item-asset-properties.json +++ b/tests/data-files/item/sample-item-asset-properties.json @@ -1,14 +1,32 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", "id": "CS3-20160503_132131_05", - "bbox": [ - -122.59750209, - 37.48803556, - -122.2880486, - 37.613537207 - ], + "properties": { + "datetime": "2016-05-03T13:22:30.040000Z", + "title": "A CS3 item", + "license": "PDDL-1.0", + "providers": [ + { + "name": "CoolSat", + "roles": [ + "producer", + "licensor" + ], + "url": "https://cool-sat.com/" + } + ], + "gsd": 30, + "platform": "political", + "instruments": [ + "guitar", + "violin" + ], + "constellation": "big dipper", + "mission": "impossible", + "created": "2017-03-17T13:22:30.040Z", + "updated": "2017-03-18T13:22:30.040Z" + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -36,34 +54,7 @@ ] ] }, - "properties": { - "datetime": "2016-05-03T13:22:30.040Z", - "title": "A CS3 item", - "license": "PDDL-1.0", - "providers": [ - { - "name": "CoolSat", - "roles": [ - "producer", - "licensor" - ], - "url": "https://cool-sat.com/" - } - ], - "gsd": 30, - "platform": "political", - "instruments": ["guitar", "violin"], - "constellation": "big dipper", - "mission": "impossible", - "created": "2017-03-17T13:22:30.040Z", - "updated": "2017-03-18T13:22:30.040Z" - }, - "collection": "CS3", "links": [ - { - "rel": "self", - "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.json" - }, { "rel": "collection", "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.1/collection-spec/examples/sentinel2.json" @@ -85,21 +76,31 @@ "license": "CC-BY-4.0", "providers": [ { - "name": "USGS", - "url": "https://landsat.usgs.gov/", - "roles": [ - "producer", - "licensor" - ] + "name": "USGS", + "url": "https://landsat.usgs.gov/", + "roles": [ + "producer", + "licensor" + ] } ], "gsd": 40, "platform": "shoes", - "instruments": ["caliper"], + "instruments": [ + "caliper" + ], "constellation": "little dipper", "mission": "possible", "created": "2017-05-17T13:22:30.040Z", "updated": "2017-05-18T13:22:30.040Z" } - } -} + }, + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "stac_extensions": [], + "collection": "CS3" +} \ No newline at end of file diff --git a/tests/data-files/item/sample-item.json b/tests/data-files/item/sample-item.json index 64c6468de..f359fc443 100644 --- a/tests/data-files/item/sample-item.json +++ b/tests/data-files/item/sample-item.json @@ -1,14 +1,22 @@ { - "stac_version": "1.0.0-beta.2", - "stac_extensions": [], "type": "Feature", + "stac_version": "1.0.0-rc.3", "id": "CS3-20160503_132131_05", - "bbox": [ - -122.59750209, - 37.48803556, - -122.2880486, - 37.613537207 - ], + "properties": { + "datetime": "2016-05-03T13:22:30.040000Z", + "title": "A CS3 item", + "license": "PDDL-1.0", + "providers": [ + { + "name": "CoolSat", + "roles": [ + "producer", + "licensor" + ], + "url": "https://cool-sat.com/" + } + ] + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -36,22 +44,6 @@ ] ] }, - "properties": { - "datetime": "2016-05-03T13:22:30.040Z", - "title": "A CS3 item", - "license": "PDDL-1.0", - "providers": [ - { - "name": "CoolSat", - "roles": [ - "producer", - "licensor" - ], - "url": "https://cool-sat.com/" - } - ] - }, - "collection": "CS3", "links": [ { "rel": "self", @@ -72,5 +64,13 @@ "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", "title": "Thumbnail" } - } -} + }, + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "stac_extensions": [], + "collection": "CS3" +} \ No newline at end of file diff --git a/tests/data-files/label/label-example-1.json b/tests/data-files/label/label-example-1.json index 366907823..81e96a532 100644 --- a/tests/data-files/label/label-example-1.json +++ b/tests/data-files/label/label-example-1.json @@ -1,9 +1,9 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "label-example-1-label-item", "properties": { - "datetime": "2019-10-04 18:55:37Z", + "datetime": "2019-10-04T18:55:37Z", "label:description": "labels for area-1-1", "label:type": "vector", "label:properties": [ @@ -86,12 +86,6 @@ ] ] }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], "links": [ { "rel": "source", @@ -105,7 +99,13 @@ "type": "application/geo+json" } }, + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], "stac_extensions": [ - "label" + "https://stac-extensions.github.io/label/v1.0.0/schema.json" ] } \ No newline at end of file diff --git a/tests/data-files/label/label-example-2.json b/tests/data-files/label/label-example-2.json index 5d46e1e2b..cd4a9ece4 100644 --- a/tests/data-files/label/label-example-2.json +++ b/tests/data-files/label/label-example-2.json @@ -1,9 +1,9 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0-rc.3", "id": "label-example-1-label-item", "properties": { - "datetime": "2019-10-04 18:55:37Z", + "datetime": "2019-10-04T18:55:37Z", "label:description": "labels for area-1-1", "label:type": "raster", "label:properties": null, @@ -79,12 +79,6 @@ ] ] }, - "bbox": [ - -2.5048828125, - 3.8916575492899987, - -1.9610595703125, - 3.8916575492899987 - ], "links": [ { "rel": "source", @@ -98,7 +92,13 @@ "type": "image/tiff; application=geotiff" } }, + "bbox": [ + -2.5048828125, + 3.8916575492899987, + -1.9610595703125, + 3.8916575492899987 + ], "stac_extensions": [ - "label" + "https://stac-extensions.github.io/label/v1.0.0/schema.json" ] } \ No newline at end of file diff --git a/tests/data-files/pointcloud/example-laz-no-statistics.json b/tests/data-files/pointcloud/example-laz-no-statistics.json index 989a78a15..89f5b948a 100644 --- a/tests/data-files/pointcloud/example-laz-no-statistics.json +++ b/tests/data-files/pointcloud/example-laz-no-statistics.json @@ -1,140 +1,135 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "autzen-full.laz", + "properties": { + "datetime": "2013-07-17T00:00:00Z", + "pc:count": 10653336, + "pc:density": 0, + "pc:encoding": "LASzip", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } ], - "assets": {}, - "bbox": [ - -123.0755422, - 44.04971882, - 123.791472, - -123.0619599, - 44.06278031, - 187.531248 - ], - "geometry": { - "coordinates": [ - [ - [ - -123.07498674, - 44.04971882 - ], - [ - -123.07554223, - 44.06248623 - ], - [ - -123.0625126, - 44.06278031 - ], - [ - -123.06195992, - 44.05001283 - ], - [ - -123.07498674, - 44.04971882 - ] - ] + "pc:type": "lidar", + "title": "USGS 3DEP LiDAR" + }, + "geometry": { + "coordinates": [ + [ + [ + -123.07498674, + 44.04971882 ], - "type": "Polygon" - }, - "id": "autzen-full.laz", - "links": [ - { - "href": "/Users/hobu/dev/git/pdal/test/data/autzen/autzen-full.laz", - "rel": "self" - } - ], - "properties": { - "datetime": "2013-07-17T00:00:00Z", - "pc:count": 10653336, - "pc:density": 0, - "pc:encoding": "LASzip", - "pc:schemas": [ - { - "name": "X", - "size": 8, - "type": "floating" - }, - { - "name": "Y", - "size": 8, - "type": "floating" - }, - { - "name": "Z", - "size": 8, - "type": "floating" - }, - { - "name": "Intensity", - "size": 2, - "type": "unsigned" - }, - { - "name": "ReturnNumber", - "size": 1, - "type": "unsigned" - }, - { - "name": "NumberOfReturns", - "size": 1, - "type": "unsigned" - }, - { - "name": "ScanDirectionFlag", - "size": 1, - "type": "unsigned" - }, - { - "name": "EdgeOfFlightLine", - "size": 1, - "type": "unsigned" - }, - { - "name": "Classification", - "size": 1, - "type": "unsigned" - }, - { - "name": "ScanAngleRank", - "size": 4, - "type": "floating" - }, - { - "name": "UserData", - "size": 1, - "type": "unsigned" - }, - { - "name": "PointSourceId", - "size": 2, - "type": "unsigned" - }, - { - "name": "GpsTime", - "size": 8, - "type": "floating" - }, - { - "name": "Red", - "size": 2, - "type": "unsigned" - }, - { - "name": "Green", - "size": 2, - "type": "unsigned" - }, - { - "name": "Blue", - "size": 2, - "type": "unsigned" - } + [ + -123.07554223, + 44.06248623 + ], + [ + -123.0625126, + 44.06278031 ], - "pc:type": "lidar", - "title": "USGS 3DEP LiDAR" - }, - "type": "Feature" + [ + -123.06195992, + 44.05001283 + ], + [ + -123.07498674, + 44.04971882 + ] + ] + ], + "type": "Polygon" + }, + "links": [], + "assets": {}, + "bbox": [ + -123.0755422, + 44.04971882, + 123.791472, + -123.0619599, + 44.06278031, + 187.531248 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" + ] } \ No newline at end of file diff --git a/tests/data-files/pointcloud/example-laz.json b/tests/data-files/pointcloud/example-laz.json index f9a5cdf38..c50eeebf6 100644 --- a/tests/data-files/pointcloud/example-laz.json +++ b/tests/data-files/pointcloud/example-laz.json @@ -1,51 +1,7 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" - ], - "assets": {}, - "bbox": [ - -123.0755422, - 44.04971882, - 123.791472, - -123.0619599, - 44.06278031, - 187.531248 - ], - "geometry": { - "coordinates": [ - [ - [ - -123.07498674, - 44.04971882 - ], - [ - -123.07554223, - 44.06248623 - ], - [ - -123.0625126, - 44.06278031 - ], - [ - -123.06195992, - 44.05001283 - ], - [ - -123.07498674, - 44.04971882 - ] - ] - ], - "type": "Polygon" - }, + "type": "Feature", + "stac_version": "1.0.0-rc.3", "id": "autzen-full.laz", - "links": [ - { - "href": "/Users/hobu/dev/git/pdal/test/data/autzen/autzen-full.laz", - "rel": "self" - } - ], "properties": { "datetime": "2013-07-17T00:00:00Z", "pc:count": 10653336, @@ -298,5 +254,44 @@ "pc:type": "lidar", "title": "USGS 3DEP LiDAR" }, - "type": "Feature" -} + "geometry": { + "coordinates": [ + [ + [ + -123.07498674, + 44.04971882 + ], + [ + -123.07554223, + 44.06248623 + ], + [ + -123.0625126, + 44.06278031 + ], + [ + -123.06195992, + 44.05001283 + ], + [ + -123.07498674, + 44.04971882 + ] + ] + ], + "type": "Polygon" + }, + "links": [], + "assets": {}, + "bbox": [ + -123.0755422, + 44.04971882, + 123.791472, + -123.0619599, + 44.06278031, + 187.531248 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" + ] +} \ No newline at end of file diff --git a/tests/data-files/projection/example-landsat8.json b/tests/data-files/projection/example-landsat8.json index 2b060dc93..21f1e6a2e 100644 --- a/tests/data-files/projection/example-landsat8.json +++ b/tests/data-files/projection/example-landsat8.json @@ -1,18 +1,187 @@ { - "stac_version": "1.0.0-rc.2", - "stac_extensions": [ - "https://stac-extensions.github.io/eo/v1.0.0/schema.json", - "https://stac-extensions.github.io/projection/v1.0.0/schema.json" - ], - "id": "LC81530252014153LGN00", "type": "Feature", - "collection": "landsat-8-l1", - "bbox": [ - 148.13933, - 59.51584, - 152.52758, - 60.63437 - ], + "stac_version": "1.0.0-rc.3", + "id": "LC81530252014153LGN00", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "proj:epsg": 32614, + "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "proj:projjson": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 14N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "conversion": { + "name": "UTM zone 14N", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": -99, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 0.9996, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 500000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "area": "World - N hemisphere - 102\u00b0W to 96\u00b0W - by country", + "bbox": { + "south_latitude": 0, + "west_longitude": -102, + "north_latitude": 84, + "east_longitude": -96 + }, + "id": { + "authority": "EPSG", + "code": 32614 + } + }, + "proj:geometry": { + "coordinates": [ + [ + [ + 169200.0, + 3712800.0 + ], + [ + 403200.0, + 3712800.0 + ], + [ + 403200.0, + 3951000.0 + ], + [ + 169200.0, + 3951000.0 + ], + [ + 169200.0, + 3712800.0 + ] + ] + ], + "type": "Polygon" + }, + "proj:bbox": [ + 169200.0, + 3712800.0, + 403200.0, + 3951000.0 + ], + "proj:centroid": { + "lat": 34.595302781575604, + "lon": -101.34448382627504 + }, + "proj:shape": [ + 8391, + 8311 + ], + "proj:transform": [ + 30.0, + 0.0, + 224985.0, + 0.0, + -30.0, + 6790215.0, + 0.0, + 0.0, + 1.0 + ] + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -40,10 +209,17 @@ ] ] }, + "links": [ + { + "rel": "collection", + "href": "https://example.com/landsat/collection.json" + } + ], "assets": { "B1": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", "eo:bands": [ { "name": "B1", @@ -51,12 +227,12 @@ "center_wavelength": 0.44, "full_width_half_max": 0.02 } - ], - "title": "Band 1 (coastal)" + ] }, "B8": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", "type": "image/tiff; application=geotiff", + "title": "Band 8 (panchromatic)", "eo:bands": [ { "name": "B8", @@ -64,7 +240,6 @@ "full_width_half_max": 0.18 } ], - "title": "Band 8 (panchromatic)", "proj:epsg": 9999, "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",TEST_TEXT]]", "proj:projjson": { @@ -244,190 +419,15 @@ ] } }, - "properties": { - "datetime": "2018-10-01T01:08:32.033Z", - "proj:epsg": 32614, - "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", - "proj:projjson": { - "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", - "type": "ProjectedCRS", - "name": "WGS 84 / UTM zone 14N", - "base_crs": { - "name": "WGS 84", - "datum": { - "type": "GeodeticReferenceFrame", - "name": "World Geodetic System 1984", - "ellipsoid": { - "name": "WGS 84", - "semi_major_axis": 6378137, - "inverse_flattening": 298.257223563 - } - }, - "coordinate_system": { - "subtype": "ellipsoidal", - "axis": [ - { - "name": "Geodetic latitude", - "abbreviation": "Lat", - "direction": "north", - "unit": "degree" - }, - { - "name": "Geodetic longitude", - "abbreviation": "Lon", - "direction": "east", - "unit": "degree" - } - ] - }, - "id": { - "authority": "EPSG", - "code": 4326 - } - }, - "conversion": { - "name": "UTM zone 14N", - "method": { - "name": "Transverse Mercator", - "id": { - "authority": "EPSG", - "code": 9807 - } - }, - "parameters": [ - { - "name": "Latitude of natural origin", - "value": 0, - "unit": "degree", - "id": { - "authority": "EPSG", - "code": 8801 - } - }, - { - "name": "Longitude of natural origin", - "value": -99, - "unit": "degree", - "id": { - "authority": "EPSG", - "code": 8802 - } - }, - { - "name": "Scale factor at natural origin", - "value": 0.9996, - "unit": "unity", - "id": { - "authority": "EPSG", - "code": 8805 - } - }, - { - "name": "False easting", - "value": 500000, - "unit": "metre", - "id": { - "authority": "EPSG", - "code": 8806 - } - }, - { - "name": "False northing", - "value": 0, - "unit": "metre", - "id": { - "authority": "EPSG", - "code": 8807 - } - } - ] - }, - "coordinate_system": { - "subtype": "Cartesian", - "axis": [ - { - "name": "Easting", - "abbreviation": "E", - "direction": "east", - "unit": "metre" - }, - { - "name": "Northing", - "abbreviation": "N", - "direction": "north", - "unit": "metre" - } - ] - }, - "area": "World - N hemisphere - 102\u00b0W to 96\u00b0W - by country", - "bbox": { - "south_latitude": 0, - "west_longitude": -102, - "north_latitude": 84, - "east_longitude": -96 - }, - "id": { - "authority": "EPSG", - "code": 32614 - } - }, - "proj:geometry": { - "coordinates": [ - [ - [ - 169200.0, - 3712800.0 - ], - [ - 403200.0, - 3712800.0 - ], - [ - 403200.0, - 3951000.0 - ], - [ - 169200.0, - 3951000.0 - ], - [ - 169200.0, - 3712800.0 - ] - ] - ], - "type": "Polygon" - }, - "proj:bbox": [ - 169200.0, - 3712800.0, - 403200.0, - 3951000.0 - ], - "proj:centroid": { - "lat": 34.595302781575604, - "lon": -101.34448382627504 - }, - "proj:shape": [ - 8391, - 8311 - ], - "proj:transform": [ - 30.0, - 0.0, - 224985.0, - 0.0, - -30.0, - 6790215.0, - 0.0, - 0.0, - 1.0 - ] - }, - "links": [ - { - "rel": "self", - "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" - } - ] -} + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" + ], + "collection": "landsat-8-l1" +} \ No newline at end of file diff --git a/tests/data-files/timestamps/example-landsat8.json b/tests/data-files/timestamps/example-landsat8.json index bb19b5265..17c1f0a18 100644 --- a/tests/data-files/timestamps/example-landsat8.json +++ b/tests/data-files/timestamps/example-landsat8.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0-rc.3", "id": "LC08_L1TP_107018_20181001", "properties": { "datetime": "2018-10-01T01:08:32Z", @@ -42,12 +42,7 @@ ] ] }, - "links": [ - { - "rel": "self", - "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" - } - ], + "links": [], "assets": { "blue": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", diff --git a/tests/data-files/view/example-landsat8.json b/tests/data-files/view/example-landsat8.json index 371bb523e..fe737de02 100644 --- a/tests/data-files/view/example-landsat8.json +++ b/tests/data-files/view/example-landsat8.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0-rc.3", "id": "LC08_L1TP_107018_20181001", "properties": { "datetime": "2018-10-01T01:08:32.033000Z", @@ -89,8 +89,8 @@ 60.63437 ], "stac_extensions": [ - "view", - "https://schemas.stacspec.org/v1.0.0-beta.2/extensions/sat/json-schema/schema.json" + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json" ], "collection": "landsat-8-l1" } \ No newline at end of file From 7438ee889c34355e8c2d78b9a8613c0f75a9a8d3 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:24:55 -0400 Subject: [PATCH 24/51] Add datacube extension --- pystac/extensions/datacube.py | 358 ++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 pystac/extensions/datacube.py diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py new file mode 100644 index 000000000..045ebc861 --- /dev/null +++ b/pystac/extensions/datacube.py @@ -0,0 +1,358 @@ +from abc import ABC +from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast + +import pystac as ps +from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks +from pystac.utils import get_required, map_opt + +T = TypeVar('T', ps.Collection, ps.Item, ps.Asset) + +SCHEMA_URI = "https://stac-extensions.github.io/datacube/v1.0.0/schema.json" + +DIMENSIONS_PROP = "cube:dimensions" + +# Dimension properties +DIM_TYPE_PROP = "type" +DIM_DESC_PROP = "description" +DIM_AXIS_PROP = "axis" +DIM_EXTENT_PROP = "extent" +DIM_VALUES_PROP = "values" +DIM_STEP_PROP = "step" +DIM_REF_SYS_PROP = "reference_system" +DIM_UNIT_PROP = "unit" + + +class Dimension(ABC): + def __init__(self, properties: Dict[str, Any]) -> None: + self.properties = properties + + @property + def dim_type(self) -> str: + return get_required(self.properties.get(DIM_TYPE_PROP), "cube:dimension", DIM_TYPE_PROP) + + @dim_type.setter + def dim_type(self, v: str) -> None: + self.properties[DIM_TYPE_PROP] = v + + @property + def description(self) -> Optional[str]: + return self.properties.get(DIM_DESC_PROP) + + @description.setter + def description(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(DIM_DESC_PROP, None) + else: + self.properties[DIM_DESC_PROP] = v + + def to_dict(self) -> Dict[str, Any]: + return self.properties + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "Dimension": + dim_type = d.get(DIM_TYPE_PROP) + if dim_type is None: + raise ps.RequiredPropertyMissing('cube_dimension', DIM_TYPE_PROP) + if dim_type == 'spatial': + axis = d.get(DIM_AXIS_PROP) + if axis is None: + raise ps.RequiredPropertyMissing('cube_dimension', DIM_AXIS_PROP) + if axis == 'z': + return VerticalSpatialDimension(d) + else: + return HorizontalSpatialDimension(d) + elif dim_type == 'temporal': + # The v1.0.0 spec says that AdditionalDimensions can have + # type 'temporal', but it is unclear how to differentiate that + # from a temporal dimension. Just key off of type for now. + # See https://github.com/stac-extensions/datacube/issues/5 + return TemporalDimension(d) + else: + return AdditionalDimension(d) + + +class HorizontalSpatialDimension(Dimension): + @property + def axis(self) -> str: + return get_required(self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP) + + @axis.setter + def axis(self, v: str) -> None: + self.properties[DIM_TYPE_PROP] = v + + @property + def extent(self) -> List[float]: + return get_required(self.properties.get(DIM_EXTENT_PROP), "cube:dimension", DIM_EXTENT_PROP) + + @extent.setter + def extent(self, v: List[float]) -> None: + self.properties[DIM_EXTENT_PROP] = v + + @property + def values(self) -> Optional[List[float]]: + return self.properties.get(DIM_VALUES_PROP) + + @values.setter + def values(self, v: Optional[List[float]]) -> None: + if v is None: + self.properties.pop(DIM_VALUES_PROP, None) + else: + self.properties[DIM_VALUES_PROP] = v + + @property + def step(self) -> Optional[float]: + return self.properties.get(DIM_STEP_PROP) + + @step.setter + def step(self, v: Optional[float]) -> None: + self.properties[DIM_STEP_PROP] = v + + def clear_step(self) -> None: + """Setting step to None sets it to the null value, + which means irregularly spaced steps. Use clear_step + to remove it from the properties.""" + self.properties.pop(DIM_STEP_PROP, None) + + @property + def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: + return self.properties.get(DIM_REF_SYS_PROP) + + @reference_system.setter + def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> None: + if v is None: + self.properties.pop(DIM_REF_SYS_PROP, None) + else: + self.properties[DIM_REF_SYS_PROP] = v + + +class VerticalSpatialDimension(Dimension): + @property + def axis(self) -> str: + return get_required(self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP) + + @axis.setter + def axis(self, v: str) -> None: + self.properties[DIM_TYPE_PROP] = v + + @property + def extent(self) -> Optional[List[Optional[float]]]: + return self.properties.get(DIM_EXTENT_PROP) + + @extent.setter + def extent(self, v: Optional[List[Optional[float]]]) -> None: + if v is None: + self.properties.pop(DIM_EXTENT_PROP, None) + else: + self.properties[DIM_EXTENT_PROP] = v + + @property + def values(self) -> Optional[Union[List[float], List[str]]]: + return self.properties.get(DIM_VALUES_PROP) + + @values.setter + def values(self, v: Optional[Union[List[float], List[str]]]) -> None: + if v is None: + self.properties.pop(DIM_VALUES_PROP, None) + else: + self.properties[DIM_VALUES_PROP] = v + + @property + def step(self) -> Optional[float]: + return self.properties.get(DIM_STEP_PROP) + + @step.setter + def step(self, v: Optional[float]) -> None: + self.properties[DIM_STEP_PROP] = v + + def clear_step(self) -> None: + """Setting step to None sets it to the null value, + which means irregularly spaced steps. Use clear_step + to remove it from the properties.""" + self.properties.pop(DIM_STEP_PROP, None) + + @property + def unit(self) -> Optional[str]: + return self.properties.get(DIM_UNIT_PROP) + + @unit.setter + def unit(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(DIM_UNIT_PROP, None) + else: + self.properties[DIM_UNIT_PROP] = v + + @property + def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: + return self.properties.get(DIM_REF_SYS_PROP) + + @reference_system.setter + def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> None: + if v is None: + self.properties.pop(DIM_REF_SYS_PROP, None) + else: + self.properties[DIM_REF_SYS_PROP] = v + + + +class TemporalDimension(Dimension): + @property + def extent(self) -> Optional[List[Optional[str]]]: + return self.properties.get(DIM_EXTENT_PROP) + + @extent.setter + def extent(self, v: Optional[List[Optional[str]]]) -> None: + if v is None: + self.properties.pop(DIM_EXTENT_PROP, None) + else: + self.properties[DIM_EXTENT_PROP] = v + + @property + def values(self) -> Optional[List[str]]: + return self.properties.get(DIM_VALUES_PROP) + + @values.setter + def values(self, v: Optional[List[str]]) -> None: + if v is None: + self.properties.pop(DIM_VALUES_PROP, None) + else: + self.properties[DIM_VALUES_PROP] = v + + @property + def step(self) -> Optional[str]: + return self.properties.get(DIM_STEP_PROP) + + @step.setter + def step(self, v: Optional[str]) -> None: + self.properties[DIM_STEP_PROP] = v + + def clear_step(self) -> None: + """Setting step to None sets it to the null value, + which means irregularly spaced steps. Use clear_step + to remove it from the properties.""" + self.properties.pop(DIM_STEP_PROP, None) + + +class AdditionalDimension(Dimension): + @property + def extent(self) -> Optional[List[Optional[float]]]: + return self.properties.get(DIM_EXTENT_PROP) + + @extent.setter + def extent(self, v: Optional[List[Optional[float]]]) -> None: + if v is None: + self.properties.pop(DIM_EXTENT_PROP, None) + else: + self.properties[DIM_EXTENT_PROP] = v + + @property + def values(self) -> Optional[Union[List[str], List[float]]]: + return self.properties.get(DIM_VALUES_PROP) + + @values.setter + def values(self, v: Optional[Union[List[str], List[float]]]) -> None: + if v is None: + self.properties.pop(DIM_VALUES_PROP, None) + else: + self.properties[DIM_VALUES_PROP] = v + + @property + def step(self) -> Optional[float]: + return self.properties.get(DIM_STEP_PROP) + + @step.setter + def step(self, v: Optional[float]) -> None: + self.properties[DIM_STEP_PROP] = v + + def clear_step(self) -> None: + """Setting step to None sets it to the null value, + which means irregularly spaced steps. Use clear_step + to remove it from the properties.""" + self.properties.pop(DIM_STEP_PROP, None) + + @property + def unit(self) -> Optional[str]: + return self.properties.get(DIM_UNIT_PROP) + + @unit.setter + def unit(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(DIM_UNIT_PROP, None) + else: + self.properties[DIM_UNIT_PROP] = v + + @property + def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: + return self.properties.get(DIM_REF_SYS_PROP) + + @reference_system.setter + def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> None: + if v is None: + self.properties.pop(DIM_REF_SYS_PROP, None) + else: + self.properties[DIM_REF_SYS_PROP] = v + + +class DatacubeExtension(Generic[T], PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): + def apply(self, dimensions: Dict[str, Dimension]) -> None: + self.dimensions = dimensions + + @property + def dimensions(self) -> Dict[str, Dimension]: + return get_required( + map_opt(lambda d: {k: Dimension.from_dict(v) + for k, v in d.items()}, + self._get_property(DIMENSIONS_PROP, Dict[str, Any])), self, DIMENSIONS_PROP) + + @dimensions.setter + def dimensions(self, v: Dict[str, Dimension]) -> None: + self._set_property(DIMENSIONS_PROP, {k: dim.to_dict() for k, dim in v.items()}) + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + @staticmethod + def ext(obj: T) -> "DatacubeExtension[T]": + if isinstance(obj, ps.Collection): + return cast(DatacubeExtension[T], CollectionDatacubeExtension(obj)) + if isinstance(obj, ps.Item): + return cast(DatacubeExtension[T], ItemDatacubeExtension(obj)) + elif isinstance(obj, ps.Asset): + return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) + else: + raise ExtensionException(f"Datacube extension does not apply to type {type(obj)}") + +class CollectionDatacubeExtension(DatacubeExtension[ps.Collection]): + def __init__(self, collection: ps.Collection): + self.collection = collection + self.properties = collection.extra_fields + + def __repr__(self) -> str: + return ''.format(self.collection.id) + +class ItemDatacubeExtension(DatacubeExtension[ps.Item]): + def __init__(self, item: ps.Item): + self.item = item + self.properties = item.properties + + def __repr__(self) -> str: + return ''.format(self.item.id) + +class AssetDatacubeExtension(DatacubeExtension[ps.Asset]): + def __init__(self, asset: ps.Asset): + self.asset_href = asset.href + self.properties = asset.properties + if asset.owner and isinstance(asset.owner, ps.Item): + self.additional_read_properties = [asset.owner.properties] + + def __repr__(self) -> str: + return ''.format(self.asset_href) + +class DatacubeExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['datacube']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + +DATACUBE_EXTENSION_HOOKS = DatacubeExtensionHooks() \ No newline at end of file From 1e0ae0702c41585610a610c9a1ebb386ddf4ae79 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:25:01 -0400 Subject: [PATCH 25/51] Add item_assets extension --- pystac/extensions/item_assets.py | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 pystac/extensions/item_assets.py diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py new file mode 100644 index 000000000..94a7a4901 --- /dev/null +++ b/pystac/extensions/item_assets.py @@ -0,0 +1,125 @@ +from typing import Any, Dict, List, Optional, Set + +import pystac as ps +from pystac.extensions.base import ExtensionManagementMixin +from pystac.extensions.hooks import ExtensionHooks +from pystac.serialization.identify import STACJSONDescription, STACVersionID +from pystac.utils import get_required + +SCHEMA_URI = "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json" + +ITEM_ASSETS_PROP = "item_assets" + +ASSET_TITLE_PROP = "title" +ASSET_DESC_PROP = "description" +ASSET_TYPE_PROP = "type" +ASSET_ROLES_PROP = "roles" + + +class AssetDefinition: + def __init__(self, properties: Dict[str, Any]) -> None: + self.properties = properties + + @property + def title(self) -> Optional[str]: + self.properties.get(ASSET_TITLE_PROP) + + @title.setter + def title(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(ASSET_TITLE_PROP, None) + else: + self.properties[ASSET_TITLE_PROP] = v + + @property + def description(self) -> Optional[str]: + self.properties.get(ASSET_DESC_PROP) + + @description.setter + def description(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(ASSET_DESC_PROP, None) + else: + self.properties[ASSET_DESC_PROP] = v + + @property + def media_type(self) -> Optional[str]: + self.properties.get(ASSET_TYPE_PROP) + + @media_type.setter + def media_type(self, v: Optional[str]) -> None: + if v is None: + self.properties.pop(ASSET_TYPE_PROP, None) + else: + self.properties[ASSET_TYPE_PROP] = v + + @property + def roles(self) -> Optional[List[str]]: + self.properties.get(ASSET_ROLES_PROP) + + @roles.setter + def roles(self, v: Optional[List[str]]) -> None: + if v is None: + self.properties.pop(ASSET_ROLES_PROP, None) + else: + self.properties[ASSET_ROLES_PROP] = v + + def create_asset(self, href: str) -> ps.Asset: + return ps.Asset( + href=href, + title=self.title, + description=self.description, + media_type=self.media_type, + roles=self.roles, + properties={ + k: v + for k, v in self.properties if k not in set( + [ASSET_TITLE_PROP, ASSET_DESC_PROP, ASSET_TYPE_PROP, ASSET_ROLES_PROP]) + }) + + +class ItemAssetsExtension(ExtensionManagementMixin[ps.Collection]): + def __init__(self, collection: ps.Collection) -> None: + self.collection = collection + + @property + def item_assets(self) -> Dict[str, AssetDefinition]: + result = get_required(self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, + ITEM_ASSETS_PROP) + return {k: AssetDefinition(v) for k, v in result.items()} + + @item_assets.setter + def item_assets(self, v: Dict[str, AssetDefinition]) -> None: + self.collection.extra_fields[ITEM_ASSETS_PROP] = { + k: asset_def.properties + for k, asset_def in v.items() + } + + def __repr__(self) -> str: + return f"" + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + @classmethod + def ext(cls, collection: ps.Collection) -> "ItemAssetsExtension": + return cls(collection) + +class ItemAssetsExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set(['asset', 'item-assets']) + stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.COLLECTION]) + + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: + # Handel that the "item-assets" extension had the id of "assets", before + # collection assets (since removed) took over the ID of "assets" + if version < '1.0.0-beta.1' and 'asset' in info.extensions: + if 'assets' in obj: + obj['item_assets'] = obj['assets'] + del obj['assets'] + + super().migrate(obj, version, info) + +ITEM_ASSETS_EXTENSION_HOOKS = ItemAssetsExtensionHooks() \ No newline at end of file From aba25aab362d182ab72a7d007302c20921d54476 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:25:13 -0400 Subject: [PATCH 26/51] Fixe tests --- pystac/__init__.py | 29 ++- pystac/errors.py | 1 - pystac/extensions/sar.py | 2 +- pystac/extensions/version.py | 2 +- pystac/serialization/identify.py | 6 +- pystac/serialization/migrate.py | 223 +++++------------------- pystac/validation/__init__.py | 17 +- pystac/validation/schema_uri_map.py | 173 +++++++++++++++++- pystac/validation/stac_validator.py | 9 +- pystac/version.py | 7 +- tests/__init__.py | 16 ++ tests/extensions/test_view.py | 2 +- tests/serialization/test_migrate.py | 5 +- tests/test_version.py | 45 ++--- tests/utils/__init__.py | 16 +- tests/validation/test_schema_uri_map.py | 2 +- tests/validation/test_validate.py | 9 +- 17 files changed, 320 insertions(+), 244 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index c68b494f3..ea70bef75 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -3,7 +3,6 @@ """ # flake8: noqa - from pystac.errors import (STACError, STACTypeError, RequiredPropertyMissing) # type:ignore from typing import Any, Dict, Optional @@ -27,8 +26,10 @@ from pystac.validation import STACValidationError # type:ignore import pystac.extensions.hooks +import pystac.extensions.datacube import pystac.extensions.eo import pystac.extensions.file +import pystac.extensions.item_assets import pystac.extensions.label import pystac.extensions.pointcloud import pystac.extensions.projection @@ -39,20 +40,18 @@ import pystac.extensions.version import pystac.extensions.view -EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks( - [ - pystac.extensions.eo.EO_EXTENSION_HOOKS, - pystac.extensions.file.FILE_EXTENSION_HOOKS, - pystac.extensions.label.LABEL_EXTENSION_HOOKS, - pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, - pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, - pystac.extensions.sar.SAR_EXTENSION_HOOKS, - pystac.extensions.sat.SAT_EXTENSION_HOOKS, - pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, - pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, - pystac.extensions.version.VERSION_EXTENSION_HOOKS, - pystac.extensions.view.VIEW_EXTENSION_HOOKS - ]) +EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks([ + pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS, pystac.extensions.eo.EO_EXTENSION_HOOKS, + pystac.extensions.file.FILE_EXTENSION_HOOKS, + pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS, + pystac.extensions.label.LABEL_EXTENSION_HOOKS, + pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, + pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, + pystac.extensions.sar.SAR_EXTENSION_HOOKS, pystac.extensions.sat.SAT_EXTENSION_HOOKS, + pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, + pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, + pystac.extensions.version.VERSION_EXTENSION_HOOKS, pystac.extensions.view.VIEW_EXTENSION_HOOKS +]) def read_file(href: str) -> STACObject: diff --git a/pystac/errors.py b/pystac/errors.py index 9d9af0e93..82436299e 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -16,7 +16,6 @@ class STACTypeError(Exception): """ pass - class RequiredPropertyMissing(Exception): """ This error is raised when a required value was expected to be there but was missing or None. This will happen, for example, diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index f0b889fab..aeba4ee43 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -190,7 +190,7 @@ def product_type(self) -> str: Returns: str """ - return get_required(self._get_property(POLARIZATIONS, str), self, POLARIZATIONS) + return get_required(self._get_property(PRODUCT_TYPE, str), self, PRODUCT_TYPE) @product_type.setter def product_type(self, v: str) -> None: diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index d45893efc..ae8cd1f9d 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -14,7 +14,7 @@ T = TypeVar('T', ps.Collection, ps.Item) -SCHEMA_URI = "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog.json" +SCHEMA_URI = "https://stac-extensions.github.io/version/v1.0.0/schema.json" # STAC fields - These are unusual for an extension in that they do not have # a prefix. e.g. nothing like "ver:" diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 76f7107d2..0d23cdb61 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -12,7 +12,7 @@ class OldExtensionShortIDs(Enum): """Enumerates the IDs of common extensions.""" CHECKSUM = 'checksum' - COLLECTION_ASSETS = 'collection-assets' # REMOVED + COLLECTION_ASSETS = 'collection-assets' DATACUBE = 'datacube' # TODO EO = 'eo' ITEM_ASSETS = 'item-assets' # TODO @@ -22,8 +22,8 @@ class OldExtensionShortIDs(Enum): SAR = 'sar' SAT = 'sat' SCIENTIFIC = 'scientific' - SINGLE_FILE_STAC = 'single-file-stac' # TODO - TILED_ASSETS = 'tiled-assets' # Removed (Unpublished) + SINGLE_FILE_STAC = 'single-file-stac' + TILED_ASSETS = 'tiled-assets' TIMESTAMPS = 'timestamps' VERSION = 'version' VIEW = 'view' diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 3b1c3037f..041ff59d1 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -1,14 +1,12 @@ -from functools import lru_cache from copy import deepcopy from typing import Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING, Tuple import pystac as ps from pystac.version import STACVersion -from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID, - STACVersionRange) +from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID) if TYPE_CHECKING: - from pystac.stac_object import STACObjectType as STACObjectType_Type + from pystac import STACObjectType as STACObjectType_Type def _migrate_links(d: Dict[str, Any], version: STACVersionID) -> None: @@ -46,176 +44,6 @@ def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, # Extensions -class OldExtensionSchemaUriMap: - """Ties old extension IDs to schemas hosted by https://schemas.stacspec.org. - - For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the - radiantearth/stac-spec GitHub repo. - """ - - # BASE_URIS contains a list of tuples, the first element is a version range and the - # second being the base URI for schemas for that range. The schema URI of a STAC - # for a particular version uses the base URI associated with the version range which - # contains it. If the version it outside of any VersionRange, there is no URI for the - # schema. - @classmethod - @lru_cache() - def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: - return [(STACVersionRange(min_version='1.0.0-beta.1'), - lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), - (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: - 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] - - # DEFAULT_SCHEMA_MAP contains a structure that matches extension schema URIs - # based on the stac object type, extension ID and the stac version. - # Uris are contained in a tuple whose first element represents the URI of the latest - # version, so that a search through version ranges is avoided if the STAC being validated - # is the latest version. If it's a previous version, the stac_version that matches - # the listed version range is used, or else the URI from the latest version is used if - # there are no overrides for previous versions. - @classmethod - @lru_cache() - def get_schema_map(cls) -> Dict[str, Any]: - return { - OldExtensionShortIDs.CHECKSUM.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/checksum/json-schema/schema.json' - }, None), - OldExtensionShortIDs.COLLECTION_ASSETS.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/collection-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.DATACUBE.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/datacube/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/datacube/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: None, - ps.STACObjectType.ITEM: None - })]), - OldExtensionShortIDs.EO.value: ({ - ps.STACObjectType.ITEM: - 'extensions/eo/json-schema/schema.json' - }, None), - OldExtensionShortIDs.ITEM_ASSETS.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/item-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.LABEL.value: ({ - ps.STACObjectType.ITEM: - 'extensions/label/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { - ps.STACObjectType.ITEM: 'extensions/label/schema.json' - })]), - OldExtensionShortIDs.POINTCLOUD.value: ( - { - # Pointcloud schema was broken in 1.0.0-beta.2 and prior; - # Use this schema version (corresponding to 1.0.0-rc.1) - # to allow for proper validation - ps.STACObjectType.ITEM: - 'https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json' - }, - None), - OldExtensionShortIDs.PROJECTION.value: ({ - ps.STACObjectType.ITEM: - 'extensions/projection/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAR.value: ({ - ps.STACObjectType.ITEM: - 'extensions/sar/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAT.value: ({ - ps.STACObjectType.ITEM: - 'extensions/sat/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SCIENTIFIC.value: ({ - ps.STACObjectType.ITEM: - 'extensions/scientific/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/scientific/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SINGLE_FILE_STAC.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/single-file-stac/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TILED_ASSETS.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/tiled-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TIMESTAMPS.value: ({ - ps.STACObjectType.ITEM: - 'extensions/timestamps/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VERSION.value: ({ - ps.STACObjectType.ITEM: - 'extensions/version/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/version/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VIEW.value: ({ - ps.STACObjectType.ITEM: - 'extensions/view/json-schema/schema.json' - }, None), - - # Removed or renamed extensions. - 'dtr': (None, None), # Invalid schema - 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: - 'extensions/asset/json-schema/schema.json' - })]), - } - - @classmethod - def _append_base_uri_if_needed(cls, uri: str, stac_version: STACVersionID) -> Optional[str]: - # Only append the base URI if it's not already an absolute URI - if '://' not in uri: - base_uri = None - for version_range, f in cls.get_base_uris(): - if version_range.contains(stac_version): - base_uri = f(stac_version) - return '{}/{}'.format(base_uri, uri) - - # No JSON Schema for the old extension - return None - else: - return uri - - @classmethod - def get_extension_schema_uri(cls, extension_id: str, object_type: "STACObjectType_Type", - stac_version: STACVersionID) -> Optional[str]: - uri = None - - is_latest = stac_version == ps.get_stac_version() - - ext_map = cls.get_schema_map() - if extension_id in ext_map: - if ext_map[extension_id][0] and \ - object_type in ext_map[extension_id][0]: - uri = ext_map[extension_id][0][object_type] - - if not is_latest: - if ext_map[extension_id][1]: - for version_range, ext_uris in ext_map[extension_id][1]: - if version_range.contains(stac_version): - if object_type in ext_uris: - uri = ext_uris[object_type] - break - - if uri is None: - return uri - else: - return cls._append_base_uri_if_needed(uri, stac_version) - - def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> Optional[Set[str]]: if version < '1.0.0-beta.2': @@ -252,24 +80,52 @@ def _get_object_migrations( def _get_removed_extension_migrations( -) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]: +) -> Dict[str, Tuple[Optional[List["STACObjectType_Type"]], Optional[Callable[ + [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]]]: """Handles removed extensions. This does not handle renamed extension or extensions that were absorbed by other extensions; for instance the FileExtensions handles the migration of the since replaced 'checksum' extension. + + Dict of the extension ID to a tuple of optional list of STACObjectType which it was + removed for (or None if removed from all), and an optional migrate function + that can modify the object in case the extension was removed but the properties + were moved. """ return { # -- Removed in 1.0 # assets in collections became a core property - OldExtensionShortIDs.COLLECTION_ASSETS.value: lambda a, b, c: None, + OldExtensionShortIDs.COLLECTION_ASSETS.value: (None, None), + + # Extensions that were placed on Collections that applied to + # the 'commons' properties of their Items, but since the commons + # property merging has went away these extensions are removed + # from the collection. Note that a migrated Collection may still + # have a "properties" in extra_fields with the extension fields. + OldExtensionShortIDs.EO.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.FILE.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.LABEL.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.POINTCLOUD.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.PROJECTION.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.SAR.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.SAT.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.TIMESTAMPS.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.VIEW.value: ([ps.STACObjectType.COLLECTION], None), + + # tiled-assets was never a fully published extension; + # remove reference to the pre-1.0 RC short ID + OldExtensionShortIDs.TILED_ASSETS.value: (None, None), + + # Single File STAC is a removed concept; is being reworked as of + # STAC 1.0.0-RC.3. Remove short ID from PySTAC as it's unsupported + OldExtensionShortIDs.SINGLE_FILE_STAC.value: (None, None), # -- Removed in 0.9.0 - - 'dtr': _migrate_datetime_range, - 'datetime-range': _migrate_datetime_range, - 'commons': lambda a, b, c: None # No changes needed, just remove the extension_id + 'dtr': (None, _migrate_datetime_range), + 'datetime-range': (None, _migrate_datetime_range), + 'commons': (None, None) } @@ -307,8 +163,11 @@ def migrate_to_latest(json_dict: Dict[str, Any], info: STACJSONDescription) -> D for ext in result['stac_extensions'][:]: if ext in removed_extension_migrations: - removed_extension_migrations[ext](result, version, info) - result['stac_extensions'].remove(ext) + object_types, migration_fn = removed_extension_migrations[ext] + if object_types is None or info.object_type in object_types: + if migration_fn: + migration_fn(result, version, info) + result['stac_extensions'].remove(ext) result['stac_version'] = STACVersion.DEFAULT_STAC_VERSION diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 464955929..77d4552ee 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -2,7 +2,8 @@ from typing import Dict, List, Any, Optional, cast, TYPE_CHECKING import pystac as ps -from pystac.serialization.identify import identify_stac_object +from pystac.serialization.identify import STACVersionID, identify_stac_object +from pystac.validation.schema_uri_map import OldExtensionSchemaUriMap from pystac.utils import make_absolute_href if TYPE_CHECKING: @@ -92,6 +93,20 @@ def validate_dict(stac_dict: Dict[str, Any], info = identify_stac_object(stac_dict) extensions = list(info.extensions) + stac_version_id = STACVersionID(stac_version) + + # If the version is before 1.0.0-rc.1, substitute extension short IDs for + # their schemas. + if stac_version_id < "1.0.0-rc.1": + + def _get_uri(ext: str) -> Optional[str]: + return OldExtensionSchemaUriMap.get_extension_schema_uri( + ext, + stac_object_type, # type:ignore + stac_version_id) + + extensions = [uri for uri in map(_get_uri, extensions) if uri is not None] + return RegisteredValidator.get_validator().validate(stac_dict, stac_object_type, stac_version, extensions, href) diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 2f53e4612..829b5f770 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,4 +1,6 @@ from abc import (ABC, abstractmethod) +from functools import lru_cache +from pystac.serialization.identify import OldExtensionShortIDs, STACVersionID from typing import Any, Callable, Dict, List, Optional, Tuple import pystac as ps @@ -13,7 +15,8 @@ def __init__(self) -> None: pass @abstractmethod - def get_object_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: + def get_object_schema_uri(self, object_type: STACObjectType, + stac_version: str) -> Optional[str]: """Get the schema URI for the given object type and stac version. Args: @@ -77,7 +80,8 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str else: return uri - def get_object_schema_uri(self, object_type: STACObjectType, stac_version: str) -> Optional[str]: + def get_object_schema_uri(self, object_type: STACObjectType, + stac_version: str) -> Optional[str]: uri = None is_latest = stac_version == ps.get_stac_version() @@ -93,3 +97,168 @@ def get_object_schema_uri(self, object_type: STACObjectType, stac_version: str) break return self._append_base_uri_if_needed(uri, stac_version) + + +class OldExtensionSchemaUriMap: + """Ties old extension IDs to schemas hosted by https://schemas.stacspec.org. + + For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the + radiantearth/stac-spec GitHub repo. + """ + + # BASE_URIS contains a list of tuples, the first element is a version range and the + # second being the base URI for schemas for that range. The schema URI of a STAC + # for a particular version uses the base URI associated with the version range which + # contains it. If the version it outside of any VersionRange, there is no URI for the + # schema. + @classmethod + @lru_cache() + def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: + return [(STACVersionRange(min_version='1.0.0-beta.1'), + lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), + (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: + 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] + + # DEFAULT_SCHEMA_MAP contains a structure that matches extension schema URIs + # based on the stac object type, extension ID and the stac version. + # Uris are contained in a tuple whose first element represents the URI of the latest + # version, so that a search through version ranges is avoided if the STAC being validated + # is the latest version. If it's a previous version, the stac_version that matches + # the listed version range is used, or else the URI from the latest version is used if + # there are no overrides for previous versions. + @classmethod + @lru_cache() + def get_schema_map(cls) -> Dict[str, Any]: + return { + OldExtensionShortIDs.CHECKSUM.value: ({ + ps.STACObjectType.CATALOG: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/checksum/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/checksum/json-schema/schema.json' + }, None), + OldExtensionShortIDs.COLLECTION_ASSETS.value: ({ + ps.STACObjectType.COLLECTION: + 'extensions/collection-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.DATACUBE.value: ({ + ps.STACObjectType.COLLECTION: + 'extensions/datacube/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/datacube/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: None, + ps.STACObjectType.ITEM: None + })]), + OldExtensionShortIDs.EO.value: ({ + ps.STACObjectType.ITEM: + 'extensions/eo/json-schema/schema.json' + }, None), + OldExtensionShortIDs.ITEM_ASSETS.value: ({ + ps.STACObjectType.COLLECTION: + 'extensions/item-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.LABEL.value: ({ + ps.STACObjectType.ITEM: + 'extensions/label/json-schema/schema.json' + }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { + ps.STACObjectType.ITEM: 'extensions/label/schema.json' + })]), + OldExtensionShortIDs.POINTCLOUD.value: ( + # Invalid schema + None, + None), + OldExtensionShortIDs.PROJECTION.value: ({ + ps.STACObjectType.ITEM: + 'extensions/projection/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAR.value: ({ + ps.STACObjectType.ITEM: + 'extensions/sar/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SAT.value: ({ + ps.STACObjectType.ITEM: + 'extensions/sat/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SCIENTIFIC.value: ({ + ps.STACObjectType.ITEM: + 'extensions/scientific/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/scientific/json-schema/schema.json' + }, None), + OldExtensionShortIDs.SINGLE_FILE_STAC.value: ({ + ps.STACObjectType.CATALOG: + 'extensions/single-file-stac/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TILED_ASSETS.value: ({ + ps.STACObjectType.CATALOG: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/tiled-assets/json-schema/schema.json', + ps.STACObjectType.ITEM: + 'extensions/tiled-assets/json-schema/schema.json' + }, None), + OldExtensionShortIDs.TIMESTAMPS.value: ({ + ps.STACObjectType.ITEM: + 'extensions/timestamps/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VERSION.value: ({ + ps.STACObjectType.ITEM: + 'extensions/version/json-schema/schema.json', + ps.STACObjectType.COLLECTION: + 'extensions/version/json-schema/schema.json' + }, None), + OldExtensionShortIDs.VIEW.value: ({ + ps.STACObjectType.ITEM: + 'extensions/view/json-schema/schema.json' + }, None), + + # Removed or renamed extensions. + 'dtr': (None, None), # Invalid schema + 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { + ps.STACObjectType.COLLECTION: + 'extensions/asset/json-schema/schema.json' + })]), + } + + @classmethod + def _append_base_uri_if_needed(cls, uri: str, stac_version: STACVersionID) -> Optional[str]: + # Only append the base URI if it's not already an absolute URI + if '://' not in uri: + base_uri = None + for version_range, f in cls.get_base_uris(): + if version_range.contains(stac_version): + base_uri = f(stac_version) + return '{}/{}'.format(base_uri, uri) + + # No JSON Schema for the old extension + return None + else: + return uri + + @classmethod + def get_extension_schema_uri(cls, extension_id: str, object_type: STACObjectType, + stac_version: STACVersionID) -> Optional[str]: + uri = None + + is_latest = stac_version == ps.get_stac_version() + + ext_map = cls.get_schema_map() + if extension_id in ext_map: + if ext_map[extension_id][0] and \ + object_type in ext_map[extension_id][0]: + uri = ext_map[extension_id][0][object_type] + + if not is_latest: + if ext_map[extension_id][1]: + for version_range, ext_uris in ext_map[extension_id][1]: + if version_range.contains(stac_version): + if object_type in ext_uris: + uri = ext_uris[object_type] + break + + if uri is None: + return uri + else: + return cls._append_base_uri_if_needed(uri, stac_version) diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 2dc68bfe8..e4da8fb2b 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -1,5 +1,6 @@ -import json from abc import (ABC, abstractmethod) +import logging +import json from pystac.stac_object import STACObjectType from typing import Any, Dict, List, Optional, Tuple @@ -14,6 +15,8 @@ except ImportError: jsonschema = None +logger = logging.getLogger(__name__) + class STACValidator(ABC): """STACValidator defines methods for validating STAC @@ -230,3 +233,7 @@ def validate_extension(self, msg = self._get_error_message(schema_uri, stac_object_type, extension_id, href, stac_dict.get('id')) raise STACValidationError(msg, source=e) from e + except Exception as e: + logger.error(f"Exception while validating {stac_object_type} href: {href}") + logger.exception(e) + raise diff --git a/pystac/version.py b/pystac/version.py index 6c8f04bf5..091eb4d9a 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -26,7 +26,7 @@ def get_stac_version(cls) -> str: return cls.DEFAULT_STAC_VERSION @classmethod - def set_stac_version(cls, stac_version: str) -> None: + def set_stac_version(cls, stac_version: Optional[str]) -> None: cls._override_version = stac_version @@ -44,7 +44,7 @@ def get_stac_version() -> str: return STACVersion.get_stac_version() -def set_stac_version(stac_version: str) -> None: +def set_stac_version(stac_version: Optional[str]) -> None: """Sets the STAC version that PySTAC should use. This is the version that will be set as the "stac_version" property @@ -57,7 +57,8 @@ def set_stac_version(stac_version: str) -> None: Args: stac_version (str): The STAC version to use instead of the latest STAC version that - PySTAC supports (described in STACVersion.DEFAULT_STAC_VERSION) + PySTAC supports (described in STACVersion.DEFAULT_STAC_VERSION). If None, + clear to use the default for this version of PySTAC. Note: Setting the STAC version to something besides the default version will not effect diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb..cbb512ac6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,16 @@ +import logging +import sys + +def setup_logging(level: int) -> None: + for package in ["pystac", "tests"]: + logger = logging.getLogger(package) + logger.setLevel(level) + + formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(message)s") + + ch = logging.StreamHandler(sys.stdout) + ch.setLevel(level) + ch.setFormatter(formatter) + logger.addHandler(ch) + +setup_logging(logging.INFO) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index b3cb6423f..3ae002ef5 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -2,7 +2,7 @@ import unittest import pystac as ps -from pystac.extensions.view import ViewExtension, +from pystac.extensions.view import ViewExtension from tests.utils import (TestCases, test_to_from_dict) diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index f9e76c825..c0dbaaa8d 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -1,3 +1,4 @@ +from pystac.extensions.item_assets import ItemAssetsExtension from pystac.extensions.view import ViewExtension import unittest @@ -18,8 +19,6 @@ def setUp(self): def test_migrate(self): collection_cache = CollectionCache() for example in self.examples: - if example.path != "/home/rob/proj/stac/pystac/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json": - continue with self.subTest(example.path): path = example.path @@ -68,5 +67,5 @@ def test_migrates_renamed_extension(self): TestCases.get_path('data-files/examples/0.9.0/extensions/asset/' 'examples/example-landsat8.json')) - self.assertIn('item-assets', collection.stac_extensions) + self.assertTrue(ItemAssetsExtension.has_extension(collection)) self.assertIn('item_assets', collection.extra_fields) diff --git a/tests/test_version.py b/tests/test_version.py index 56fb7ac31..d21383c92 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -6,28 +6,29 @@ class VersionTest(unittest.TestCase): + def setUp(self): + self._prev_env_version = os.environ.get('PYSTAC_STAC_VERSION_OVERRIDE') + self._prev_version = ps.get_stac_version() + + def tearDown(self): + if self._prev_env_version is None: + os.environ.pop('PYSTAC_STAC_VERSION_OVERRIDE', None) + else: + os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = self._prev_env_version + ps.set_stac_version(None) + + def test_override_stac_version_with_environ(self): - version = os.environ.get('PYSTAC_STAC_VERSION_OVERRIDE') - - try: - override_version = '1.0.0-gamma.2' - os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = override_version - cat = TestCases.test_case_1() - d = cat.to_dict() - self.assertEqual(d['stac_version'], override_version) - finally: - if version is None: - del os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] - else: - os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = version + + override_version = '1.0.0-gamma.2' + os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = override_version + cat = TestCases.test_case_1() + d = cat.to_dict() + self.assertEqual(d['stac_version'], override_version) def test_override_stac_version_with_call(self): - version = ps.get_stac_version() - try: - override_version = '1.0.0-delta.2' - ps.set_stac_version(override_version) - cat = TestCases.test_case_1() - d = cat.to_dict() - self.assertEqual(d['stac_version'], override_version) - finally: - ps.set_stac_version(version) + override_version = '1.0.0-delta.2' + ps.set_stac_version(override_version) + cat = TestCases.test_case_1() + d = cat.to_dict() + self.assertEqual(d['stac_version'], override_version) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 668774c8a..ab4c6c9fa 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,16 +1,24 @@ # flake8: noqa -from tests.utils.test_cases import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, ARBITRARY_EXTENT) +from typing import Any, Dict, Type +import unittest +from tests.utils.test_cases import ( + TestCases, # type:ignore + ARBITRARY_GEOM, # type:ignore + ARBITRARY_BBOX, # type:ignore + ARBITRARY_EXTENT) # type:ignore from copy import deepcopy from datetime import datetime from dateutil.parser import parse -from tests.utils.stac_io_mock import MockStacIO +import pystac as ps +from tests.utils.stac_io_mock import MockStacIO # type:ignore -def test_to_from_dict(test_class, stac_object_class, d): - def _parse_times(a_dict): +def test_to_from_dict(test_class: unittest.TestCase, stac_object_class: Type[ps.STACObject], + d: Dict[str, Any]) -> None: + def _parse_times(a_dict: Dict[str, Any]) -> None: for k, v in a_dict.items(): if isinstance(v, dict): _parse_times(v) diff --git a/tests/validation/test_schema_uri_map.py b/tests/validation/test_schema_uri_map.py index 6a702c2e7..0f10a4194 100644 --- a/tests/validation/test_schema_uri_map.py +++ b/tests/validation/test_schema_uri_map.py @@ -11,4 +11,4 @@ def test_gets_schema_uri_for_old_version(self): self.assertEqual( uri, 'https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.0/' - 'item/json-schema/schema.json') + 'item-spec/json-schema/item.json') diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 82c0e5622..b0e233dbd 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -35,6 +35,8 @@ def test_validate_current_version(self): def test_validate_examples(self): for example in TestCases.get_examples_info(): + #if example.path != "/home/rob/proj/stac/pystac/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json": + # continue with self.subTest(example.path): stac_version = example.stac_version path = example.path @@ -83,10 +85,11 @@ def test_validate_error_contains_href(self): def test_validate_all(self): for test_case in TestCases.all_test_catalogs(): - catalog_href = get_opt(TestCases.test_case_7().get_self_href()) - stac_dict = ps.STAC_IO.read_json(catalog_href) + catalog_href = test_case.get_self_href() + if catalog_href is not None: + stac_dict = ps.STAC_IO.read_json(catalog_href) - pystac.validation.validate_all(stac_dict, catalog_href) + pystac.validation.validate_all(stac_dict, catalog_href) # Modify a 0.8.1 collection in a catalog to be invalid with a since-renamed extension # and make sure it catches the validation error. From 71cba8a7b1070262df1cf6820d38d29b7f3d8ddd Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:36:06 -0400 Subject: [PATCH 27/51] yapf formatting --- pystac/asset.py | 1 + pystac/collection.py | 2 +- pystac/errors.py | 7 +- pystac/extensions/base.py | 4 +- pystac/extensions/datacube.py | 11 ++- pystac/extensions/hooks.py | 5 +- pystac/extensions/item_assets.py | 4 +- pystac/extensions/pointcloud.py | 3 +- pystac/extensions/projection.py | 5 +- pystac/item.py | 2 +- pystac/serialization/identify.py | 1 - pystac/utils.py | 3 +- tests/__init__.py | 2 + tests/data-files/change_stac_version.py | 12 ++-- tests/extensions/test_custom.py | 7 +- tests/extensions/test_file.py | 6 +- tests/extensions/test_pointcloud.py | 17 ++--- tests/extensions/test_projection.py | 92 ++++++++++++++----------- tests/extensions/test_sar.py | 13 ++-- tests/extensions/test_timestamps.py | 49 +++++++------ tests/extensions/test_version.py | 17 +++-- tests/extensions/test_view.py | 58 +++++++++------- tests/serialization/test_identify.py | 4 +- tests/test_item.py | 48 ++++++++----- tests/test_layout.py | 6 +- tests/test_link.py | 8 +-- tests/test_version.py | 1 - tests/utils/test_cases.py | 13 ++-- tests/validation/test_validate.py | 13 ++-- 29 files changed, 241 insertions(+), 173 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index 6458ad40e..0d6333821 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -8,6 +8,7 @@ from pystac.collection import Collection as Collection_Type from pystac.item import Item as Item_Type + class Asset: """An object that contains a link to data associated with an Item or Collection that can be downloaded or streamed. diff --git a/pystac/collection.py b/pystac/collection.py index c73572462..d6ec7ba16 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -545,7 +545,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: if not self.summaries.is_empty(): d['summaries'] = self.summaries.to_dict() if any(self.assets): - d['assets'] = {k: v.to_dict() for k, v in self.assets.items() } + d['assets'] = {k: v.to_dict() for k, v in self.assets.items()} return d diff --git a/pystac/errors.py b/pystac/errors.py index 82436299e..b696d3e12 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -16,6 +16,7 @@ class STACTypeError(Exception): """ pass + class RequiredPropertyMissing(Exception): """ This error is raised when a required value was expected to be there but was missing or None. This will happen, for example, @@ -31,8 +32,6 @@ class RequiredPropertyMissing(Exception): def __init__(self, obj: Union[str, Any], prop: str, - msg: Optional[str] = None, - *args: Any, - **kwargs: Any) -> None: + msg: Optional[str] = None) -> None: msg = msg or f"{repr(obj)} does not have required property {prop}" - super().__init__(msg, *args, **kwargs) \ No newline at end of file + super().__init__(msg) diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 2fee8a0d0..050b41250 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -13,7 +13,7 @@ def __init__(self, collection: ps.Collection) -> None: self.summaries = collection.summaries def _set_summary(self, prop_key: str, v: Optional[Union[List[Any], ps.RangeSummary[Any], - Dict[str, Any]]]) -> None: + Dict[str, Any]]]) -> None: if v is None: self.summaries.remove(prop_key) else: @@ -27,7 +27,7 @@ class PropertiesExtension(ABC): properties: Dict[str, Any] additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None - def _get_property(self, prop_name: str, typ: Type[P] = Any) -> Optional[P]: + def _get_property(self, prop_name: str, typ: Type[P] = Type[Any]) -> Optional[P]: result: Optional[typ] = self.properties.get(prop_name) if result is not None: return result diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index 045ebc861..0a98f1c82 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -194,7 +194,6 @@ def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> No self.properties[DIM_REF_SYS_PROP] = v - class TemporalDimension(Dimension): @property def extent(self) -> Optional[List[Optional[str]]]: @@ -324,6 +323,7 @@ def ext(obj: T) -> "DatacubeExtension[T]": else: raise ExtensionException(f"Datacube extension does not apply to type {type(obj)}") + class CollectionDatacubeExtension(DatacubeExtension[ps.Collection]): def __init__(self, collection: ps.Collection): self.collection = collection @@ -332,6 +332,7 @@ def __init__(self, collection: ps.Collection): def __repr__(self) -> str: return ''.format(self.collection.id) + class ItemDatacubeExtension(DatacubeExtension[ps.Item]): def __init__(self, item: ps.Item): self.item = item @@ -340,6 +341,7 @@ def __init__(self, item: ps.Item): def __repr__(self) -> str: return ''.format(self.item.id) + class AssetDatacubeExtension(DatacubeExtension[ps.Asset]): def __init__(self, asset: ps.Asset): self.asset_href = asset.href @@ -350,9 +352,12 @@ def __init__(self, asset: ps.Asset): def __repr__(self) -> str: return ''.format(self.asset_href) + class DatacubeExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(['datacube']) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + stac_object_types: Set[ps.STACObjectType] = set( + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + -DATACUBE_EXTENSION_HOOKS = DatacubeExtensionHooks() \ No newline at end of file +DATACUBE_EXTENSION_HOOKS = DatacubeExtensionHooks() diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index 8e4aa5117..3d60b557b 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -42,7 +42,7 @@ def get_object_links(self, obj: "STACObject_Type") -> Optional[List[str]]: return None def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + info: STACJSONDescription) -> None: """Migrate a STAC Object in dict format from a previous version. The base implementation will update the stac_extensions to the latest schema ID. This method will only be called for STAC objects that have been @@ -60,6 +60,7 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, obj['stac_extensions'].append(self.schema_uri) break + class RegisteredExtensionHooks: def __init__(self, hooks: Iterable[ExtensionHooks]): self.hooks = dict([(e.schema_uri, e) for e in hooks]) @@ -88,7 +89,7 @@ def get_extended_object_links(self, obj: "STACObject_Type") -> List[str]: return result or [] def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + info: STACJSONDescription) -> None: for hooks in self.hooks.values(): if info.object_type in hooks._get_stac_object_types(): hooks.migrate(obj, version, info) diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index 94a7a4901..e79a9f7c2 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -106,6 +106,7 @@ def get_schema_uri(cls) -> str: def ext(cls, collection: ps.Collection) -> "ItemAssetsExtension": return cls(collection) + class ItemAssetsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(['asset', 'item-assets']) @@ -122,4 +123,5 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, super().migrate(obj, version, info) -ITEM_ASSETS_EXTENSION_HOOKS = ItemAssetsExtensionHooks() \ No newline at end of file + +ITEM_ASSETS_EXTENSION_HOOKS = ItemAssetsExtensionHooks() diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index f1e106a64..17df24830 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -522,4 +522,5 @@ class PointcloudExtensionHooks(ExtensionHooks): prev_extension_ids: Set[str] = set(['pointcloud']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -POINTCLOUD_EXTENSION_HOOKS = PointcloudExtensionHooks() \ No newline at end of file + +POINTCLOUD_EXTENSION_HOOKS = PointcloudExtensionHooks() diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 773b89b62..0f2a0d0cc 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -175,7 +175,6 @@ def bbox(self) -> Optional[List[float]]: def bbox(self, v: Optional[List[float]]) -> None: self._set_property(BBOX_PROP, v) - @property def centroid(self) -> Optional[Dict[str, float]]: """Get or sets coordinates representing the centroid of the item in the asset data CRS. @@ -248,6 +247,7 @@ def ext(obj: T) -> "ProjectionExtension[T]": else: raise ExtensionException(f"File extension does not apply to type {type(obj)}") + class ItemProjectionExtension(ProjectionExtension[ps.Item]): def __init__(self, item: ps.Item): self.item = item @@ -267,10 +267,11 @@ def __init__(self, asset: ps.Asset): def __repr__(self) -> str: return ''.format(self.asset_href) + class ProjectionExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(['proj', 'projection']) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -PROJECTION_EXTENSION_HOOKS = ProjectionExtensionHooks() \ No newline at end of file +PROJECTION_EXTENSION_HOOKS = ProjectionExtensionHooks() diff --git a/pystac/item.py b/pystac/item.py index 5ea7d1039..6d74957e0 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -811,7 +811,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: if not include_self_link: links = [x for x in links if x.rel != 'self'] - assets = {k: v.to_dict() for k, v in self.assets.items() } + assets = {k: v.to_dict() for k, v in self.assets.items()} if self.datetime is not None: self.properties['datetime'] = datetime_to_str(self.datetime) diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 0d23cdb61..f746d64bd 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -356,7 +356,6 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: # code translates the short name IDs used pre-1.0.0-RC1 to the # relevant extension schema uri identifier. - if not version_range.is_single_version(): # Final Checks diff --git a/pystac/utils.py b/pystac/utils.py index 0daefeaae..e9bdf458c 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -232,6 +232,7 @@ def get_opt(option: Optional[T]) -> T: raise ValueError("Cannot get value from None") return option + def get_required(option: Optional[T], obj: Union[str, Any], prop: str) -> T: """ Retrieves an optional value that comes from a required property. If the option is None, throws an RequiredPropertyError with @@ -239,4 +240,4 @@ def get_required(option: Optional[T], obj: Union[str, Any], prop: str) -> T: """ if option is None: raise RequiredPropertyMissing(obj, prop) - return option \ No newline at end of file + return option diff --git a/tests/__init__.py b/tests/__init__.py index cbb512ac6..4c566cbae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,7 @@ import logging import sys + def setup_logging(level: int) -> None: for package in ["pystac", "tests"]: logger = logging.getLogger(package) @@ -13,4 +14,5 @@ def setup_logging(level: int) -> None: ch.setFormatter(formatter) logger.addHandler(ch) + setup_logging(logging.INFO) diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index 83afa0d52..9a611dde6 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -11,6 +11,7 @@ TARGET_VERSION = ps.get_stac_version() + def migrate(path: str) -> None: try: with open(path) as f: @@ -23,19 +24,16 @@ def migrate(path: str) -> None: cur_ver = stac_json['stac_version'] #if not cur_ver == TARGET_VERSION: if True: - print(' - Migrating {} from {} to {}...'.format( - path, cur_ver, TARGET_VERSION)) + print(' - Migrating {} from {} to {}...'.format(path, cur_ver, TARGET_VERSION)) obj = ps.read_dict(stac_json, href=path) migrated = obj.to_dict(include_self_link=False) with open(path, 'w') as f: - json.dump(migrated, f, indent=2) + json.dump(migrated, f, indent=2) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('--file', - metavar='FILE', - help='Only migrate this specific file.') + parser.add_argument('--file', metavar='FILE', help='Only migrate this specific file.') args = parser.parse_args() @@ -59,4 +57,4 @@ def migrate(path: str) -> None: for fname in files: if fname.endswith('.json'): path = os.path.join(root, fname) - migrate(path) \ No newline at end of file + migrate(path) diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index df1a063b6..acec366b1 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -10,7 +10,6 @@ from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension, SummariesExtension from pystac.extensions.hooks import ExtensionHooks - T = TypeVar('T', ps.Catalog, ps.Collection, ps.Item, ps.Asset) SCHEMA_URI = "https://example.com/v2.0/custom-schema.json" @@ -95,6 +94,7 @@ def __init__(self, asset: ps.Asset) -> None: self.additional_read_properties = [asset.owner.extra_fields] super().__init__(None) + class SummariesCustomExtension(SummariesExtension): @property def test_prop(self) -> Optional[RangeSummary[str]]: @@ -114,7 +114,8 @@ class CustomExtensionHooks(ExtensionHooks): def get_object_links(self, obj: ps.STACObject) -> Optional[List[str]]: return [TEST_LINK_REL] - def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: + def migrate(self, obj: Dict[str, Any], version: STACVersionID, + info: STACJSONDescription) -> None: if version < "1.0.0-rc2" and info.object_type == ps.STACObjectType.ITEM: if 'test:old-prop-name' in obj['properties']: obj['properties'][TEST_PROP] = obj['properties']['test:old-prop-name'] @@ -131,4 +132,4 @@ def tearDown(self) -> None: # TODO: Test custom extensions and extension hooks def test_migrates(self): - pass \ No newline at end of file + pass diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index c46b69009..6b3fa7d23 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -39,7 +39,8 @@ def test_asset_checksum(self): asset = item.assets["thumbnail"] # Get - self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", FileExtension.ext(asset).checksum) + self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", + FileExtension.ext(asset).checksum) # Set new_checksum = "90e40210163700a8a6501eccd00b6d3b44ddaed0" @@ -80,4 +81,5 @@ def test_migrates_old_checksum(self): self.assertTrue(FileExtension.has_extension(item)) self.assertEqual( - FileExtension.ext(item.assets['noises']).checksum, "90e40210a30d1711e81a4b11ef67b28744321659") + FileExtension.ext(item.assets['noises']).checksum, + "90e40210a30d1711e81a4b11ef67b28744321659") diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index d187dd5c1..84463dffb 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -26,12 +26,13 @@ def test_apply(self): self.assertFalse(PointcloudExtension.has_extension(item)) PointcloudExtension.add_to(item) - PointcloudExtension.ext(item).apply(1000, 'lidar', 'laszip', - [PointcloudSchema({ - 'name': 'X', - 'size': 8, - 'type': 'floating' - })]) + PointcloudExtension.ext(item).apply( + 1000, 'lidar', 'laszip', + [PointcloudSchema({ + 'name': 'X', + 'size': 8, + 'type': 'floating' + })]) self.assertTrue(PointcloudExtension.has_extension(item)) def test_validate_pointcloud(self): @@ -57,8 +58,8 @@ def test_count(self): # Ensure setting bad count fails validation with self.assertRaises(ps.STACValidationError): - PointcloudExtension.ext(pc_item).count = 'not_an_int' # type:ignore - pc_item.validate() + PointcloudExtension.ext(pc_item).count = 'not_an_int' # type:ignore + pc_item.validate() def test_type(self): pc_item = ps.Item.from_file(self.example_uri) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 9d97c74bd..896108700 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -124,14 +124,16 @@ def test_epsg(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, - ProjectionExtension.ext(proj_item).epsg) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).epsg, + ProjectionExtension.ext(proj_item).epsg) self.assertEqual(ProjectionExtension.ext(asset_prop).epsg, 9999) # Set to Asset ProjectionExtension.ext(asset_no_prop).epsg = 8888 - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).epsg, - ProjectionExtension.ext(proj_item).epsg) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).epsg, + ProjectionExtension.ext(proj_item).epsg) self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, 8888) # Validate @@ -152,15 +154,17 @@ def test_wkt2(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, - ProjectionExtension.ext(proj_item).wkt2) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).wkt2, + ProjectionExtension.ext(proj_item).wkt2) self.assertTrue('TEST_TEXT' in get_opt(ProjectionExtension.ext(asset_prop).wkt2)) # Set to Asset asset_value = "TEST TEXT 2" ProjectionExtension.ext(asset_no_prop).wkt2 = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).wkt2, - ProjectionExtension.ext(proj_item).wkt2) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).wkt2, + ProjectionExtension.ext(proj_item).wkt2) self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, asset_value) # Validate @@ -181,16 +185,18 @@ def test_projjson(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).projjson, - ProjectionExtension.ext(proj_item).projjson) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).projjson, + ProjectionExtension.ext(proj_item).projjson) self.assertEqual(ProjectionExtension.ext(asset_prop).projjson['id']['code'], 9999) # Set to Asset asset_value = deepcopy(PROJJSON) asset_value['id']['code'] = 7777 ProjectionExtension.ext(asset_no_prop).projjson = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).projjson, - ProjectionExtension.ext(proj_item).projjson) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).projjson, + ProjectionExtension.ext(proj_item).projjson) self.assertEqual(ProjectionExtension.ext(asset_no_prop).projjson['id']['code'], 7777) # Validate @@ -216,16 +222,18 @@ def test_geometry(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, - ProjectionExtension.ext(proj_item).geometry) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).geometry, + ProjectionExtension.ext(proj_item).geometry) self.assertEqual( ProjectionExtension.ext(asset_prop).geometry['coordinates'][0][0], [0.0, 0.0]) # Set to Asset asset_value: Dict[str, Any] = {'type': 'Point', 'coordinates': [1.0, 2.0]} ProjectionExtension.ext(asset_no_prop).geometry = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).geometry, - ProjectionExtension.ext(proj_item).geometry) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).geometry, + ProjectionExtension.ext(proj_item).geometry) self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, asset_value) # Validate @@ -251,15 +259,17 @@ def test_bbox(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, - ProjectionExtension.ext(proj_item).bbox) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).bbox, + ProjectionExtension.ext(proj_item).bbox) self.assertEqual(ProjectionExtension.ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) # Set to Asset asset_value = [10.0, 20.0, 30.0, 40.0] ProjectionExtension.ext(asset_no_prop).bbox = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).bbox, - ProjectionExtension.ext(proj_item).bbox) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).bbox, + ProjectionExtension.ext(proj_item).bbox) self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, asset_value) # Validate @@ -281,18 +291,17 @@ def test_centroid(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, - ProjectionExtension.ext(proj_item).centroid) - self.assertEqual(ProjectionExtension.ext(asset_prop).centroid, { - "lat": 0.5, - "lon": 0.3 - }) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).centroid, + ProjectionExtension.ext(proj_item).centroid) + self.assertEqual(ProjectionExtension.ext(asset_prop).centroid, {"lat": 0.5, "lon": 0.3}) # Set to Asset asset_value = {"lat": 1.5, "lon": 1.3} - ProjectionExtension.ext(asset_no_prop).centroid = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).centroid, - ProjectionExtension.ext(proj_item).centroid) + ProjectionExtension.ext(asset_no_prop).centroid = asset_value + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).centroid, + ProjectionExtension.ext(proj_item).centroid) self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, asset_value) # Validate @@ -319,15 +328,17 @@ def test_shape(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, - ProjectionExtension.ext(proj_item).shape) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).shape, + ProjectionExtension.ext(proj_item).shape) self.assertEqual(ProjectionExtension.ext(asset_prop).shape, [16781, 16621]) # Set to Asset asset_value = [1, 2] ProjectionExtension.ext(asset_no_prop).shape = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).shape, - ProjectionExtension.ext(proj_item).shape) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).shape, + ProjectionExtension.ext(proj_item).shape) self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, asset_value) # Validate @@ -349,16 +360,19 @@ def test_transform(self): # Get from Asset asset_no_prop = proj_item.assets['B1'] asset_prop = proj_item.assets['B8'] - self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, - ProjectionExtension.ext(proj_item).transform) - self.assertEqual(ProjectionExtension.ext(asset_prop).transform, - [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0]) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).transform, + ProjectionExtension.ext(proj_item).transform) + self.assertEqual( + ProjectionExtension.ext(asset_prop).transform, + [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0]) # Set to Asset asset_value = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0] ProjectionExtension.ext(asset_no_prop).transform = asset_value - self.assertNotEqual(ProjectionExtension.ext(asset_no_prop).transform, - ProjectionExtension.ext(proj_item).transform) + self.assertNotEqual( + ProjectionExtension.ext(asset_no_prop).transform, + ProjectionExtension.ext(proj_item).transform) self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, asset_value) # Validate diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index ce00e84d1..0008d93d2 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -62,9 +62,10 @@ def test_all(self): observation_direction: sar.ObservationDirection = sar.ObservationDirection.LEFT SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type, - center_frequency, resolution_range, resolution_azimuth, - pixel_spacing_range, pixel_spacing_azimuth, looks_range, - looks_azimuth, looks_equivalent_number, observation_direction) + center_frequency, resolution_range, resolution_azimuth, + pixel_spacing_range, pixel_spacing_azimuth, looks_range, + looks_azimuth, looks_equivalent_number, + observation_direction) self.assertEqual(center_frequency, SarExtension.ext(self.item).center_frequency) self.assertIn(sar.CENTER_FREQUENCY, self.item.properties) @@ -87,7 +88,8 @@ def test_all(self): self.assertEqual(looks_azimuth, SarExtension.ext(self.item).looks_azimuth) self.assertIn(sar.LOOKS_AZIMUTH, self.item.properties) - self.assertEqual(looks_equivalent_number, SarExtension.ext(self.item).looks_equivalent_number) + self.assertEqual(looks_equivalent_number, + SarExtension.ext(self.item).looks_equivalent_number) self.assertIn(sar.LOOKS_EQUIVALENT_NUMBER, self.item.properties) self.assertEqual(observation_direction, SarExtension.ext(self.item).observation_direction) @@ -102,7 +104,8 @@ def test_polarization_must_be_list(self): polarizations = sar.Polarization.HV product_type: str = 'Some product' with self.assertRaises(ps.STACError): - SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type) # type:ignore + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, + product_type) # type:ignore if __name__ == '__main__': diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index 90c59f872..e1327b55b 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -27,8 +27,8 @@ def test_apply(self): TimestampsExtension.add_to(item) self.assertTrue(TimestampsExtension.has_extension(item)) TimestampsExtension.ext(item).apply(published=str_to_datetime("2020-01-03T06:45:55Z"), - expires=str_to_datetime("2020-02-03T06:45:55Z"), - unpublished=str_to_datetime("2020-03-03T06:45:55Z")) + expires=str_to_datetime("2020-02-03T06:45:55Z"), + unpublished=str_to_datetime("2020-03-03T06:45:55Z")) for d in [ TimestampsExtension.ext(item).published, @@ -72,16 +72,18 @@ def test_expires(self): # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(TimestampsExtension.ext(asset_no_prop).expires, - TimestampsExtension.ext(timestamps_item).expires) - self.assertEqual(TimestampsExtension.ext(asset_prop).expires, - str_to_datetime("2018-12-02T00:00:00Z")) + self.assertEqual( + TimestampsExtension.ext(asset_no_prop).expires, + TimestampsExtension.ext(timestamps_item).expires) + self.assertEqual( + TimestampsExtension.ext(asset_prop).expires, str_to_datetime("2018-12-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") - TimestampsExtension.ext(asset_no_prop).expires =asset_value - self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).expires, - TimestampsExtension.ext(timestamps_item).expires) + TimestampsExtension.ext(asset_no_prop).expires = asset_value + self.assertNotEqual( + TimestampsExtension.ext(asset_no_prop).expires, + TimestampsExtension.ext(timestamps_item).expires) self.assertEqual(TimestampsExtension.ext(asset_no_prop).expires, asset_value) # Validate @@ -104,16 +106,18 @@ def test_published(self): # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(TimestampsExtension.ext(asset_no_prop).published, - TimestampsExtension.ext(timestamps_item).published) - self.assertEqual(TimestampsExtension.ext(asset_prop).published, - str_to_datetime("2018-11-02T00:00:00Z")) + self.assertEqual( + TimestampsExtension.ext(asset_no_prop).published, + TimestampsExtension.ext(timestamps_item).published) + self.assertEqual( + TimestampsExtension.ext(asset_prop).published, str_to_datetime("2018-11-02T00:00:00Z")) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") TimestampsExtension.ext(asset_no_prop).published = asset_value - self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).published, - TimestampsExtension.ext(timestamps_item).published) + self.assertNotEqual( + TimestampsExtension.ext(asset_no_prop).published, + TimestampsExtension.ext(timestamps_item).published) self.assertEqual(TimestampsExtension.ext(asset_no_prop).published, asset_value) # Validate @@ -134,16 +138,19 @@ def test_unpublished(self): # Get from Asset asset_no_prop = timestamps_item.assets['red'] asset_prop = timestamps_item.assets['blue'] - self.assertEqual(TimestampsExtension.ext(asset_no_prop).unpublished, - TimestampsExtension.ext(timestamps_item).unpublished) - self.assertEqual(TimestampsExtension.ext(asset_prop).unpublished, - str_to_datetime("2019-01-02T00:00:00Z")) + self.assertEqual( + TimestampsExtension.ext(asset_no_prop).unpublished, + TimestampsExtension.ext(timestamps_item).unpublished) + self.assertEqual( + TimestampsExtension.ext(asset_prop).unpublished, + str_to_datetime("2019-01-02T00:00:00Z")) # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") TimestampsExtension.ext(asset_no_prop).unpublished = asset_value - self.assertNotEqual(TimestampsExtension.ext(asset_no_prop).unpublished, - TimestampsExtension.ext(timestamps_item).unpublished) + self.assertNotEqual( + TimestampsExtension.ext(asset_no_prop).unpublished, + TimestampsExtension.ext(timestamps_item).unpublished) self.assertEqual(TimestampsExtension.ext(asset_no_prop).unpublished, asset_value) # Validate diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 20f74958e..e8e799229 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -105,7 +105,8 @@ def test_all_links(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, + successor) self.item.validate() def test_full_copy(self): @@ -148,7 +149,8 @@ def test_setting_none_clears_link(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, + successor) VersionExtension.ext(self.item).latest = None links = self.item.get_links(version.LATEST) @@ -170,7 +172,8 @@ def test_multiple_link_setting(self): latest1 = make_item(2013) predecessor1 = make_item(2010) successor1 = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest1, predecessor1, successor1) + VersionExtension.ext(self.item).apply(self.version, deprecated, latest1, predecessor1, + successor1) year = 2015 latest2 = make_item(year) @@ -294,7 +297,8 @@ def test_validate_all(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, + successor) self.collection.validate() def test_full_copy(self): @@ -336,7 +340,8 @@ def test_setting_none_clears_link(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, successor) + VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, + successor) VersionExtension.ext(self.collection).latest = None links = self.collection.get_links(version.LATEST) @@ -359,7 +364,7 @@ def test_multiple_link_setting(self): predecessor1 = make_collection(2010) successor1 = make_collection(2012) VersionExtension.ext(self.collection).apply(self.version, deprecated, latest1, predecessor1, - successor1) + successor1) year = 2015 latest2 = make_collection(year) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 3ae002ef5..0b721615b 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -22,10 +22,10 @@ def test_apply(self): ViewExtension.add_to(item) ViewExtension.ext(item).apply(off_nadir=1.0, - incidence_angle=2.0, - azimuth=3.0, - sun_azimuth=4.0, - sun_elevation=5.0) + incidence_angle=2.0, + azimuth=3.0, + sun_azimuth=4.0, + sun_elevation=5.0) self.assertEqual(ViewExtension.ext(item).off_nadir, 1.0) self.assertEqual(ViewExtension.ext(item).incidence_angle, 2.0) @@ -53,15 +53,17 @@ def test_off_nadir(self): # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(ViewExtension.ext(asset_no_prop).off_nadir, - ViewExtension.ext(view_item).off_nadir) + self.assertEqual( + ViewExtension.ext(asset_no_prop).off_nadir, + ViewExtension.ext(view_item).off_nadir) self.assertEqual(ViewExtension.ext(asset_prop).off_nadir, 3.0) # Set to Asset asset_value = 13.0 ViewExtension.ext(asset_no_prop).off_nadir = asset_value - self.assertNotEqual(ViewExtension.ext(asset_no_prop).off_nadir, - ViewExtension.ext(view_item).off_nadir) + self.assertNotEqual( + ViewExtension.ext(asset_no_prop).off_nadir, + ViewExtension.ext(view_item).off_nadir) self.assertEqual(ViewExtension.ext(asset_no_prop).off_nadir, asset_value) # Validate @@ -82,15 +84,17 @@ def test_incidence_angle(self): # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(ViewExtension.ext(asset_no_prop).incidence_angle, - ViewExtension.ext(view_item).incidence_angle) + self.assertEqual( + ViewExtension.ext(asset_no_prop).incidence_angle, + ViewExtension.ext(view_item).incidence_angle) self.assertEqual(ViewExtension.ext(asset_prop).incidence_angle, 4.0) # Set to Asset asset_value = 14.0 ViewExtension.ext(asset_no_prop).incidence_angle = asset_value - self.assertNotEqual(ViewExtension.ext(asset_no_prop).incidence_angle, - ViewExtension.ext(view_item).incidence_angle) + self.assertNotEqual( + ViewExtension.ext(asset_no_prop).incidence_angle, + ViewExtension.ext(view_item).incidence_angle) self.assertEqual(ViewExtension.ext(asset_no_prop).incidence_angle, asset_value) # Validate @@ -111,15 +115,17 @@ def test_azimuth(self): # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, - ViewExtension.ext(view_item).azimuth) + self.assertEqual( + ViewExtension.ext(asset_no_prop).azimuth, + ViewExtension.ext(view_item).azimuth) self.assertEqual(ViewExtension.ext(asset_prop).azimuth, 5.0) # Set to Asset asset_value = 15.0 ViewExtension.ext(asset_no_prop).azimuth = asset_value - self.assertNotEqual(ViewExtension.ext(asset_no_prop).azimuth, - ViewExtension.ext(view_item).azimuth) + self.assertNotEqual( + ViewExtension.ext(asset_no_prop).azimuth, + ViewExtension.ext(view_item).azimuth) self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, asset_value) # Validate @@ -140,15 +146,17 @@ def test_sun_azimuth(self): # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, - ViewExtension.ext(view_item).sun_azimuth) + self.assertEqual( + ViewExtension.ext(asset_no_prop).sun_azimuth, + ViewExtension.ext(view_item).sun_azimuth) self.assertEqual(ViewExtension.ext(asset_prop).sun_azimuth, 1.0) # Set to Asset asset_value = 11.0 ViewExtension.ext(asset_no_prop).sun_azimuth = asset_value - self.assertNotEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, - ViewExtension.ext(view_item).sun_azimuth) + self.assertNotEqual( + ViewExtension.ext(asset_no_prop).sun_azimuth, + ViewExtension.ext(view_item).sun_azimuth) self.assertEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, asset_value) # Validate @@ -169,15 +177,17 @@ def test_sun_elevation(self): # Get from Asset asset_no_prop = view_item.assets['blue'] asset_prop = view_item.assets['red'] - self.assertEqual(ViewExtension.ext(asset_no_prop).sun_elevation, - ViewExtension.ext(view_item).sun_elevation) + self.assertEqual( + ViewExtension.ext(asset_no_prop).sun_elevation, + ViewExtension.ext(view_item).sun_elevation) self.assertEqual(ViewExtension.ext(asset_prop).sun_elevation, 2.0) # Set to Asset asset_value = 12.0 ViewExtension.ext(asset_no_prop).sun_elevation = asset_value - self.assertNotEqual(ViewExtension.ext(asset_no_prop).sun_elevation, - ViewExtension.ext(view_item).sun_elevation) + self.assertNotEqual( + ViewExtension.ext(asset_no_prop).sun_elevation, + ViewExtension.ext(view_item).sun_elevation) self.assertEqual(ViewExtension.ext(asset_no_prop).sun_elevation, asset_value) # Validate diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index 781912732..0698147fc 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -39,9 +39,7 @@ def test_identify(self): self.assertEqual(actual.object_type, example.object_type, msg=msg) version_contained_in_range = actual.version_range.contains(example.stac_version) self.assertTrue(version_contained_in_range, msg=msg) - self.assertEqual(set(actual.extensions), - set(example.extensions), - msg=msg) + self.assertEqual(set(actual.extensions), set(example.extensions), msg=msg) class VersionTest(unittest.TestCase): diff --git a/tests/test_item.py b/tests/test_item.py index b9fdb5818..acce5cffa 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -122,7 +122,8 @@ def test_null_datetime(self): null_dt_item.validate() def test_get_set_asset_datetime(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) item_datetime = item.datetime # No property on asset @@ -304,13 +305,17 @@ def test_common_metadata_providers(self): providers_object_list = [Provider.from_dict(d) for d in providers_dict_list] example_providers_dict_list: List[Dict[str, Any]] = [{ - "name": "ExampleProvider_1", + "name": + "ExampleProvider_1", "roles": ["example_role_1", "example_role_2"], - "url": "https://exampleprovider1.com/" + "url": + "https://exampleprovider1.com/" }, { - "name": "ExampleProvider_2", + "name": + "ExampleProvider_2", "roles": ["example_role_1", "example_role_2"], - "url": "https://exampleprovider2.com/" + "url": + "https://exampleprovider2.com/" }] example_providers_object_list = [Provider.from_dict(d) for d in example_providers_dict_list] @@ -407,7 +412,8 @@ def test_common_metadata_basics(self): self.assertEqual(x.properties['gsd'], example_gsd) def test_asset_start_datetime(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.start_datetime @@ -428,7 +434,8 @@ def test_asset_start_datetime(self): self.assertEqual(cm.start_datetime, item_value) def test_asset_end_datetime(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.end_datetime @@ -449,7 +456,8 @@ def test_asset_end_datetime(self): self.assertEqual(cm.end_datetime, item_value) def test_asset_license(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.license @@ -470,7 +478,8 @@ def test_asset_license(self): self.assertEqual(cm.license, item_value) def test_asset_providers(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = get_opt(cm.providers) @@ -495,7 +504,8 @@ def test_asset_providers(self): self.assertEqual(get_opt(cm.providers)[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.platform @@ -516,7 +526,8 @@ def test_asset_platform(self): self.assertEqual(cm.platform, item_value) def test_asset_instruments(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.instruments @@ -537,7 +548,8 @@ def test_asset_instruments(self): self.assertEqual(cm.instruments, item_value) def test_asset_constellation(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.constellation @@ -558,7 +570,8 @@ def test_asset_constellation(self): self.assertEqual(cm.constellation, item_value) def test_asset_mission(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.mission @@ -579,7 +592,8 @@ def test_asset_mission(self): self.assertEqual(cm.mission, item_value) def test_asset_gsd(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.gsd @@ -600,7 +614,8 @@ def test_asset_gsd(self): self.assertEqual(cm.gsd, item_value) def test_asset_created(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.created @@ -621,7 +636,8 @@ def test_asset_created(self): self.assertEqual(cm.created, item_value) def test_asset_updated(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + item = ps.Item.from_file( + TestCases.get_path('data-files/item/sample-item-asset-properties.json')) cm = item.common_metadata item_value = cm.updated diff --git a/tests/test_layout.py b/tests/test_layout.py index af115d863..d518133a6 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -20,7 +20,11 @@ def test_templates_item_datetime(self): template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') - item = ps.Item('test', geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=dt, properties={}) + item = ps.Item('test', + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=dt, + properties={}) parts = template.get_template_values(item) diff --git a/tests/test_link.py b/tests/test_link.py index e3b79a027..67734fc8b 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -12,10 +12,10 @@ class LinkTest(unittest.TestCase): def setUp(self): self.item = ps.Item(id='test-item', - geometry=None, - bbox=None, - datetime=TEST_DATETIME, - properties={}) + geometry=None, + bbox=None, + datetime=TEST_DATETIME, + properties={}) def test_minimal(self): rel = 'my rel' diff --git a/tests/test_version.py b/tests/test_version.py index d21383c92..04aa8d1a1 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -17,7 +17,6 @@ def tearDown(self): os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = self._prev_env_version ps.set_stac_version(None) - def test_override_stac_version_with_environ(self): override_version = '1.0.0-gamma.2' diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index f296f41f2..8d29388ad 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -53,6 +53,7 @@ ARBITRARY_EXTENT = Extent(spatial=SpatialExtent.from_coordinates(ARBITRARY_GEOM['coordinates']), temporal=TemporalExtent.from_now()) # noqa: E126 + @dataclass class ExampleInfo: path: str @@ -88,13 +89,11 @@ def get_examples_info() -> List[ExampleInfo]: valid = row[4] != 'INVALID' examples.append( - ExampleInfo( - path=path, - object_type=ps.STACObjectType(object_type), - stac_version=stac_version, - extensions=extensions, - valid=valid - )) + ExampleInfo(path=path, + object_type=ps.STACObjectType(object_type), + stac_version=stac_version, + extensions=extensions, + valid=valid)) return examples @staticmethod diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index b0e233dbd..636eebd4f 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -19,9 +19,8 @@ class ValidateTest(unittest.TestCase): def test_validate_current_version(self): - catalog = ps.read_file( - TestCases.get_path('data-files/catalogs/test-case-1/' - 'catalog.json')) + catalog = ps.read_file(TestCases.get_path('data-files/catalogs/test-case-1/' + 'catalog.json')) catalog.validate() collection = ps.read_file( @@ -133,10 +132,10 @@ def test_validates_geojson_with_tuple_coordinates(self): } item = ps.Item(id='test-item', - geometry=geom, - bbox=[-115.308, 36.126, -115.305, 36.129], - datetime=datetime.utcnow(), - properties={}) + geometry=geom, + bbox=[-115.308, 36.126, -115.305, 36.129], + datetime=datetime.utcnow(), + properties={}) # Should not raise. item.validate() From c0737126119b149fff90bb4aa52f16b5419cda03 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:43:40 -0400 Subject: [PATCH 28/51] Switch to pyright mypy isn't able to infer types as well as pyright, and disallows some useful capabilites such as using type variables in non-static contexts (e.g. avoids https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases). This adds a node dependency to the project, which is a downside, but I believe is the right way to go for more advanced type usage than mypy will allow --- scripts/pyright | 73 ++++++++++++++++++++++++++++++++++++ scripts/test | 2 +- tests/extensions/test_sar.py | 4 +- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100755 scripts/pyright diff --git a/scripts/pyright b/scripts/pyright new file mode 100755 index 000000000..e50f0ac2c --- /dev/null +++ b/scripts/pyright @@ -0,0 +1,73 @@ +#!/bin/bash +PATH_TO_PYRIGHT=`which pyright` + +vercomp () { + if [[ $1 == $2 ]] + then + return 0 + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if [[ -z ${ver2[i]} ]] + then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})) + then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + return 2 + fi + done + return 0 +} + +# Node version check +echo "Checking node version..." +NODE_VERSION=`node -v | cut -d'v' -f2` +MIN_NODE_VERSION="10.15.2" +vercomp $MIN_NODE_VERSION $NODE_VERSION +# 1 == gt +if [[ $? -eq 1 ]]; then + echo "Node version ${NODE_VERSION} too old, min expected is ${MIN_NODE_VERSION}, run:" + echo " npm -g upgrade node" + exit -1 +fi + +# Do we need to sudo? +echo "Checking node_modules dir..." +NODE_MODULES=`npm -g root` +SUDO="sudo" +if [ -w "$NODE_MODULES" ]; then + SUDO="" #nop +fi + +# If we can't find pyright, install it. +echo "Checking pyright exists..." +if [ -z "$PATH_TO_PYRIGHT" ]; then + echo "...installing pyright" + ${SUDO} npm install -g pyright +else + # already installed, upgrade to make sure it's current + # this avoids a sudo on launch if we're already current + echo "Checking pyright version..." + CURRENT=`pyright --version | cut -d' ' -f2` + REMOTE=`npm info pyright version` + if [ "$CURRENT" != "$REMOTE" ]; then + echo "...new version of pyright found, upgrading." + ${SUDO} npm upgrade -g pyright + fi +fi + +echo "done." +pyright "$@" \ No newline at end of file diff --git a/scripts/test b/scripts/test index bc1582076..99385fc94 100755 --- a/scripts/test +++ b/scripts/test @@ -18,7 +18,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then usage else # Types - mypy pystac + scripts/pyright pystac tests # Lint flake8 pystac tests diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index 0008d93d2..190ddf1d7 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -104,8 +104,8 @@ def test_polarization_must_be_list(self): polarizations = sar.Polarization.HV product_type: str = 'Some product' with self.assertRaises(ps.STACError): - SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, - product_type) # type:ignore + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, # type:ignore + product_type) if __name__ == '__main__': From b2e2441ddb248ca84c0c51735d5ac3e53791cc97 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:55:06 -0400 Subject: [PATCH 29/51] Flake8 fixes --- pystac/collection.py | 3 ++- pystac/errors.py | 5 +---- pystac/extensions/eo.py | 7 ++++--- pystac/extensions/file.py | 7 ++++--- pystac/extensions/item_assets.py | 2 +- pystac/extensions/sar.py | 5 +++-- pystac/extensions/sat.py | 2 +- pystac/extensions/scientific.py | 2 +- pystac/extensions/timestamps.py | 2 +- pystac/extensions/version.py | 2 +- pystac/extensions/view.py | 2 +- pystac/serialization/migrate.py | 2 +- pystac/stac_object.py | 2 -- pystac/utils.py | 2 +- tests/data-files/change_stac_version.py | 3 +-- tests/extensions/test_sar.py | 7 +++++-- tests/validation/test_validate.py | 2 -- 17 files changed, 28 insertions(+), 29 deletions(-) diff --git a/pystac/collection.py b/pystac/collection.py index d6ec7ba16..1f1057484 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,6 +1,7 @@ from copy import (copy, deepcopy) from datetime import datetime as Datetime -from typing import Any, Dict, Generic, Iterable, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast +from typing import (Any, Dict, Generic, Iterable, List, Optional, TYPE_CHECKING, Tuple, Type, + TypeVar, Union, cast) import dateutil.parser from dateutil import tz diff --git a/pystac/errors.py b/pystac/errors.py index b696d3e12..c95f649dc 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -29,9 +29,6 @@ class RequiredPropertyMissing(Exception): error message, or be a string that describes the object. prop: The property that is missing """ - def __init__(self, - obj: Union[str, Any], - prop: str, - msg: Optional[str] = None) -> None: + def __init__(self, obj: Union[str, Any], prop: str, msg: Optional[str] = None) -> None: msg = msg or f"{repr(obj)} does not have required property {prop}" super().__init__(msg) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 97a459bc8..0e4061bca 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -3,7 +3,8 @@ from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension +from pystac.extensions.base import (ExtensionException, ExtensionManagementMixin, + PropertiesExtension, SummariesExtension) from pystac.extensions.hooks import ExtensionHooks from pystac.extensions import view from pystac.serialization.identify import STACJSONDescription, STACVersionID @@ -414,7 +415,7 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, if 'eo:{}'.format(field) in obj['properties']: if 'stac_extensions' not in obj: obj['stac_extensions'] = [] - if not view.SCHEMA_URI in obj['stac_extensions']: + if view.SCHEMA_URI not in obj['stac_extensions']: obj['stac_extensions'].append(view.SCHEMA_URI) if not 'view:{}'.format(field) in obj['properties']: obj['properties']['view:{}'.format(field)] = \ @@ -442,4 +443,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, super().migrate(obj, version, info) -EO_EXTENSION_HOOKS: ExtensionHooks = EOExtensionHooks() \ No newline at end of file +EO_EXTENSION_HOOKS: ExtensionHooks = EOExtensionHooks() diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 503004094..bd733f7dc 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,9 +1,10 @@ import enum -from pystac.serialization.identify import OldExtensionShortIDs, STACJSONDescription, STACVersionID +from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID) from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension +from pystac.extensions.base import (ExtensionException, ExtensionManagementMixin, + PropertiesExtension, SummariesExtension) from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt @@ -236,4 +237,4 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, obj['assets'][key][CHECKSUM_PROP] = old_checksum[key] -FILE_EXTENSION_HOOKS = FileExtensionHooks() \ No newline at end of file +FILE_EXTENSION_HOOKS = FileExtensionHooks() diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index e79a9f7c2..72799d218 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -114,7 +114,7 @@ class ItemAssetsExtensionHooks(ExtensionHooks): def migrate(self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: - # Handel that the "item-assets" extension had the id of "assets", before + # Handle that the "item-assets" extension had the id of "assets", before # collection assets (since removed) took over the ID of "assets" if version < '1.0.0-beta.1' and 'asset' in info.extensions: if 'assets' in obj: diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index aeba4ee43..087e184fc 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -331,11 +331,12 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, obj['properties']['instruments'] = [obj['properties']['sar:instrument']] del obj['properties']['sar:instrument'] - if 'sar:constellation' in obj['properties'] and 'constellation' not in obj['properties']: + if ('sar:constellation' in obj['properties'] + and 'constellation' not in obj['properties']): obj['properties']['constellation'] = obj['properties']['sar:constellation'] del obj['properties']['sar:constellation'] super().migrate(obj, version, info) -SAR_EXTENSION_HOOKS: ExtensionHooks = SarExtensionHooks() \ No newline at end of file +SAR_EXTENSION_HOOKS: ExtensionHooks = SarExtensionHooks() diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index bbb377a73..4aff5660c 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -122,4 +122,4 @@ class SatExtensionHooks(ExtensionHooks): stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -SAT_EXTENSION_HOOKS = SatExtensionHooks() \ No newline at end of file +SAT_EXTENSION_HOOKS = SatExtensionHooks() diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index ff7002b88..649ee8a9d 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -215,4 +215,4 @@ class ScientificExtensionHooks(ExtensionHooks): [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) -SCIENTIFIC_EXTENSION_HOOKS = ScientificExtensionHooks() \ No newline at end of file +SCIENTIFIC_EXTENSION_HOOKS = ScientificExtensionHooks() diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index acf8dfeb2..e372868ce 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -139,4 +139,4 @@ class TimestampsExtensionHooks(ExtensionHooks): stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -TIMESTAMPS_EXTENSION_HOOKS = TimestampsExtensionHooks() \ No newline at end of file +TIMESTAMPS_EXTENSION_HOOKS = TimestampsExtensionHooks() diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index ae8cd1f9d..c387a1353 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -181,4 +181,4 @@ def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: return None -VERSION_EXTENSION_HOOKS: ExtensionHooks = VersionExtensionHooks() \ No newline at end of file +VERSION_EXTENSION_HOOKS: ExtensionHooks = VersionExtensionHooks() diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index bcb9b759b..b00d40e92 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -171,4 +171,4 @@ class ViewExtensionHooks(ExtensionHooks): stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) -VIEW_EXTENSION_HOOKS = ViewExtensionHooks() \ No newline at end of file +VIEW_EXTENSION_HOOKS = ViewExtensionHooks() diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 041ff59d1..3a02a4ac0 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -81,7 +81,7 @@ def _get_object_migrations( def _get_removed_extension_migrations( ) -> Dict[str, Tuple[Optional[List["STACObjectType_Type"]], Optional[Callable[ - [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]]]: + [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]]]: # noqa """Handles removed extensions. This does not handle renamed extension or extensions that were absorbed diff --git a/pystac/stac_object.py b/pystac/stac_object.py index ef36d27ae..b011f58ed 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -7,11 +7,9 @@ from pystac.link import Link from pystac.stac_io import STAC_IO from pystac.utils import (is_absolute_href, make_absolute_href) -#from pystac.extensions import ExtensionError if TYPE_CHECKING: from pystac.catalog import Catalog as Catalog_Type - #from pystac.extensions.base import STACObjectExtension as STACObjectExtension_Type class STACObjectType(str, Enum): diff --git a/pystac/utils.py b/pystac/utils.py index e9bdf458c..a874beb18 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -220,7 +220,7 @@ def get_opt(option: Optional[T]) -> T: """ Retrieves the value of the Optional type. If the Optional is None, this will raise a value error. - Use this to get a propertly typed value from an optional + Use this to get a properly typed value from an optional in contexts where you can be certain the value is not None. If there is potential for a non-None value, it's best to handle the None case of the optional instead of using this method. diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index 9a611dde6..aaf579398 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -22,8 +22,7 @@ def migrate(path: str) -> None: if 'stac_version' in stac_json: cur_ver = stac_json['stac_version'] - #if not cur_ver == TARGET_VERSION: - if True: + if not cur_ver == TARGET_VERSION: print(' - Migrating {} from {} to {}...'.format(path, cur_ver, TARGET_VERSION)) obj = ps.read_dict(stac_json, href=path) migrated = obj.to_dict(include_self_link=False) diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index 190ddf1d7..dcc3c2db2 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -104,8 +104,11 @@ def test_polarization_must_be_list(self): polarizations = sar.Polarization.HV product_type: str = 'Some product' with self.assertRaises(ps.STACError): - SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, # type:ignore - product_type) + SarExtension.ext(self.item).apply( + mode, + frequency_band, + polarizations, # type:ignore + product_type) if __name__ == '__main__': diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 636eebd4f..6a32198cd 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -34,8 +34,6 @@ def test_validate_current_version(self): def test_validate_examples(self): for example in TestCases.get_examples_info(): - #if example.path != "/home/rob/proj/stac/pystac/tests/data-files/examples/landsat-0.6.0/156/029/2015-01-01/LC81560292015001LGN00.json": - # continue with self.subTest(example.path): stac_version = example.stac_version path = example.path From f6e1716503bb7ce557d3a8392d2836f9ecdd5fab Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 01:59:02 -0400 Subject: [PATCH 30/51] Remove dataclasses usage as we're still supporting python 3.6 --- tests/utils/test_cases.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 8d29388ad..d3c83f3f4 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -1,4 +1,3 @@ -from dataclasses import dataclass import os from datetime import datetime import csv @@ -54,13 +53,14 @@ temporal=TemporalExtent.from_now()) # noqa: E126 -@dataclass class ExampleInfo: - path: str - object_type: ps.STACObjectType - stac_version: str - extensions: List[str] - valid: bool + def __init__(self, path: str, object_type: ps.STACObjectType, stac_version: str, + extensions: List[str], valid: bool) -> None: + self.path = path + self.object_type = object_type + self.stac_version = stac_version + self.extensions = extensions + self.valid = valid class TestCases: From d0c6cc546eb915e038fa0a44207e49a58ec4a90c Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 13:15:37 -0400 Subject: [PATCH 31/51] Refactor STAC_IO to StacIO --- pystac/__init__.py | 11 +- pystac/catalog.py | 6 + pystac/link.py | 14 +- pystac/serialization/__init__.py | 3 +- pystac/serialization/common_properties.py | 4 +- pystac/stac_io.py | 230 +++++++++++++++++++--- pystac/stac_object.py | 113 +++-------- pystac/validation/__init__.py | 9 +- pystac/validation/stac_validator.py | 4 +- requirements-dev.txt | 3 + setup.py | 3 +- tests/data-files/get_examples.py | 2 +- tests/extensions/test_label.py | 4 +- tests/extensions/test_sar.py | 1 + tests/serialization/test_identify.py | 3 +- tests/serialization/test_migrate.py | 5 +- tests/test_catalog.py | 44 ++--- tests/test_item.py | 2 +- tests/test_writing.py | 8 +- tests/utils/stac_io_mock.py | 42 ++-- tests/validation/test_validate.py | 7 +- 21 files changed, 324 insertions(+), 194 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index ea70bef75..5a980b1b4 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore -from pystac.stac_io import STAC_IO # type:ignore +from pystac.stac_io import StacIO # type:ignore from pystac.stac_object import (STACObject, STACObjectType) # type:ignore from pystac.media_type import MediaType # type:ignore from pystac.link import (Link, HIERARCHICAL_LINKS) # type:ignore @@ -99,7 +99,8 @@ def write_file(obj: STACObject, def read_dict(d: Dict[str, Any], href: Optional[str] = None, - root: Optional[Catalog] = None) -> STACObject: + root: Optional[Catalog] = None, + stac_io: Optional[StacIO] = None) -> STACObject: """Reads a STAC object from a dict representing the serialized JSON version of the STAC object. @@ -115,5 +116,9 @@ def read_dict(d: Dict[str, Any], root (Catalog or Collection): Optional root of the catalog for this object. If provided, the root's resolved object cache can be used to search for previously resolved instances of the STAC object. + stac_io: Optional StacIO instance to use for reading. If None, the + default instance will be used. """ - return STAC_IO.stac_object_from_dict(d, href, root) + if stac_io is None: + stac_io = StacIO.default() + return stac_io.stac_object_from_dict(d, href, root) diff --git a/pystac/catalog.py b/pystac/catalog.py index fd220da35..d9eb4240c 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -110,6 +110,12 @@ class Catalog(STACObject): STAC_OBJECT_TYPE = ps.STACObjectType.CATALOG + _stac_io: Optional[ps.StacIO] = None + """Optional instance of StacIO that will be used by default + for any IO operations on objects contained by this catalog. + Set while reading in a catalog. This is set when a catalog + is read by a StacIO instance.""" + DEFAULT_FILE_NAME = "catalog.json" """Default file name that will be given to this STAC object in a canonical format.""" def __init__(self, diff --git a/pystac/link.py b/pystac/link.py index ce1195e6f..281159baa 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -2,7 +2,6 @@ from typing import Any, Dict, Optional, TYPE_CHECKING, Union, cast import pystac as ps -from pystac.stac_io import STAC_IO from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) if TYPE_CHECKING: @@ -176,11 +175,22 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": target_href = make_absolute_href(target_href, start_href) obj = None + stac_io: Optional[ps.StacIO] = None + if root is not None: obj = root._resolved_objects.get_by_href(target_href) + stac_io = root._stac_io if obj is None: - obj = STAC_IO.read_stac_object(target_href, root=root) + + if stac_io is None: + if self.owner is not None: + if isinstance(self.owner, ps.Catalog): + stac_io = self.owner._stac_io + if stac_io is None: + stac_io = ps.StacIO.default() + + obj = stac_io.read_stac_object(target_href, root=root) obj.set_self_href(target_href) if root is not None: obj = root._resolved_objects.get_or_cache(obj) diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index 015d6bc9b..45e9403dd 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -27,8 +27,7 @@ def stac_object_from_dict(d: Dict[str, Any], If provided, the root's resolved object cache can be used to search for previously resolved instances of the STAC object. - Note: This is used internally in STAC_IO to deserialize STAC Objects. - It is in the top level __init__ in order to avoid circular dependencies. + Note: This is used internally in StacIO instances to deserialize STAC Objects. """ if identify_stac_object_type(d) == ps.STACObjectType.ITEM: collection_cache = None diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index 72ea7b925..283ee42fb 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -11,6 +11,8 @@ def merge_common_properties(item_dict: Dict[str, Any], json_href: Optional[str] = None) -> bool: """Merges Collection properties into an Item. + Note: This is only applicable to reading old STAC versions (pre 1.0.0-beta.1). + Args: item_dict (dict): JSON dict of the Item which properties should be merged into. @@ -70,7 +72,7 @@ def merge_common_properties(item_dict: Dict[str, Any], collection = collection_cache.get_by_href(collection_href) if collection is None: - collection = ps.STAC_IO.read_json(collection_href) + collection = ps.StacIO.default().read_json(collection_href) if collection is not None: collection_id = None diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 135c889d4..bef68b14a 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -1,61 +1,231 @@ +from abc import ABC, abstractmethod import os import json -from typing import Any, Callable, Dict, Optional, TYPE_CHECKING +from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union from urllib.parse import urlparse from urllib.request import urlopen from urllib.error import HTTPError +import pystac as ps import pystac.serialization +# Use orjson if available +try: + import orjson +except ImportError: + orjson = None + if TYPE_CHECKING: from pystac.stac_object import STACObject as STACObject_Type from pystac.catalog import Catalog as Catalog_Type + from pystac.link import Link as Link_Type -class STAC_IO: - """Methods used to read and save STAC json. - Allows users of the library to set their own methods - (e.g. for reading and writing from cloud storage) - """ - @staticmethod - def default_read_text_method(uri: str) -> str: - """Default method for reading text. Only handles local file paths.""" - parsed = urlparse(uri) +class StacIO(ABC): + _default_io: Optional[Type["StacIO"]] = None + + @abstractmethod + def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) -> str: + """Read text from the given URI. + + The source to read from can be specified + as a string or a Link. If it's a string, it's the URL of the HREF from which to + read. When reading links, PySTAC will pass in the entire link body. + This enables implementations to utilize additional link information, + e.g. the "post" information in a pagination link from a STAC API search. + + Args: + source (str or pystac.Link): The source to read from. + + Returns: + str: The text contained in the file at the location specified by the uri. + """ + raise NotImplementedError("read_text not implemented") + + @abstractmethod + def write_text(self, dest: Union[str, "Link_Type"], txt: str, *args: Any, + **kwargs: Any) -> None: + """Write the given text to a file at the given URI. + + The destination to write to from can be specified + as a string or a Link. If it's a string, it's the URL of the HREF from which to + read. When writing based on links links, PySTAC will pass in the entire link body. + + Args: + dest (str or pystac.Link): The destination to write to. + txt (str): The text to write. + """ + raise NotImplementedError("write_text not implemented") + + def _json_loads(self, txt: str, source: Union[str, "Link_Type"]) -> Dict[str, Any]: + if orjson is not None: + return orjson.loads(txt) + else: + return json.loads(self.read_text(txt)) + + def _json_dumps(self, json_dict: Dict[str, Any], source: Union[str, "Link_Type"]) -> str: + if orjson is not None: + return orjson.dumps(json_dict, option=orjson.OPT_INDENT_2).decode('utf-8') + else: + return json.dumps(json_dict, indent=2) + + def stac_object_from_dict(self, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + result = pystac.serialization.stac_object_from_dict(d, href, root) + if isinstance(result, ps.Catalog): + # Set the stac_io instance for usage by io operations + # where this catalog is the root. + result._stac_io = self + return result + + def read_json(self, source: Union[str, "Link_Type"]) -> Dict[str, Any]: + """Read a dict from the given source. + + See :func:`StacIO.read_text ` for usage of + str vs Link as a parameter. + + Args: + source (str or Link): The source from which to read. + + Returns: + dict: A dict representation of the JSON contained in the file at the + given source. + """ + txt = self.read_text(source) + return self._json_loads(txt, source) + + def read_stac_object(self, + source: Union[str, "Link_Type"], + root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + """Read a STACObject from a JSON file at the given source. + + See :func:`StacIO.read_text ` for usage of + str vs Link as a parameter. + + Args: + source (str or pystac.Link): The source from which to read. + root (Catalog or Collection): Optional root of the catalog for this object. + If provided, the root's resolved object cache can be used to search for + previously resolved instances of the STAC object. + + Returns: + STACObject: The deserialized STACObject from the serialized JSON + contained in the file at the given uri. + """ + d = self.read_json(source) + href = source if isinstance(source, str) else source.get_absolute_href() + return self.stac_object_from_dict(d, href=href, root=root) + + def save_json(self, dest: Union[str, "Link_Type"], json_dict: Dict[str, Any]) -> None: + """Write a dict to the given URI as JSON. + + See :func:`StacIO.write_text ` for usage of + str vs Link as a parameter. + + Args: + dest (str or pystac.Link): The destination file to write the text to. + json_dict (dict): The JSON dict to write. + """ + txt = self._json_dumps(json_dict, dest) + self.write_text(dest, txt) + + @classmethod + def set_default(cls, stac_io_class: Type["StacIO"]) -> None: + """Set the default StacIO instance to use.""" + cls._default_io = stac_io_class + + @classmethod + def default(cls) -> "StacIO": + if cls._default_io is None: + cls._default_io = DefaultStacIO + + return cls._default_io() + + +class DefaultStacIO(StacIO): + def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) -> str: + if isinstance(source, str): + href = source + else: + href = source.get_absolute_href() + if href is None: + raise IOError(f"Could not get an absolute HREF from link {source}") + + parsed = urlparse(href) if parsed.scheme != '': try: - with urlopen(uri) as f: + with urlopen(href) as f: return f.read().decode('utf-8') except HTTPError as e: - raise Exception("Could not read uri {}".format(uri)) from e + raise Exception("Could not read uri {}".format(href)) from e else: - with open(uri) as f: + with open(href) as f: return f.read() - @staticmethod - def default_write_text_method(uri: str, txt: str) -> None: - """Default method for writing text. Only handles local file paths.""" - dirname = os.path.dirname(uri) + def write_text(self, dest: Union[str, "Link_Type"], txt: str, *args: Any, + **kwargs: Any) -> None: + if isinstance(dest, str): + href = dest + else: + href = dest.get_absolute_href() + if href is None: + raise IOError(f"Could not get an absolute HREF from link {dest}") + + dirname = os.path.dirname(href) if dirname != '' and not os.path.isdir(dirname): os.makedirs(dirname) - with open(uri, 'w') as f: + with open(href, 'w') as f: f.write(txt) - read_text_method: Callable[[str], str] = default_read_text_method - """Users of PySTAC can replace the read_text_method in order - to expand the ability of PySTAC to read different file systems. - For example, a client of the library might replace this class - member in it's own __init__.py with a method that can read from - cloud storage. + +class DuplicateObjectKeyError(Exception): + pass + + +class DuplicateKeyReportingMixin(StacIO): + """A mixin for StacIO implementations that will report + on duplicate keys in the JSON being read in. + + See https://github.com/stac-utils/pystac/issues/313 """ + def _json_loads(self, txt: str, source: Union[str, "Link_Type"]) -> Dict[str, Any]: + return json.loads(txt, object_pairs_hook=self.duplicate_object_names_report_builder(source)) + + @staticmethod + def duplicate_object_names_report_builder( + source: Union[str, "Link_Type"]) -> Callable[[List[Tuple[str, Any]]], Dict[str, Any]]: + def report_duplicate_object_names(object_pairs: List[Tuple[str, Any]]) -> Dict[str, Any]: + result: Dict[str, Any] = {} + for key, value in object_pairs: + if key in result: + url = source if isinstance(source, str) else source.get_absolute_href() + raise DuplicateObjectKeyError(f"Found duplicate object name “{key}” in “{url}”") + else: + result[key] = value + return result + + return report_duplicate_object_names + + +class STAC_IO: + """DEPRECATED: Methods used to read and save STAC json. + Allows users of the library to set their own methods + (e.g. for reading and writing from cloud storage) - write_text_method: Callable[[str, str], None] = default_write_text_method - """Users of PySTAC can replace the write_text_method in order - to expand the ability of PySTAC to write to different file systems. - For example, a client of the library might replace this class - member in it's own __init__.py with a method that can read from - cloud storage. + Note: The static methods of this class are deprecated. Move to using + instance methods of a specific instance of StacIO. """ + @staticmethod + def read_text_method(uri: str) -> str: + return StacIO.default().read_text(uri) + + @staticmethod + def write_text_method(uri: str, txt: str) -> None: + """Default method for writing text.""" + return StacIO.default().write_text(uri, txt) @staticmethod def stac_object_from_dict(d: Dict[str, Any], diff --git a/pystac/stac_object.py b/pystac/stac_object.py index b011f58ed..7653c1084 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -5,7 +5,6 @@ import pystac as ps from pystac import STACError from pystac.link import Link -from pystac.stac_io import STAC_IO from pystac.utils import (is_absolute_href, make_absolute_href) if TYPE_CHECKING: @@ -278,14 +277,21 @@ def get_stac_objects(self, rel: str) -> Iterable["STACObject"]: link.resolve_stac_object(root=self.get_root()) yield cast("STACObject", link.target) - def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = None) -> None: + def save_object(self, + include_self_link: bool = True, + dest_href: Optional[str] = None, + stac_io: Optional[ps.StacIO] = None) -> None: """Saves this STAC Object to it's 'self' HREF. Args: - include_self_link (bool): If this is true, include the 'self' link with this object. - Otherwise, leave out the self link. - dest_href (str): Optional HREF to save the file to. If None, the object will be saved - to the object's self href. + include_self_link (bool): If this is true, include the 'self' link with this object. + Otherwise, leave out the self link. + dest_href (str): Optional HREF to save the file to. If None, the object will be saved + to the object's self href. + stac_io: Optional instance of StacIO to use. If not provided, will use the + instance set on the object's root if available, otherwise will use the + default instance. + Raises: :class:`~pystac.STACError`: If no self href is set, this error will be raised. @@ -295,6 +301,16 @@ def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = STAC best practices document `_ """ + if stac_io is None: + root = self.get_root() + if root is not None: + root_stac_io = root._stac_io + if root_stac_io is not None: + stac_io = root_stac_io + + if stac_io is None: + stac_io = ps.StacIO.default() + if dest_href is None: self_href = self.get_self_href() if self_href is None: @@ -302,7 +318,7 @@ def save_object(self, include_self_link: bool = True, dest_href: Optional[str] = 'Self HREF must be set before saving without an explicit dest_href.') dest_href = self_href - STAC_IO.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) + stac_io.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) def full_copy(self, root: Optional["Catalog_Type"] = None, @@ -420,21 +436,26 @@ def clone(self) -> "STACObject": pass @classmethod - def from_file(cls, href: str) -> "STACObject": + def from_file(cls, href: str, stac_io: Optional[ps.StacIO] = None) -> "STACObject": """Reads a STACObject implementation from a file. Args: href (str): The HREF to read the object from. + stac_io: Optional instance of StacIO to use. If not provided, will use the + default instance. Returns: The specific STACObject implementation class that is represented by the JSON read from the file located at HREF. """ + if stac_io is None: + stac_io = ps.StacIO.default() + if not is_absolute_href(href): href = make_absolute_href(href) - d = STAC_IO.read_json(href) + d = stac_io.read_json(href) - o = STAC_IO.stac_object_from_dict(d, href, None) + o = stac_io.stac_object_from_dict(d, href, None) # Set the self HREF, if it's not already set to something else. if o.get_self_href() is None: @@ -471,75 +492,3 @@ def from_dict(cls, STACObject: The STACObject parsed from this dict. """ pass - - -# class ExtensionIndex: -# """Defines methods for accessing extension functionality. - -# To access a specific extension, use the __getitem__ on this class with the -# extension ID:: - -# # Access the "bands" property on the eo extension. -# item.ext['eo'].bands -# """ -# def __init__(self, stac_object: STACObject) -> None: -# self.stac_object = stac_object - -# def __getitem__(self, extension_id: str) -> "STACObjectExtension_Type": -# """Gets the extension object for the given extension. - -# Returns: -# CatalogExtension or CollectionExtension or ItemExtension: The extension object -# through which you can access the extension functionality for the extension represented -# by the extension_id. -# """ -# # Check to make sure this is a registered extension. -# if not ps.STAC_EXTENSIONS.is_registered_extension(extension_id): -# raise ExtensionError("'{}' is not an extension " -# "registered with PySTAC".format(extension_id)) - -# if not self.implements(extension_id): -# raise ExtensionError("{} does not implement the {} extension. " -# "Use the 'ext.enable' method to enable this extension " -# "first.".format(self.stac_object, extension_id)) - -# return ps.STAC_EXTENSIONS.extend_object(extension_id, self.stac_object) - -# def __getattr__(self, extension_id: str) -> "STACObjectExtension_Type": -# """Gets an extension based on a dynamic attribute. - -# This takes the attribute name and passes it to __getitem__. - -# This allows the following two lines to be equivalent:: - -# item.ext["label"].label_properties -# item.ext.label.label_properties -# """ -# if extension_id.startswith('__') and hasattr(ExtensionIndex, extension_id): -# return self.__getattribute__(extension_id) -# return self[extension_id] - -# def enable(self, extension_id: str) -> None: -# """Enables a stac extension for the given object. If the object already -# enables the extension, no action is taken. If it does not, the extension ID is -# added to the object's stac_extension property. - -# Args: -# extension_id (str): The extension ID representing the extension -# the object should implement - -# """ -# ps.STAC_EXTENSIONS.enable_extension(extension_id, self.stac_object) - -# def implements(self, extension_id: str) -> bool: -# """Returns true if the associated object implements the given extension. - -# Args: -# extension_id (str): The extension ID to check - -# Returns: -# [bool]: True if the object implements the extensions - i.e. if -# the extension ID is in the "stac_extensions" property. -# """ -# return (self.stac_object.stac_extensions is not None -# and extension_id in self.stac_object.stac_extensions) diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 77d4552ee..6273fde13 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -111,7 +111,7 @@ def _get_uri(ext: str) -> Optional[str]: extensions, href) -def validate_all(stac_dict: Dict[str, Any], href: str) -> None: +def validate_all(stac_dict: Dict[str, Any], href: str, stac_io: Optional[ps.StacIO] = None) -> None: """Validate STAC JSON and all contained catalogs, collections and items. If this stac_dict represents a catalog or collection, this method will @@ -122,11 +122,16 @@ def validate_all(stac_dict: Dict[str, Any], href: str) -> None: stac_dict (dict): Dictionary that is the STAC json of the object. href (str): HREF of the STAC object being validated. Used for error reporting and resolving relative links. + stac_io: Optional StacIO instance to use for reading hrefs. If None, + the StacIO.default() instance is used. Raises: STACValidationError: This will raise a STACValidationError if this or any contained catalog, collection or item has a validation error. """ + if stac_io is None: + stac_io = ps.StacIO.default() + info = identify_stac_object(stac_dict) # Validate this object @@ -148,7 +153,7 @@ def validate_all(stac_dict: Dict[str, Any], href: str) -> None: if rel in ['item', 'child']: link_href = make_absolute_href(cast(str, link.get('href')), start_href=href) if link_href is not None: - d = ps.STAC_IO.read_json(link_href) + d = stac_io.read_json(link_href) validate_all(d, link_href) diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index e4da8fb2b..3ada2e782 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -4,7 +4,7 @@ from pystac.stac_object import STACObjectType from typing import Any, Dict, List, Optional, Tuple -from pystac import STAC_IO +import pystac as ps from pystac.validation import STACValidationError from pystac.validation.schema_uri_map import DefaultSchemaUriMap, SchemaUriMap @@ -134,7 +134,7 @@ def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: if schema_uri not in self.schema_cache: - s = json.loads(STAC_IO.read_text(schema_uri)) + s = json.loads(ps.StacIO.default().read_text(schema_uri)) self.schema_cache[schema_uri] = s schema = self.schema_cache[schema_uri] diff --git a/requirements-dev.txt b/requirements-dev.txt index 5feddb12f..e5ebda679 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,3 +12,6 @@ sphinxcontrib-fulltoc==1.2.0 sphinxcontrib-napoleon==0.7 nbsphinx==0.7.1 coverage==5.2.* + +# optional dependencies +orjson==3.5.2 diff --git a/setup.py b/setup.py index 597d6bb05..6851f848a 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,8 @@ include_package_data=False, install_requires=["python-dateutil>=2.7.0"], extras_require={ - "validation": ["jsonschema==3.2.0"] + "validation": ["jsonschema>=3.0"], + "orjson": ["orjson>=3.5"] }, license="Apache Software License 2.0", zip_safe=False, diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index 6c640e7ab..3f3e46c0a 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -23,7 +23,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: if rel is not None and rel == 'collection': href: str = link['href'] try: - json.loads(ps.STAC_IO.read_text(href)) + json.loads(ps.StacIO.default().read_text(href)) filtered_links.append(link) except (HTTPError, FileNotFoundError, json.decoder.JSONDecodeError): print('===REMOVING UNREADABLE COLLECTION AT {}'.format(href)) diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index e86c6c599..2559dd606 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory import pystac as ps -from pystac import (Catalog, Item, CatalogType, STAC_IO) +from pystac import (Catalog, Item, CatalogType) from pystac.extensions.label import (LabelExtension, LabelClasses, LabelCount, LabelOverview, LabelStatistics, LabelType) import pystac.validation @@ -38,7 +38,7 @@ def test_from_file(self): label_example_2.validate() def test_from_file_pre_081(self): - d = STAC_IO.read_json(self.label_example_1_uri) + d = ps.StacIO.default().read_json(self.label_example_1_uri) d['stac_version'] = '0.8.0-rc1' d['properties']['label:property'] = d['properties']['label:properties'] diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index dcc3c2db2..407a26fae 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -31,6 +31,7 @@ def test_required(self): frequency_band: sar.FrequencyBand = sar.FrequencyBand.P polarizations: List[sar.Polarization] = [sar.Polarization.HV, sar.Polarization.VH] product_type: str = 'Some product' + SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type) self.assertEqual(mode, SarExtension.ext(self.item).instrument_mode) self.assertIn(sar.INSTRUMENT_MODE, self.item.properties) diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index 0698147fc..8012ae549 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -2,7 +2,6 @@ from urllib.error import HTTPError import pystac as ps -from pystac import STAC_IO from pystac.cache import CollectionCache from pystac.serialization import (identify_stac_object, identify_stac_object_type, merge_common_properties) @@ -20,7 +19,7 @@ def test_identify(self): for example in self.examples: with self.subTest(example.path): path = example.path - d = STAC_IO.read_json(path) + d = ps.StacIO.default().read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: try: merge_common_properties(d, diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index c0dbaaa8d..9c827579c 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -3,7 +3,6 @@ import unittest import pystac as ps -from pystac import (STAC_IO, STACObject) from pystac.cache import CollectionCache from pystac.serialization import (identify_stac_object, identify_stac_object_type, merge_common_properties, migrate_to_latest) @@ -22,7 +21,7 @@ def test_migrate(self): with self.subTest(example.path): path = example.path - d = STAC_IO.read_json(path) + d = ps.StacIO.default().read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: merge_common_properties(d, json_href=path, collection_cache=collection_cache) @@ -42,7 +41,7 @@ def test_migrate(self): # Test that PySTAC can read it without errors. if info.object_type != ps.STACObjectType.ITEMCOLLECTION: - self.assertIsInstance(ps.read_dict(migrated_d, href=path), STACObject) + self.assertIsInstance(ps.read_dict(migrated_d, href=path), ps.STACObject) def test_migrates_removed_extension(self): item = ps.Item.from_file( diff --git a/tests/test_catalog.py b/tests/test_catalog.py index e88f98dbb..56230bb75 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -19,7 +19,7 @@ def test_determine_type_for_absolute_published(self): cat = TestCases.test_case_1() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat_json = ps.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.StacIO.default().read_json(os.path.join(tmp_dir, 'catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.ABSOLUTE_PUBLISHED) @@ -28,13 +28,13 @@ def test_determine_type_for_relative_published(self): cat = TestCases.test_case_2() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) - cat_json = ps.STAC_IO.read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.StacIO.default().read_json(os.path.join(tmp_dir, 'catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.RELATIVE_PUBLISHED) def test_determine_type_for_self_contained(self): - cat_json = ps.STAC_IO.read_json( + cat_json = ps.StacIO.default().read_json( TestCases.get_path('data-files/catalogs/test-case-1/catalog.json')) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.SELF_CONTAINED) @@ -778,26 +778,26 @@ def test_set_hrefs_manually(self): def test_collections_cache_correctly(self): catalogs = TestCases.all_test_catalogs() + mock_io = MockStacIO() for cat in catalogs: - with MockStacIO() as mock_io: - expected_collection_reads = set([]) - for root, _, items in cat.walk(): - if isinstance(root, Collection) and root != cat: - expected_collection_reads.add(root.get_self_href()) - - # Iterate over items to make sure they are read - self.assertNotEqual(list(items), None) - - call_uris: List[Any] = [ - call[0][0] for call in mock_io.read_text_method.call_args_list - if call[0][0] in expected_collection_reads - ] - - for collection_uri in expected_collection_reads: - calls = len([x for x in call_uris if x == collection_uri]) - self.assertEqual( - calls, 1, - '{} was read {} times instead of once!'.format(collection_uri, calls)) + cat._stac_io = mock_io + expected_collection_reads = set([]) + for root, _, items in cat.walk(): + if isinstance(root, Collection) and root != cat: + expected_collection_reads.add(root.get_self_href()) + + # Iterate over items to make sure they are read + self.assertNotEqual(list(items), None) + + call_uris: List[Any] = [ + call[0][0] for call in mock_io.mock.read_text.call_args_list + if call[0][0] in expected_collection_reads + ] + + for collection_uri in expected_collection_reads: + calls = len([x for x in call_uris if x == collection_uri]) + self.assertEqual( + calls, 1, '{} was read {} times instead of once!'.format(collection_uri, calls)) def test_reading_iterating_and_writing_works_as_expected(self): """ Test case to cover issue #88 """ diff --git a/tests/test_item.py b/tests/test_item.py index acce5cffa..9488679b7 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -170,7 +170,7 @@ def test_null_geometry(self): item_dict['bbox'] def test_0_9_item_with_no_extensions_does_not_read_collection_data(self): - item_json = ps.STAC_IO.read_json( + item_json = ps.StacIO.default().read_json( TestCases.get_path('data-files/examples/hand-0.9.0/010100/010100.json')) assert item_json.get('stac_extensions') is None assert item_json.get('stac_version') == '0.9.0' diff --git a/tests/test_writing.py b/tests/test_writing.py index ad6d26177..9ce484e18 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -2,7 +2,7 @@ from tempfile import TemporaryDirectory import pystac as ps -from pystac import (STAC_IO, Collection, CatalogType, HIERARCHICAL_LINKS) +from pystac import (Collection, CatalogType, HIERARCHICAL_LINKS) from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href from pystac.validation import validate_dict @@ -27,7 +27,7 @@ def validate_catalog(self, catalog: ps.Catalog): return validated_count def validate_file(self, path: str, object_type: str): - d = STAC_IO.read_json(path) + d = ps.StacIO.default().read_json(path) return validate_dict(d, ps.STACObjectType(object_type)) def validate_link_types(self, root_href: str, catalog_type: ps.CatalogType): @@ -44,7 +44,7 @@ def validate_asset_href_type(item: ps.Item, item_href: str): self.assertTrue(is_valid) def validate_item_link_type(href: str, link_type: str, should_include_self: bool): - item_dict = STAC_IO.read_json(href) + item_dict = ps.StacIO.default().read_json(href) item = ps.Item.from_file(href) rel_links = HIERARCHICAL_LINKS + ps.EXTENSION_HOOKS.get_extended_object_links(item) for link in item.get_links(): @@ -60,7 +60,7 @@ def validate_item_link_type(href: str, link_type: str, should_include_self: bool self.assertEqual('self' in rels, should_include_self) def validate_catalog_link_type(href: str, link_type: str, should_include_self: bool): - cat_dict = STAC_IO.read_json(href) + cat_dict = ps.StacIO.default().read_json(href) cat = ps.Catalog.from_file(href) rels = set([link['rel'] for link in cat_dict['links']]) diff --git a/tests/utils/stac_io_mock.py b/tests/utils/stac_io_mock.py index 2ea87ce26..7ebcb0896 100644 --- a/tests/utils/stac_io_mock.py +++ b/tests/utils/stac_io_mock.py @@ -1,40 +1,20 @@ +from typing import Any, Union from unittest.mock import Mock -from pystac.stac_io import STAC_IO +import pystac as ps -class MockStacIO: +class MockStacIO(ps.StacIO): """Creates a mock that records STAC_IO calls for testing and allows clients to replace STAC_IO functionality, all within a context scope. """ - def __init__(self, read_text_method=None, write_text_method=None): - self.read_text_method = read_text_method - self.write_text_method = write_text_method + def __init__(self): + self.mock = Mock() - def __enter__(self): - mock = Mock() - self.old_read_text_method = STAC_IO.read_text_method - self.old_write_text_method = STAC_IO.write_text_method + def read_text(self, source: Union[str, ps.Link], *args: Any, **kwargs: Any) -> str: + self.mock.read_text(source) + return ps.StacIO.default().read_text(source) - def read_text_method(uri): - mock.read_text_method(uri) - if self.read_text_method: - return self.read_text_method(uri) - else: - return self.old_read_text_method(uri) - - def write_text_method(uri, txt): - mock.write_text_method(uri, txt) - if self.write_text_method: - return self.write_text_method(uri, txt) - else: - return self.old_write_text_method(uri, txt) - - STAC_IO.read_text_method = read_text_method - STAC_IO.write_text_method = write_text_method - - return mock - - def __exit__(self, type, value, traceback): - STAC_IO.read_text_method = self.old_read_text_method - STAC_IO.write_text_method = self.old_write_text_method + def write_text(self, dest: Union[str, ps.Link], txt: str, *args: Any, **kwargs: Any) -> None: + self.mock.write_text(dest, txt) + ps.StacIO.default().write_text(dest, txt) diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 6a32198cd..6f37fb4b7 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -84,7 +84,7 @@ def test_validate_all(self): for test_case in TestCases.all_test_catalogs(): catalog_href = test_case.get_self_href() if catalog_href is not None: - stac_dict = ps.STAC_IO.read_json(catalog_href) + stac_dict = ps.StacIO.default().read_json(catalog_href) pystac.validation.validate_all(stac_dict, catalog_href) @@ -100,7 +100,8 @@ def test_validate_all(self): new_cat_href = os.path.join(dst_dir, 'catalog.json') # Make sure it's valid before modification - pystac.validation.validate_all(ps.STAC_IO.read_json(new_cat_href), new_cat_href) + pystac.validation.validate_all(ps.StacIO.default().read_json(new_cat_href), + new_cat_href) # Modify a contained collection to add an extension for which the # collection is invalid. @@ -110,7 +111,7 @@ def test_validate_all(self): with open(os.path.join(dst_dir, 'acc/collection.json'), 'w') as f: json.dump(col, f) - stac_dict = ps.STAC_IO.read_json(new_cat_href) + stac_dict = ps.StacIO.default().read_json(new_cat_href) with self.assertRaises(STACValidationError): pystac.validation.validate_all(stac_dict, new_cat_href) From 501e98efa88fbe11dd9d418a4e7c1b6b99dba9eb Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 19:01:09 -0400 Subject: [PATCH 32/51] Move to black as formatter --- requirements-dev.txt | 2 +- scripts/format | 3 ++- scripts/test | 26 ++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e5ebda679..b46289357 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==0.790 flake8==3.8.* -yapf==0.30.* +black==21.4b2 codespell==1.17.1 ipython==7.16.1 diff --git a/scripts/format b/scripts/format index a7e68fd4f..640929c39 100755 --- a/scripts/format +++ b/scripts/format @@ -17,6 +17,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then if [ "${1:-}" = "--help" ]; then usage else - yapf -ipr pystac tests + black pystac + black tests fi fi diff --git a/scripts/test b/scripts/test index 99385fc94..2cfc070b3 100755 --- a/scripts/test +++ b/scripts/test @@ -17,14 +17,28 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then if [ "${1:-}" = "--help" ]; then usage else - # Types + echo + echo " -- CHECKING TYPES WITH PYRIGHT --" + echo + scripts/pyright pystac tests - # Lint + echo + echo " -- LINTING WITH FLAKE8 --" + echo + flake8 pystac tests - # Code formatting - yapf -dpr pystac tests + echo + echo " -- CHECKING FORMAT WITH BLACK --" + echo + + black --check pystac + black --check tests + + echo + echo " -- CHECKING SPELLING WITH CODESPELL --" + echo # Code spelling codespell -I .codespellignore -f \ @@ -36,6 +50,10 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then *.py \ *.md + echo + echo " -- RUNNING UNIT TESTS --" + echo + # Test suite with coverage enabled coverage run --source=pystac/ -m unittest discover tests/ coverage xml From aa57019d79c357e5d8ae2cf44100ceb5c7db60d7 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 19:01:31 -0400 Subject: [PATCH 33/51] Reformat with black --- pystac/__init__.py | 68 ++- pystac/asset.py | 67 ++- pystac/cache.py | 118 ++-- pystac/catalog.py | 309 +++++----- pystac/collection.py | 337 ++++++----- pystac/errors.py | 9 +- pystac/extensions/__init__.py | 4 +- pystac/extensions/base.py | 20 +- pystac/extensions/datacube.py | 66 ++- pystac/extensions/eo.py | 263 +++++---- pystac/extensions/file.py | 82 +-- pystac/extensions/hooks.py | 20 +- pystac/extensions/item_assets.py | 38 +- pystac/extensions/label.py | 302 +++++----- pystac/extensions/pointcloud.py | 214 +++---- pystac/extensions/projection.py | 59 +- pystac/extensions/sar.py | 163 +++--- pystac/extensions/sat.py | 37 +- pystac/extensions/scientific.py | 68 ++- pystac/extensions/timestamps.py | 33 +- pystac/extensions/version.py | 69 ++- pystac/extensions/view.py | 43 +- pystac/item.py | 374 +++++++------ pystac/layout.py | 172 +++--- pystac/link.py | 92 +-- pystac/media_type.py | 30 +- pystac/serialization/__init__.py | 9 +- pystac/serialization/common_properties.py | 58 +- pystac/serialization/identify.py | 260 +++++---- pystac/serialization/migrate.py | 132 +++-- pystac/stac_io.py | 99 +++- pystac/stac_object.py | 78 +-- pystac/utils.py | 70 +-- pystac/validation/__init__.py | 78 +-- pystac/validation/schema_uri_map.py | 292 ++++++---- pystac/validation/stac_validator.py | 124 +++-- pystac/version.py | 6 +- tests/data-files/change_stac_version.py | 30 +- tests/data-files/get_examples.py | 74 ++- tests/extensions/test_custom.py | 39 +- tests/extensions/test_eo.py | 70 ++- tests/extensions/test_file.py | 18 +- tests/extensions/test_label.py | 140 +++-- tests/extensions/test_pointcloud.py | 89 +-- tests/extensions/test_projection.py | 163 +++--- tests/extensions/test_sar.py | 76 ++- tests/extensions/test_sat.py | 28 +- tests/extensions/test_scientific.py | 50 +- tests/extensions/test_timestamps.py | 102 ++-- tests/extensions/test_version.py | 72 +-- tests/extensions/test_view.py | 94 ++-- tests/serialization/test_identify.py | 63 ++- tests/serialization/test_migrate.py | 57 +- tests/test_cache.py | 43 +- tests/test_catalog.py | 651 +++++++++++++--------- tests/test_collection.py | 167 +++--- tests/test_item.py | 386 +++++++------ tests/test_layout.py | 350 +++++++----- tests/test_link.py | 85 ++- tests/test_utils.py | 229 +++++--- tests/test_version.py | 16 +- tests/test_writing.py | 55 +- tests/utils/__init__.py | 12 +- tests/utils/stac_io_mock.py | 5 +- tests/utils/test_cases.py | 214 ++++--- tests/validation/test_schema_uri_map.py | 8 +- tests/validation/test_validate.py | 73 ++- 67 files changed, 4607 insertions(+), 3115 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index 5a980b1b4..b61c21b8c 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -3,15 +3,23 @@ """ # flake8: noqa -from pystac.errors import (STACError, STACTypeError, RequiredPropertyMissing) # type:ignore +from pystac.errors import ( + STACError, # type:ignore + STACTypeError, # type:ignore + RequiredPropertyMissing, # type:ignore +) from typing import Any, Dict, Optional -from pystac.version import (__version__, get_stac_version, set_stac_version) # type:ignore +from pystac.version import ( + __version__, + get_stac_version, # type:ignore + set_stac_version, # type:ignore +) from pystac.stac_io import StacIO # type:ignore -from pystac.stac_object import (STACObject, STACObjectType) # type:ignore +from pystac.stac_object import STACObject, STACObjectType # type:ignore from pystac.media_type import MediaType # type:ignore -from pystac.link import (Link, HIERARCHICAL_LINKS) # type:ignore -from pystac.catalog import (Catalog, CatalogType) # type:ignore +from pystac.link import Link, HIERARCHICAL_LINKS # type:ignore +from pystac.catalog import Catalog, CatalogType # type:ignore from pystac.collection import ( Collection, # type:ignore Extent, # type:ignore @@ -19,8 +27,9 @@ TemporalExtent, # type:ignore Provider, # type:ignore Summaries, # type:ignore - RangeSummary) # type:ignore -from pystac.item import (Item, Asset, CommonMetadata) # type:ignore + RangeSummary, # type:ignore +) +from pystac.item import Item, Asset, CommonMetadata # type:ignore import pystac.validation from pystac.validation import STACValidationError # type:ignore @@ -40,18 +49,23 @@ import pystac.extensions.version import pystac.extensions.view -EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks([ - pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS, pystac.extensions.eo.EO_EXTENSION_HOOKS, - pystac.extensions.file.FILE_EXTENSION_HOOKS, - pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS, - pystac.extensions.label.LABEL_EXTENSION_HOOKS, - pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, - pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, - pystac.extensions.sar.SAR_EXTENSION_HOOKS, pystac.extensions.sat.SAT_EXTENSION_HOOKS, - pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, - pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, - pystac.extensions.version.VERSION_EXTENSION_HOOKS, pystac.extensions.view.VIEW_EXTENSION_HOOKS -]) +EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks( + [ + pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS, + pystac.extensions.eo.EO_EXTENSION_HOOKS, + pystac.extensions.file.FILE_EXTENSION_HOOKS, + pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS, + pystac.extensions.label.LABEL_EXTENSION_HOOKS, + pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, + pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, + pystac.extensions.sar.SAR_EXTENSION_HOOKS, + pystac.extensions.sat.SAT_EXTENSION_HOOKS, + pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, + pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, + pystac.extensions.version.VERSION_EXTENSION_HOOKS, + pystac.extensions.view.VIEW_EXTENSION_HOOKS, + ] +) def read_file(href: str) -> STACObject: @@ -72,9 +86,9 @@ def read_file(href: str) -> STACObject: return STACObject.from_file(href) -def write_file(obj: STACObject, - include_self_link: bool = True, - dest_href: Optional[str] = None) -> None: +def write_file( + obj: STACObject, include_self_link: bool = True, dest_href: Optional[str] = None +) -> None: """Writes a STACObject to a file. This will write only the Catalog, Collection or Item ``obj``. It will not attempt @@ -97,10 +111,12 @@ def write_file(obj: STACObject, obj.save_object(include_self_link=include_self_link, dest_href=dest_href) -def read_dict(d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, - stac_io: Optional[StacIO] = None) -> STACObject: +def read_dict( + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None, + stac_io: Optional[StacIO] = None, +) -> STACObject: """Reads a STAC object from a dict representing the serialized JSON version of the STAC object. diff --git a/pystac/asset.py b/pystac/asset.py index 0d6333821..03d560a84 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -40,13 +40,16 @@ class Asset: object JSON. owner: The Item or Collection this asset belongs to, or None if it has no owner. """ - def __init__(self, - href: str, - title: Optional[str] = None, - description: Optional[str] = None, - media_type: Optional[str] = None, - roles: Optional[List[str]] = None, - properties: Optional[Dict[str, Any]] = None) -> None: + + def __init__( + self, + href: str, + title: Optional[str] = None, + description: Optional[str] = None, + media_type: Optional[str] = None, + roles: Optional[List[str]] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: self.href = href self.title = title self.description = description @@ -95,23 +98,23 @@ def to_dict(self) -> Dict[str, Any]: dict: A serialization of the Asset that can be written out as JSON. """ - d: Dict[str, Any] = {'href': self.href} + d: Dict[str, Any] = {"href": self.href} if self.media_type is not None: - d['type'] = self.media_type + d["type"] = self.media_type if self.title is not None: - d['title'] = self.title + d["title"] = self.title if self.description is not None: - d['description'] = self.description + d["description"] = self.description if self.properties is not None and len(self.properties) > 0: for k, v in self.properties.items(): d[k] = v if self.roles is not None: - d['roles'] = self.roles + d["roles"] = self.roles return d @@ -121,15 +124,17 @@ def clone(self) -> "Asset": Returns: Asset: The clone of this asset. """ - return Asset(href=self.href, - title=self.title, - description=self.description, - media_type=self.media_type, - roles=self.roles, - properties=self.properties) + return Asset( + href=self.href, + title=self.title, + description=self.description, + media_type=self.media_type, + roles=self.roles, + properties=self.properties, + ) def __repr__(self) -> str: - return ''.format(self.href) + return "".format(self.href) @staticmethod def from_dict(d: Dict[str, Any]) -> "Asset": @@ -139,18 +144,20 @@ def from_dict(d: Dict[str, Any]) -> "Asset": Asset: The Asset deserialized from the JSON dict. """ d = copy(d) - href = d.pop('href') - media_type = d.pop('type', None) - title = d.pop('title', None) - description = d.pop('description', None) - roles = d.pop('roles', None) + href = d.pop("href") + media_type = d.pop("type", None) + title = d.pop("title", None) + description = d.pop("description", None) + roles = d.pop("roles", None) properties = None if any(d): properties = d - return Asset(href=href, - media_type=media_type, - title=title, - description=description, - roles=roles, - properties=properties) + return Asset( + href=href, + media_type=media_type, + title=title, + description=description, + roles=roles, + properties=properties, + ) diff --git a/pystac/cache.py b/pystac/cache.py index a3aeefcba..38e5cf1c4 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -30,7 +30,7 @@ def get_cache_key(stac_object: "STACObject_Type") -> Tuple[str, bool]: while obj is not None: ids.append(obj.id) obj = obj.get_parent() - return ('/'.join(ids), False) + return ("/".join(ids), False) class ResolvedObjectCache: @@ -57,10 +57,13 @@ class ResolvedObjectCache: their cached object. ids_to_collections (Dict[str, Collection]): Map of collection IDs to collections. """ - def __init__(self, - id_keys_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, - hrefs_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, - ids_to_collections: Dict[str, "Collection_Type"] = None): + + def __init__( + self, + id_keys_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, + hrefs_to_objects: Optional[Dict[str, "STACObject_Type"]] = None, + ids_to_collections: Dict[str, "Collection_Type"] = None, + ): self.id_keys_to_objects = id_keys_to_objects or {} self.hrefs_to_objects = hrefs_to_objects or {} self.ids_to_collections = ids_to_collections or {} @@ -165,7 +168,9 @@ def remove(self, obj: "STACObject_Type") -> None: def __contains__(self, obj: "STACObject_Type") -> bool: key, is_href = get_cache_key(obj) - return key in self.hrefs_to_objects if is_href else key in self.id_keys_to_objects + return ( + key in self.hrefs_to_objects if is_href else key in self.id_keys_to_objects + ) def contains_collection_id(self, collection_id: str) -> bool: """Returns True if there is a collection with given collection ID is cached.""" @@ -177,7 +182,9 @@ def as_collection_cache(self) -> "CollectionCache": return self._collection_cache @staticmethod - def merge(first: "ResolvedObjectCache", second: "ResolvedObjectCache") -> "ResolvedObjectCache": + def merge( + first: "ResolvedObjectCache", second: "ResolvedObjectCache" + ) -> "ResolvedObjectCache": """Merges two ResolvedObjectCache. The merged cache will give preference to the first argument; that is, if there @@ -192,17 +199,25 @@ def merge(first: "ResolvedObjectCache", second: "ResolvedObjectCache") -> "Resol Returns: ResolvedObjectCache: The resulting merged cache. """ - merged = ResolvedObjectCache(id_keys_to_objects=dict( - ChainMap(copy(first.id_keys_to_objects), copy(second.id_keys_to_objects))), - hrefs_to_objects=dict( - ChainMap(copy(first.hrefs_to_objects), - copy(second.hrefs_to_objects))), - ids_to_collections=dict( - ChainMap(copy(first.ids_to_collections), - copy(second.ids_to_collections)))) + merged = ResolvedObjectCache( + id_keys_to_objects=dict( + ChainMap( + copy(first.id_keys_to_objects), copy(second.id_keys_to_objects) + ) + ), + hrefs_to_objects=dict( + ChainMap(copy(first.hrefs_to_objects), copy(second.hrefs_to_objects)) + ), + ids_to_collections=dict( + ChainMap( + copy(first.ids_to_collections), copy(second.ids_to_collections) + ) + ), + ) merged._collection_cache = ResolvedObjectCollectionCache.merge( - merged, first._collection_cache, second._collection_cache) + merged, first._collection_cache, second._collection_cache + ) return merged @@ -214,50 +229,65 @@ class CollectionCache: The CollectionCache will contain collections as either as dicts or PySTAC Collections, and will set Collection JSON that it reads in order to merge in common properties. """ - def __init__(self, - cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None): + + def __init__( + self, + cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + ): self.cached_ids = cached_ids or {} self.cached_hrefs = cached_hrefs or {} - def get_by_id(self, collection_id: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: + def get_by_id( + self, collection_id: str + ) -> Optional[Union["Collection_Type", Dict[str, Any]]]: return self.cached_ids.get(collection_id) - def get_by_href(self, href: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: + def get_by_href( + self, href: str + ) -> Optional[Union["Collection_Type", Dict[str, Any]]]: return self.cached_hrefs.get(href) def contains_id(self, collection_id: str) -> bool: return collection_id in self.cached_ids - def cache(self, - collection: Union["Collection_Type", Dict[str, Any]], - href: Optional[str] = None) -> None: + def cache( + self, + collection: Union["Collection_Type", Dict[str, Any]], + href: Optional[str] = None, + ) -> None: """Caches a collection JSON.""" if isinstance(collection, ps.Collection): self.cached_ids[collection.id] = collection else: - self.cached_ids[collection['id']] = collection + self.cached_ids[collection["id"]] = collection if href is not None: self.cached_hrefs[href] = collection class ResolvedObjectCollectionCache(CollectionCache): - def __init__(self, - resolved_object_cache: ResolvedObjectCache, - cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, - cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None): + def __init__( + self, + resolved_object_cache: ResolvedObjectCache, + cached_ids: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + cached_hrefs: Dict[str, Union["Collection_Type", Dict[str, Any]]] = None, + ): super().__init__(cached_ids, cached_hrefs) self.resolved_object_cache = resolved_object_cache - def get_by_id(self, collection_id: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: + def get_by_id( + self, collection_id: str + ) -> Optional[Union["Collection_Type", Dict[str, Any]]]: result = self.resolved_object_cache.get_collection_by_id(collection_id) if result is None: return super().get_by_id(collection_id) else: return result - def get_by_href(self, href: str) -> Optional[Union["Collection_Type", Dict[str, Any]]]: + def get_by_href( + self, href: str + ) -> Optional[Union["Collection_Type", Dict[str, Any]]]: result = self.resolved_object_cache.get_by_href(href) if result is None: return super().get_by_href(href) @@ -265,18 +295,23 @@ def get_by_href(self, href: str) -> Optional[Union["Collection_Type", Dict[str, return cast(ps.Collection, result) def contains_id(self, collection_id: str) -> bool: - return (self.resolved_object_cache.contains_collection_id(collection_id) - or super().contains_id(collection_id)) - - def cache(self, - collection: Union["Collection_Type", Dict[str, Any]], - href: Optional[str] = None) -> None: + return self.resolved_object_cache.contains_collection_id( + collection_id + ) or super().contains_id(collection_id) + + def cache( + self, + collection: Union["Collection_Type", Dict[str, Any]], + href: Optional[str] = None, + ) -> None: super().cache(collection, href) @staticmethod - def merge(resolved_object_cache: ResolvedObjectCache, - first: Optional["ResolvedObjectCollectionCache"], - second: Optional["ResolvedObjectCollectionCache"]) -> "ResolvedObjectCollectionCache": + def merge( + resolved_object_cache: ResolvedObjectCache, + first: Optional["ResolvedObjectCollectionCache"], + second: Optional["ResolvedObjectCollectionCache"], + ) -> "ResolvedObjectCollectionCache": first_cached_ids = {} if first is not None: first_cached_ids = copy(first.cached_ids) @@ -296,4 +331,5 @@ def merge(resolved_object_cache: ResolvedObjectCache, return ResolvedObjectCollectionCache( resolved_object_cache, cached_ids=dict(ChainMap(first_cached_ids, second_cached_ids)), - cached_hrefs=dict(ChainMap(first_cached_hrefs, second_cached_hrefs))) + cached_hrefs=dict(ChainMap(first_cached_hrefs, second_cached_hrefs)), + ) diff --git a/pystac/catalog.py b/pystac/catalog.py index d9eb4240c..2e09f70b6 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -1,14 +1,30 @@ import os from copy import deepcopy from enum import Enum -from typing import Any, Callable, Dict, Iterable, List, Optional, TYPE_CHECKING, Tuple, Union, cast +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + TYPE_CHECKING, + Tuple, + Union, + cast, +) import pystac as ps from pystac.stac_object import STACObject -from pystac.layout import (BestPracticesLayoutStrategy, HrefLayoutStrategy, LayoutTemplate) +from pystac.layout import ( + BestPracticesLayoutStrategy, + HrefLayoutStrategy, + LayoutTemplate, +) from pystac.link import Link from pystac.cache import ResolvedObjectCache -from pystac.utils import (is_absolute_href, make_absolute_href) +from pystac.utils import is_absolute_href, make_absolute_href + if TYPE_CHECKING: from pystac.item import Asset as Asset_Type, Item as Item_Type @@ -17,32 +33,32 @@ class CatalogType(str, Enum): def __str__(self) -> str: return str(self.value) - SELF_CONTAINED = 'SELF_CONTAINED' + SELF_CONTAINED = "SELF_CONTAINED" """A 'self-contained catalog' is one that is designed for portability. Users may want to download a catalog from online and be able to use it on their local computer, so all links need to be relative. See: `The best practices documentation on self-contained catalogs `_ - """ # noqa E501 + """ # noqa E501 - ABSOLUTE_PUBLISHED = 'ABSOLUTE_PUBLISHED' + ABSOLUTE_PUBLISHED = "ABSOLUTE_PUBLISHED" """ Absolute Published Catalog is a catalog that uses absolute links for everything, both in the links objects and in the asset hrefs. See: `The best practices documentation on published catalogs `_ - """ # noqa E501 + """ # noqa E501 - RELATIVE_PUBLISHED = 'RELATIVE_PUBLISHED' + RELATIVE_PUBLISHED = "RELATIVE_PUBLISHED" """ Relative Published Catalog is a catalog that uses relative links for everything, but includes an absolute self link at the root catalog, to identify its online location. See: `The best practices documentation on published catalogs `_ - """ # noqa E501 + """ # noqa E501 @classmethod def determine_type(cls, stac_json: Dict[str, Any]) -> Optional["CatalogType"]: @@ -59,11 +75,11 @@ def determine_type(cls, stac_json: Dict[str, Any]) -> Optional["CatalogType"]: """ self_link = None relative = False - for link in stac_json['links']: - if link['rel'] == 'self': + for link in stac_json["links"]: + if link["rel"] == "self": self_link = link else: - relative |= not is_absolute_href(link['href']) + relative |= not is_absolute_href(link["href"]) if self_link: if relative: @@ -118,14 +134,17 @@ class Catalog(STACObject): DEFAULT_FILE_NAME = "catalog.json" """Default file name that will be given to this STAC object in a canonical format.""" - def __init__(self, - id: str, - description: str, - title: Optional[str] = None, - stac_extensions: Optional[List[str]] = None, - extra_fields: Optional[Dict[str, Any]] = None, - href: Optional[str] = None, - catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED): + + def __init__( + self, + id: str, + description: str, + title: Optional[str] = None, + stac_extensions: Optional[List[str]] = None, + extra_fields: Optional[Dict[str, Any]] = None, + href: Optional[str] = None, + catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED, + ): super().__init__(stac_extensions or []) self.id = id @@ -148,21 +167,27 @@ def __init__(self, self._resolved_objects.cache(self) def __repr__(self) -> str: - return ''.format(self.id) + return "".format(self.id) def set_root(self, root: Optional["Catalog"]) -> None: STACObject.set_root(self, root) if root is not None: - root._resolved_objects = ResolvedObjectCache.merge(root._resolved_objects, - self._resolved_objects) + root._resolved_objects = ResolvedObjectCache.merge( + root._resolved_objects, self._resolved_objects + ) def is_relative(self) -> bool: - return self.catalog_type in [CatalogType.RELATIVE_PUBLISHED, CatalogType.SELF_CONTAINED] - - def add_child(self, - child: "Catalog", - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None) -> None: + return self.catalog_type in [ + CatalogType.RELATIVE_PUBLISHED, + CatalogType.SELF_CONTAINED, + ] + + def add_child( + self, + child: "Catalog", + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None, + ) -> None: """Adds a link to a child :class:`~pystac.Catalog` or :class:`~pystac.Collection`. This method will set the child's parent to this object, and its root to this Catalog's root. @@ -176,7 +201,7 @@ def add_child(self, # Prevent typo confusion if isinstance(child, ps.Item): - raise ps.STACError('Cannot add item as child. Use add_item instead.') + raise ps.STACError("Cannot add item as child. Use add_item instead.") if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -203,10 +228,12 @@ def add_children(self, children: Iterable["Catalog"]) -> None: for child in children: self.add_child(child) - def add_item(self, - item: "Item_Type", - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None) -> None: + def add_item( + self, + item: "Item_Type", + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None, + ) -> None: """Adds a link to an :class:`~pystac.Item`. This method will set the item's parent to this object, and its root to this Catalog's root. @@ -218,7 +245,7 @@ def add_item(self, # Prevent typo confusion if isinstance(item, ps.Catalog): - raise ps.STACError('Cannot add catalog as item. Use add_child instead.') + raise ps.STACError("Cannot add catalog as item. Use add_child instead.") if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -272,7 +299,7 @@ def get_children(self) -> Iterable["Catalog"]: Iterable[Catalog]: Generator of children who's parent is this catalog. """ - return map(lambda x: cast(ps.Catalog, x), self.get_stac_objects('child')) + return map(lambda x: cast(ps.Catalog, x), self.get_stac_objects("child")) def get_child_links(self) -> List[Link]: """Return all child links of this catalog. @@ -280,7 +307,7 @@ def get_child_links(self) -> List[Link]: Return: List[Link]: List of links of this catalog with ``rel == 'child'`` """ - return self.get_links('child') + return self.get_links("child") def clear_children(self) -> None: """Removes all children from this catalog. @@ -301,7 +328,7 @@ def remove_child(self, child_id: str) -> None: new_links: List[ps.Link] = [] root = self.get_root() for link in self.links: - if link.rel != 'child': + if link.rel != "child": new_links.append(link) else: link.resolve_stac_object(root=root) @@ -339,7 +366,7 @@ def get_items(self) -> Iterable["Item_Type"]: Return: Iterable[Item]: Generator of items who's parent is this catalog. """ - return map(lambda x: cast(ps.Item, x), self.get_stac_objects('item')) + return map(lambda x: cast(ps.Item, x), self.get_stac_objects("item")) def clear_items(self) -> None: """Removes all items from this catalog. @@ -353,7 +380,7 @@ def clear_items(self) -> None: item.set_parent(None) item.set_root(None) - self.links = [link for link in self.links if link.rel != 'item'] + self.links = [link for link in self.links if link.rel != "item"] def remove_item(self, item_id: str) -> None: """Removes an item from this catalog. @@ -364,7 +391,7 @@ def remove_item(self, item_id: str) -> None: new_links: List[ps.Link] = [] root = self.get_root() for link in self.links: - if link.rel != 'item': + if link.rel != "item": new_links.append(link) else: link.resolve_stac_object(root=root) @@ -395,43 +422,45 @@ def get_item_links(self) -> List[Link]: Return: List[Link]: List of links of this catalog with ``rel == 'item'`` """ - return self.get_links('item') + return self.get_links("item") def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: links = self.links if not include_self_link: - links = [x for x in links if x.rel != 'self'] + links = [x for x in links if x.rel != "self"] d: Dict[str, Any] = { - 'type': self.STAC_OBJECT_TYPE.value.title(), - 'id': self.id, - 'stac_version': ps.get_stac_version(), - 'description': self.description, - 'links': [link.to_dict() for link in links] + "type": self.STAC_OBJECT_TYPE.value.title(), + "id": self.id, + "stac_version": ps.get_stac_version(), + "description": self.description, + "links": [link.to_dict() for link in links], } if self.stac_extensions is not None: - d['stac_extensions'] = self.stac_extensions + d["stac_extensions"] = self.stac_extensions for key in self.extra_fields: d[key] = self.extra_fields[key] if self.title is not None: - d['title'] = self.title + d["title"] = self.title return d def clone(self) -> "Catalog": - clone = Catalog(id=self.id, - description=self.description, - title=self.title, - stac_extensions=self.stac_extensions, - extra_fields=deepcopy(self.extra_fields), - catalog_type=self.catalog_type) + clone = Catalog( + id=self.id, + description=self.description, + title=self.title, + stac_extensions=self.stac_extensions, + extra_fields=deepcopy(self.extra_fields), + catalog_type=self.catalog_type, + ) clone._resolved_objects.cache(clone) for link in self.links: - if link.rel == 'root': + if link.rel == "root": # Catalog __init__ sets correct root to clone; don't reset # if the root link points to self root_is_self = link.is_resolved() and link.target is self @@ -459,10 +488,12 @@ def make_all_asset_hrefs_absolute(self) -> None: for item in items: item.make_asset_hrefs_absolute() - def normalize_and_save(self, - root_href: str, - catalog_type: Optional[CatalogType] = None, - strategy: Optional[HrefLayoutStrategy] = None) -> None: + def normalize_and_save( + self, + root_href: str, + catalog_type: Optional[CatalogType] = None, + strategy: Optional[HrefLayoutStrategy] = None, + ) -> None: """Normalizes link HREFs to the given root_href, and saves the catalog. This is a convenience method that simply calls :func:`Catalog.normalize_hrefs @@ -481,9 +512,9 @@ def normalize_and_save(self, self.normalize_hrefs(root_href, strategy=strategy) self.save(catalog_type) - def normalize_hrefs(self, - root_href: str, - strategy: Optional[HrefLayoutStrategy] = None) -> None: + def normalize_hrefs( + self, root_href: str, strategy: Optional[HrefLayoutStrategy] = None + ) -> None: """Normalize HREFs will regenerate all link HREFs based on an absolute root_href and the canonical catalog layout as specified in the STAC specification's best practices. @@ -497,7 +528,7 @@ def normalize_hrefs(self, See: `STAC best practices document `_ for the canonical layout of a STAC. - """ # noqa E501 + """ # noqa E501 if strategy is None: _strategy: HrefLayoutStrategy = BestPracticesLayoutStrategy() else: @@ -517,8 +548,9 @@ def fn() -> None: return fn - def process_catalog(cat: Catalog, _root_href: str, - is_root: bool) -> List[Callable[[], None]]: + def process_catalog( + cat: Catalog, _root_href: str, is_root: bool + ) -> List[Callable[[], None]]: setter_funcs: List[Callable[[], None]] = [] cat.resolve_links() @@ -547,11 +579,13 @@ def fn() -> None: for fn in setter_funcs: fn() - def generate_subcatalogs(self, - template: str, - defaults: Optional[Dict[str, Any]] = None, - parent_ids: Optional[List[str]] = None, - **kwargs: Any) -> List["Catalog"]: + def generate_subcatalogs( + self, + template: str, + defaults: Optional[Dict[str, Any]] = None, + parent_ids: Optional[List[str]] = None, + **kwargs: Any, + ) -> List["Catalog"]: """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 @@ -576,23 +610,26 @@ def generate_subcatalogs(self, parent_ids.append(self.id) for child in self.get_children(): result.extend( - child.generate_subcatalogs(template, - defaults=defaults, - parent_ids=parent_ids.copy())) + child.generate_subcatalogs( + template, defaults=defaults, parent_ids=parent_ids.copy() + ) + ) layout_template = LayoutTemplate(template, defaults=defaults) keep_item_links: List[Link] = [] - item_links = [lk for lk in self.links if lk.rel == 'item'] + item_links = [lk for lk in self.links if lk.rel == "item"] for link in item_links: link.resolve_stac_object(root=self.get_root()) item = cast(ps.Item, link.target) item_parts = layout_template.get_template_values(item) id_iter = reversed(parent_ids) - if all([ - '{}'.format(id) == next(id_iter, None) + if all( + [ + "{}".format(id) == next(id_iter, None) for id in reversed(list(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. @@ -600,25 +637,26 @@ def generate_subcatalogs(self, continue curr_parent = self for k, v in item_parts.items(): - subcat_id = '{}'.format(v) + subcat_id = "{}".format(v) 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_desc = "Catalog of items from {} with {} of {}".format( + curr_parent.id, k, v + ) subcat = ps.Catalog(id=subcat_id, description=subcat_desc) curr_parent.add_child(subcat) result.append(subcat) curr_parent = subcat # resolve collection link so when added back points to correct location - col_link = item.get_single_link('collection') + col_link = item.get_single_link("collection") if col_link is not None: col_link.resolve_stac_object() curr_parent.add_item(item) # keep only non-item links and item links that have not been moved elsewhere - self.links = [lk for lk in self.links if lk.rel != 'item'] + keep_item_links + self.links = [lk for lk in self.links if lk.rel != "item"] + keep_item_links return result @@ -643,7 +681,7 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: """ root = self.get_root() if root is None: - raise Exception('There is no root catalog') + raise Exception("There is no root catalog") if catalog_type is not None: root.catalog_type = catalog_type @@ -656,8 +694,9 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: for item_link in self.get_item_links(): if item_link.is_resolved(): - cast(ps.Item, - item_link.target).save_object(include_self_link=items_include_self_link) + cast(ps.Item, item_link.target).save_object( + include_self_link=items_include_self_link + ) include_self_link = False # include a self link if this is the root catalog or if ABSOLUTE_PUBLISHED catalog @@ -672,7 +711,9 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: if catalog_type is not None: self.catalog_type = catalog_type - def walk(self) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["Item_Type"]]]: + def walk( + self, + ) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["Item_Type"]]]: """Walks through children and items of catalogs. For each catalog in the STAC's tree rooted at this catalog (including this catalog @@ -711,11 +752,14 @@ def validate_all(self) -> None: item.validate() def _object_links(self) -> List[str]: - return ['child', 'item'] + (ps.EXTENSION_HOOKS.get_extended_object_links(self) or []) + return ["child", "item"] + ( + ps.EXTENSION_HOOKS.get_extended_object_links(self) or [] + ) def map_items( - self, item_mapper: Callable[["Item_Type"], Union["Item_Type", - List["Item_Type"]]]) -> "Catalog": + self, + item_mapper: Callable[["Item_Type"], Union["Item_Type", List["Item_Type"]]], + ) -> "Catalog": """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -740,7 +784,7 @@ def process_catalog(catalog: Catalog) -> None: item_link.resolve_stac_object(root=self.get_root()) mapped = item_mapper(cast(ps.Item, item_link.target)) if mapped is None: - raise Exception('item_mapper cannot return None.') + raise Exception("item_mapper cannot return None.") if isinstance(mapped, ps.Item): item_link.target = mapped item_links.append(item_link) @@ -756,9 +800,11 @@ def process_catalog(catalog: Catalog) -> None: return new_cat def map_assets( - self, asset_mapper: Callable[[str, "Asset_Type"], Union["Asset_Type", Tuple[str, - "Asset_Type"], - Dict[str, "Asset_Type"]]] + self, + asset_mapper: Callable[ + [str, "Asset_Type"], + Union["Asset_Type", Tuple[str, "Asset_Type"], Dict[str, "Asset_Type"]], + ], ) -> "Catalog": """Creates a copy of a catalog, with each Asset for each Item passed through the asset_mapper function. @@ -773,11 +819,14 @@ def map_assets( Catalog: A full copy of this catalog, with assets manipulated according to the asset_mapper function. """ - def apply_asset_mapper(tup: Tuple[str, "Asset_Type"]) -> List[Tuple[str, ps.Asset]]: + + def apply_asset_mapper( + tup: Tuple[str, "Asset_Type"] + ) -> List[Tuple[str, ps.Asset]]: k, v = tup result = asset_mapper(k, v) if result is None: - raise Exception('asset_mapper cannot return None.') + raise Exception("asset_mapper cannot return None.") if isinstance(result, ps.Asset): return [(k, result)] elif isinstance(result, tuple): @@ -785,12 +834,14 @@ def apply_asset_mapper(tup: Tuple[str, "Asset_Type"]) -> List[Tuple[str, ps.Asse else: assets = list(result.items()) if len(assets) < 1: - raise Exception('asset_mapper must return a non-empty list') + raise Exception("asset_mapper must return a non-empty list") return assets def item_mapper(item: ps.Item) -> ps.Item: new_assets = [ - x for result in map(apply_asset_mapper, item.assets.items()) for x in result + x + for result in map(apply_asset_mapper, item.assets.items()) + for x in result ] item.assets = dict(new_assets) return item @@ -805,24 +856,26 @@ def describe(self, include_hrefs: bool = False, _indent: int = 0) -> None: include_hrefs (bool) - If True, print out each object's self link HREF along with the object ID. """ - s = '{}* {}'.format(' ' * _indent, self) + s = "{}* {}".format(" " * _indent, self) if include_hrefs: - s += ' {}'.format(self.get_self_href()) + s += " {}".format(self.get_self_href()) print(s) for child in self.get_children(): child.describe(include_hrefs=include_hrefs, _indent=_indent + 4) for item in self.get_items(): - s = '{}* {}'.format(' ' * (_indent + 2), item) + s = "{}* {}".format(" " * (_indent + 2), item) if include_hrefs: - s += ' {}'.format(item.get_self_href()) + s += " {}".format(item.get_self_href()) print(s) @classmethod - def from_dict(cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog"] = None, - migrate: bool = False) -> "Catalog": + def from_dict( + cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog"] = None, + migrate: bool = False, + ) -> "Catalog": if migrate: result = ps.read_dict(d, href=href, root=root) if not isinstance(result, Catalog): @@ -833,35 +886,37 @@ def from_dict(cls, d = deepcopy(d) - id = d.pop('id') - description = d.pop('description') - title = d.pop('title', None) - stac_extensions = d.pop('stac_extensions', None) - links = d.pop('links') + id = d.pop("id") + description = d.pop("description") + title = d.pop("title", None) + stac_extensions = d.pop("stac_extensions", None) + links = d.pop("links") - d.pop('stac_version') + d.pop("stac_version") - cat = Catalog(id=id, - description=description, - title=title, - stac_extensions=stac_extensions, - extra_fields=d, - href=href, - catalog_type=catalog_type or CatalogType.ABSOLUTE_PUBLISHED) + cat = Catalog( + id=id, + description=description, + title=title, + stac_extensions=stac_extensions, + extra_fields=d, + href=href, + catalog_type=catalog_type or CatalogType.ABSOLUTE_PUBLISHED, + ) for link in links: - if link['rel'] == 'root': + if link["rel"] == "root": # Remove the link that's generated in Catalog's constructor. - cat.remove_links('root') + cat.remove_links("root") - if link['rel'] != 'self' or href is None: + if link["rel"] != "self" or href is None: cat.add_link(Link.from_dict(link)) return cat - def full_copy(self, - root: Optional["Catalog"] = None, - parent: Optional["Catalog"] = None) -> "Catalog": + def full_copy( + self, root: Optional["Catalog"] = None, parent: Optional["Catalog"] = None + ) -> "Catalog": return cast(Catalog, super().full_copy(root, parent)) @classmethod diff --git a/pystac/collection.py b/pystac/collection.py index 1f1057484..c710e0c4b 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,13 +1,25 @@ -from copy import (copy, deepcopy) +from copy import copy, deepcopy from datetime import datetime as Datetime -from typing import (Any, Dict, Generic, Iterable, List, Optional, TYPE_CHECKING, Tuple, Type, - TypeVar, Union, cast) +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Optional, + TYPE_CHECKING, + Tuple, + Type, + TypeVar, + Union, + cast, +) import dateutil.parser from dateutil import tz import pystac as ps -from pystac import (STACObjectType, CatalogType) +from pystac import STACObjectType, CatalogType from pystac.asset import Asset from pystac.catalog import Catalog from pystac.layout import HrefLayoutStrategy @@ -17,7 +29,7 @@ if TYPE_CHECKING: from pystac.item import Item as Item_Type -T = TypeVar('T') +T = TypeVar("T") class SpatialExtent: @@ -35,6 +47,7 @@ class SpatialExtent: array must be 2*n where n is the number of dimensions. For example, a 2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]] """ + def __init__(self, bboxes: Union[List[List[float]], List[float]]) -> None: # 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. @@ -49,7 +62,7 @@ def to_dict(self) -> Dict[str, Any]: Returns: dict: A serialization of the SpatialExtent that can be written out as JSON. """ - d = {'bbox': self.bboxes} + d = {"bbox": self.bboxes} return d def clone(self) -> "SpatialExtent": @@ -67,7 +80,7 @@ def from_dict(d: Dict[str, Any]) -> "SpatialExtent": Returns: SpatialExtent: The SpatialExtent deserialized from the JSON dict. """ - return SpatialExtent(bboxes=d['bbox']) + return SpatialExtent(bboxes=d["bbox"]) @staticmethod def from_coordinates(coordinates: List[Any]) -> "SpatialExtent": @@ -83,16 +96,19 @@ def from_coordinates(coordinates: List[Any]) -> "SpatialExtent": SpatialExtent: A SpatialExtent with a single bbox that covers the given coordinates. """ + def process_coords( coord_lists: List[Any], xmin: Optional[float] = None, ymin: Optional[float] = None, xmax: Optional[float] = None, - ymax: Optional[float] = None + ymax: Optional[float] = None, ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]: for coord in coord_lists: if isinstance(coord[0], list): - xmin, ymin, xmax, ymax = process_coords(coord, xmin, ymin, xmax, ymax) + xmin, ymin, xmax, ymax = process_coords( + coord, xmin, ymin, xmax, ymax + ) else: x, y = coord if xmin is None or x < xmin: @@ -108,7 +124,9 @@ def process_coords( xmin, ymin, xmax, ymax = process_coords(coordinates) if xmin is None or ymin is None or xmax is None or ymax is None: - raise ValueError(f"Could not determine bounds from coordinate sequence {coordinates}") + raise ValueError( + f"Could not determine bounds from coordinate sequence {coordinates}" + ) return SpatialExtent([[xmin, ymin, xmax, ymax]]) @@ -132,7 +150,10 @@ class TemporalExtent: Note: Datetimes are required to be in UTC. """ - def __init__(self, intervals: Union[List[List[Optional[Datetime]]], List[Optional[Datetime]]]): + + def __init__( + self, intervals: Union[List[List[Optional[Datetime]]], List[Optional[Datetime]]] + ): # 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. @@ -160,7 +181,7 @@ def to_dict(self) -> Dict[str, Any]: encoded_intervals.append([start, end]) - d = {'interval': encoded_intervals} + d = {"interval": encoded_intervals} return d def clone(self) -> "TemporalExtent": @@ -179,7 +200,7 @@ def from_dict(d: Dict[str, Any]) -> "TemporalExtent": TemporalExtent: The TemporalExtent deserialized from the JSON dict. """ parsed_intervals: List[List[Optional[Datetime]]] = [] - for i in d['interval']: + for i in d["interval"]: start = None end = None @@ -199,7 +220,9 @@ def from_now() -> "TemporalExtent": Returns: TemporalExtent: The resulting TemporalExtent. """ - return TemporalExtent(intervals=[[Datetime.utcnow().replace(microsecond=0), None]]) + return TemporalExtent( + intervals=[[Datetime.utcnow().replace(microsecond=0), None]] + ) class Extent: @@ -213,6 +236,7 @@ class Extent: spatial (SpatialExtent): Potential spatial extent covered by the collection. temporal (TemporalExtent): Potential temporal extent covered by the collection. """ + def __init__(self, spatial: SpatialExtent, temporal: TemporalExtent): self.spatial = spatial self.temporal = temporal @@ -223,7 +247,7 @@ def to_dict(self) -> Dict[str, Any]: Returns: dict: A serialization of the Extent that can be written out as JSON. """ - d = {'spatial': self.spatial.to_dict(), 'temporal': self.temporal.to_dict()} + d = {"spatial": self.spatial.to_dict(), "temporal": self.temporal.to_dict()} return d @@ -244,21 +268,23 @@ def from_dict(d: Dict[str, Any]) -> "Extent": """ # Handle pre-0.8 spatial extents - spatial_extent = d['spatial'] + spatial_extent = d["spatial"] if isinstance(spatial_extent, list): - spatial_extent_dict: Dict[str, Any] = {'bbox': [spatial_extent]} + spatial_extent_dict: Dict[str, Any] = {"bbox": [spatial_extent]} else: spatial_extent_dict = spatial_extent # Handle pre-0.8 temporal extents - temporal_extent = d['temporal'] + temporal_extent = d["temporal"] if isinstance(temporal_extent, list): - temporal_extent_dict: Dict[str, Any] = {'interval': [temporal_extent]} + temporal_extent_dict: Dict[str, Any] = {"interval": [temporal_extent]} else: temporal_extent_dict = temporal_extent - return Extent(SpatialExtent.from_dict(spatial_extent_dict), - TemporalExtent.from_dict(temporal_extent_dict)) + return Extent( + SpatialExtent.from_dict(spatial_extent_dict), + TemporalExtent.from_dict(temporal_extent_dict), + ) @staticmethod def from_items(items: Iterable["Item_Type"]) -> "Extent": @@ -271,8 +297,12 @@ def from_items(items: Iterable["Item_Type"]) -> "Extent": Extent: An Extent that spatially and temporally covers all of the given items. """ - bounds_values: List[List[float]] = [[float('inf')], [float('inf')], [float('-inf')], - [float('-inf')]] + bounds_values: List[List[float]] = [ + [float("inf")], + [float("inf")], + [float("-inf")], + [float("-inf")], + ] datetimes: List[Datetime] = [] starts: List[Datetime] = [] ends: List[Datetime] = [] @@ -292,19 +322,31 @@ def from_items(items: Iterable["Item_Type"]) -> "Extent": start_timestamp = None else: start_timestamp = min( - [dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) for dt in datetimes + starts]) + [ + dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) + for dt in datetimes + starts + ] + ) if not any(datetimes + ends): end_timestamp = None else: end_timestamp = max( - [dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) for dt in datetimes + ends]) - - spatial = SpatialExtent([[ - min(bounds_values[0]), - min(bounds_values[1]), - max(bounds_values[2]), - max(bounds_values[3]) - ]]) + [ + dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) + for dt in datetimes + ends + ] + ) + + spatial = SpatialExtent( + [ + [ + min(bounds_values[0]), + min(bounds_values[1]), + max(bounds_values[2]), + max(bounds_values[3]), + ] + ] + ) temporal = TemporalExtent([[start_timestamp, end_timestamp]]) return Extent(spatial, temporal) @@ -336,11 +378,14 @@ class Provider: url (str): Optional homepage on which the provider describes the dataset and publishes contact information. """ - def __init__(self, - name: str, - description: Optional[str] = None, - roles: Optional[List[str]] = None, - url: Optional[str] = None): + + def __init__( + self, + name: str, + description: Optional[str] = None, + roles: Optional[List[str]] = None, + url: Optional[str] = None, + ): self.name = name self.description = description self.roles = roles @@ -352,13 +397,13 @@ def to_dict(self) -> Dict[str, Any]: Returns: dict: A serialization of the Provider that can be written out as JSON. """ - d: Dict[str, Any] = {'name': self.name} + d: Dict[str, Any] = {"name": self.name} if self.description is not None: - d['description'] = self.description + d["description"] = self.description if self.roles is not None: - d['roles'] = self.roles + d["roles"] = self.roles if self.url is not None: - d['url'] = self.url + d["url"] = self.url return d @@ -369,10 +414,12 @@ def from_dict(d: Dict[str, Any]) -> "Provider": Returns: TemporalExtent: The Provider deserialized from the JSON dict. """ - return Provider(name=d['name'], - description=d.get('description'), - roles=d.get('roles'), - url=d.get('url')) + return Provider( + name=d["name"], + description=d.get("description"), + roles=d.get("roles"), + url=d.get("url"), + ) class RangeSummary(Generic[T]): @@ -381,12 +428,12 @@ def __init__(self, minimum: T, maximum: T): self.maximum = maximum def to_dict(self) -> Dict[str, Any]: - return {'minimum': self.minimum, 'maximum': self.maximum} + return {"minimum": self.minimum, "maximum": self.maximum} @classmethod def from_dict(cls, d: Dict[str, Any], typ: Type[T] = Any) -> "RangeSummary[T]": - minimum: Optional[T] = get_required(d.get('minimum'), 'RangeSummary', 'minimum') - maximum: Optional[T] = get_required(d.get("maximum"), 'RangeSummary', 'maximum') + minimum: Optional[T] = get_required(d.get("minimum"), "RangeSummary", "minimum") + maximum: Optional[T] = get_required(d.get("maximum"), "RangeSummary", "maximum") return cls(minimum=minimum, maximum=maximum) @@ -411,13 +458,18 @@ def get_range(self, prop: str, typ: Type[T]) -> Optional[RangeSummary[T]]: def get_schema(self, prop: str) -> Optional[Dict[str, Any]]: return self.schemas.get(prop) - def add(self, prop_key: str, summary: Union[List[Any], RangeSummary[Any], Dict[str, - Any]]) -> None: + def add( + self, + prop_key: str, + summary: Union[List[Any], RangeSummary[Any], Dict[str, Any]], + ) -> None: if isinstance(summary, list): self.lists[prop_key] = summary elif isinstance(summary, dict): - if 'minimum' in summary: - self.ranges[prop_key] = RangeSummary[Any].from_dict(cast(Dict[str, Any], summary)) + if "minimum" in summary: + self.ranges[prop_key] = RangeSummary[Any].from_dict( + cast(Dict[str, Any], summary) + ) else: self.schemas[prop_key] = summary elif isinstance(summary, RangeSummary): @@ -432,15 +484,16 @@ def remove(self, prop_key: str) -> None: self.other.pop(prop_key, None) def is_empty(self): - return not (any(self.lists) or any(self.ranges) or any(self.schemas) or any(self.other)) + return not ( + any(self.lists) or any(self.ranges) or any(self.schemas) or any(self.other) + ) def to_dict(self) -> Dict[str, Any]: return { **self.lists, - **{k: v.to_dict() - for k, v in self.ranges.items()}, + **{k: v.to_dict() for k, v in self.ranges.items()}, **self.schemas, - **self.other + **self.other, } @classmethod @@ -498,21 +551,31 @@ class Collection(Catalog): DEFAULT_FILE_NAME = "collection.json" """Default file name that will be given to this STAC object in a canonical format.""" - def __init__(self, - id: str, - description: str, - extent: Extent, - title: Optional[str] = None, - stac_extensions: Optional[List[str]] = None, - href: Optional[str] = None, - extra_fields: Optional[Dict[str, Any]] = None, - catalog_type: Optional[CatalogType] = None, - license: str = 'proprietary', - keywords: Optional[List[str]] = None, - providers: Optional[List[Provider]] = None, - summaries: Optional[Summaries] = None): - super().__init__(id, description, title, stac_extensions, extra_fields, href, catalog_type - or CatalogType.ABSOLUTE_PUBLISHED) + + def __init__( + self, + id: str, + description: str, + extent: Extent, + title: Optional[str] = None, + stac_extensions: Optional[List[str]] = None, + href: Optional[str] = None, + extra_fields: Optional[Dict[str, Any]] = None, + catalog_type: Optional[CatalogType] = None, + license: str = "proprietary", + keywords: Optional[List[str]] = None, + providers: Optional[List[Provider]] = None, + summaries: Optional[Summaries] = None, + ): + super().__init__( + id, + description, + title, + stac_extensions, + extra_fields, + href, + catalog_type or CatalogType.ABSOLUTE_PUBLISHED, + ) self.extent = extent self.license = license @@ -524,49 +587,53 @@ def __init__(self, self.assets: Dict[str, Asset] = {} def __repr__(self) -> str: - return ''.format(self.id) - - def add_item(self, - item: "Item_Type", - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None) -> None: + return "".format(self.id) + + def add_item( + self, + item: "Item_Type", + title: Optional[str] = None, + strategy: Optional[HrefLayoutStrategy] = None, + ) -> None: super().add_item(item, title, strategy) item.set_collection(self) def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d = super().to_dict(include_self_link) - d['extent'] = self.extent.to_dict() - d['license'] = self.license + d["extent"] = self.extent.to_dict() + d["license"] = self.license if self.stac_extensions is not None: - d['stac_extensions'] = self.stac_extensions + d["stac_extensions"] = self.stac_extensions if self.keywords is not None: - d['keywords'] = self.keywords + d["keywords"] = self.keywords if self.providers is not None: - d['providers'] = list(map(lambda x: x.to_dict(), self.providers)) + d["providers"] = list(map(lambda x: x.to_dict(), self.providers)) if not self.summaries.is_empty(): - d['summaries'] = self.summaries.to_dict() + d["summaries"] = self.summaries.to_dict() if any(self.assets): - d['assets'] = {k: v.to_dict() for k, v in self.assets.items()} + d["assets"] = {k: v.to_dict() for k, v in self.assets.items()} return d def clone(self) -> "Collection": - clone = Collection(id=self.id, - description=self.description, - extent=self.extent.clone(), - title=self.title, - stac_extensions=self.stac_extensions, - extra_fields=self.extra_fields, - catalog_type=self.catalog_type, - license=self.license, - keywords=self.keywords, - providers=self.providers, - summaries=self.summaries) + clone = Collection( + id=self.id, + description=self.description, + extent=self.extent.clone(), + title=self.title, + stac_extensions=self.stac_extensions, + extra_fields=self.extra_fields, + catalog_type=self.catalog_type, + license=self.license, + keywords=self.keywords, + providers=self.providers, + summaries=self.summaries, + ) clone._resolved_objects.cache(clone) for link in self.links: - if link.rel == 'root': + if link.rel == "root": # Collection __init__ sets correct root to clone; don't reset # if the root link points to self root_is_self = link.is_resolved() and link.target is self @@ -579,11 +646,13 @@ def clone(self) -> "Collection": return clone @classmethod - def from_dict(cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, - migrate: bool = False) -> "Collection": + def from_dict( + cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None, + migrate: bool = False, + ) -> "Collection": if migrate: result = ps.read_dict(d, href=href, root=root) if not isinstance(result, Collection): @@ -593,44 +662,46 @@ def from_dict(cls, catalog_type = CatalogType.determine_type(d) d = deepcopy(d) - id = d.pop('id') - description = d.pop('description') - license = d.pop('license') - extent = Extent.from_dict(d.pop('extent')) - title = d.get('title') - stac_extensions = d.get('stac_extensions') - keywords = d.get('keywords') - providers = d.get('providers') + id = d.pop("id") + description = d.pop("description") + license = d.pop("license") + extent = Extent.from_dict(d.pop("extent")) + title = d.get("title") + stac_extensions = d.get("stac_extensions") + keywords = d.get("keywords") + providers = d.get("providers") if providers is not None: providers = list(map(lambda x: Provider.from_dict(x), providers)) - summaries = d.get('summaries') + summaries = d.get("summaries") if summaries is not None: summaries = Summaries(summaries) - assets: Optional[Dict[str, Any]] = d.get('assets', None) - links = d.pop('links') - - d.pop('stac_version') - - collection = Collection(id=id, - description=description, - extent=extent, - title=title, - stac_extensions=stac_extensions, - extra_fields=d, - license=license, - keywords=keywords, - providers=providers, - summaries=summaries, - href=href, - catalog_type=catalog_type) + assets: Optional[Dict[str, Any]] = d.get("assets", None) + links = d.pop("links") + + d.pop("stac_version") + + collection = Collection( + id=id, + description=description, + extent=extent, + title=title, + stac_extensions=stac_extensions, + extra_fields=d, + license=license, + keywords=keywords, + providers=providers, + summaries=summaries, + href=href, + catalog_type=catalog_type, + ) for link in links: - if link['rel'] == 'root': + if link["rel"] == "root": # Remove the link that's generated in Catalog's constructor. - collection.remove_links('root') + collection.remove_links("root") - if link['rel'] != 'self' or href is None: + if link["rel"] != "self" or href is None: collection.add_link(Link.from_dict(link)) if assets is not None: @@ -663,9 +734,9 @@ def update_extent_from_items(self) -> None: """ self.extent = Extent.from_items(self.get_all_items()) - def full_copy(self, - root: Optional["Catalog"] = None, - parent: Optional["Catalog"] = None) -> "Collection": + def full_copy( + self, root: Optional["Catalog"] = None, parent: Optional["Catalog"] = None + ) -> "Collection": return cast(Collection, super().full_copy(root, parent)) @classmethod diff --git a/pystac/errors.py b/pystac/errors.py index c95f649dc..bf5a80f92 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -6,6 +6,7 @@ class STACError(Exception): invalid formats or trying to operate on a STAC that does not have the required information available. """ + pass @@ -14,11 +15,12 @@ class STACTypeError(Exception): a STAC entity that is not correct for the context; for example, if a Catalog JSON was read in as an Item. """ + pass class RequiredPropertyMissing(Exception): - """ This error is raised when a required value was expected + """This error is raised when a required value was expected to be there but was missing or None. This will happen, for example, in an extension that has required properties, where the required property is missing from the extended object @@ -29,6 +31,9 @@ class RequiredPropertyMissing(Exception): error message, or be a string that describes the object. prop: The property that is missing """ - def __init__(self, obj: Union[str, Any], prop: str, msg: Optional[str] = None) -> None: + + def __init__( + self, obj: Union[str, Any], prop: str, msg: Optional[str] = None + ) -> None: msg = msg or f"{repr(obj)} does not have required property {prop}" super().__init__(msg) diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index 46e827d5d..bb6919ca7 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -2,6 +2,6 @@ class ExtensionError(Exception): - """An error related to the construction of extensions. - """ + """An error related to the construction of extensions.""" + pass diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 050b41250..476c74732 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -12,15 +12,18 @@ class SummariesExtension: def __init__(self, collection: ps.Collection) -> None: self.summaries = collection.summaries - def _set_summary(self, prop_key: str, v: Optional[Union[List[Any], ps.RangeSummary[Any], - Dict[str, Any]]]) -> None: + def _set_summary( + self, + prop_key: str, + v: Optional[Union[List[Any], ps.RangeSummary[Any], Dict[str, Any]]], + ) -> None: if v is None: self.summaries.remove(prop_key) else: self.summaries.add(prop_key, v) -P = TypeVar('P') +P = TypeVar("P") class PropertiesExtension(ABC): @@ -38,14 +41,16 @@ def _get_property(self, prop_name: str, typ: Type[P] = Type[Any]) -> Optional[P] return result return None - def _set_property(self, prop_name: str, v: Optional[Any], pop_if_none: bool = True) -> None: + def _set_property( + self, prop_name: str, v: Optional[Any], pop_if_none: bool = True + ) -> None: if v is None and pop_if_none: self.properties.pop(prop_name, None) else: self.properties[prop_name] = v -S = TypeVar('S', bound=ps.STACObject) +S = TypeVar("S", bound=ps.STACObject) class ExtensionManagementMixin(Generic[S], ABC): @@ -70,4 +75,7 @@ def remove_from(cls, obj: S) -> None: @classmethod def has_extension(cls, obj: S) -> bool: - return (obj.stac_extensions is not None and cls.get_schema_uri() in obj.stac_extensions) + return ( + obj.stac_extensions is not None + and cls.get_schema_uri() in obj.stac_extensions + ) diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index 0a98f1c82..2eacc8f58 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -2,11 +2,15 @@ from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required, map_opt -T = TypeVar('T', ps.Collection, ps.Item, ps.Asset) +T = TypeVar("T", ps.Collection, ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/datacube/v1.0.0/schema.json" @@ -29,7 +33,9 @@ def __init__(self, properties: Dict[str, Any]) -> None: @property def dim_type(self) -> str: - return get_required(self.properties.get(DIM_TYPE_PROP), "cube:dimension", DIM_TYPE_PROP) + return get_required( + self.properties.get(DIM_TYPE_PROP), "cube:dimension", DIM_TYPE_PROP + ) @dim_type.setter def dim_type(self, v: str) -> None: @@ -53,16 +59,16 @@ def to_dict(self) -> Dict[str, Any]: def from_dict(d: Dict[str, Any]) -> "Dimension": dim_type = d.get(DIM_TYPE_PROP) if dim_type is None: - raise ps.RequiredPropertyMissing('cube_dimension', DIM_TYPE_PROP) - if dim_type == 'spatial': + raise ps.RequiredPropertyMissing("cube_dimension", DIM_TYPE_PROP) + if dim_type == "spatial": axis = d.get(DIM_AXIS_PROP) if axis is None: - raise ps.RequiredPropertyMissing('cube_dimension', DIM_AXIS_PROP) - if axis == 'z': + raise ps.RequiredPropertyMissing("cube_dimension", DIM_AXIS_PROP) + if axis == "z": return VerticalSpatialDimension(d) else: return HorizontalSpatialDimension(d) - elif dim_type == 'temporal': + elif dim_type == "temporal": # The v1.0.0 spec says that AdditionalDimensions can have # type 'temporal', but it is unclear how to differentiate that # from a temporal dimension. Just key off of type for now. @@ -75,7 +81,9 @@ def from_dict(d: Dict[str, Any]) -> "Dimension": class HorizontalSpatialDimension(Dimension): @property def axis(self) -> str: - return get_required(self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP) + return get_required( + self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP + ) @axis.setter def axis(self, v: str) -> None: @@ -83,7 +91,9 @@ def axis(self, v: str) -> None: @property def extent(self) -> List[float]: - return get_required(self.properties.get(DIM_EXTENT_PROP), "cube:dimension", DIM_EXTENT_PROP) + return get_required( + self.properties.get(DIM_EXTENT_PROP), "cube:dimension", DIM_EXTENT_PROP + ) @extent.setter def extent(self, v: List[float]) -> None: @@ -129,7 +139,9 @@ def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> No class VerticalSpatialDimension(Dimension): @property def axis(self) -> str: - return get_required(self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP) + return get_required( + self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP + ) @axis.setter def axis(self, v: str) -> None: @@ -292,17 +304,24 @@ def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> No self.properties[DIM_REF_SYS_PROP] = v -class DatacubeExtension(Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): +class DatacubeExtension( + Generic[T], + PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]], +): def apply(self, dimensions: Dict[str, Dimension]) -> None: self.dimensions = dimensions @property def dimensions(self) -> Dict[str, Dimension]: return get_required( - map_opt(lambda d: {k: Dimension.from_dict(v) - for k, v in d.items()}, - self._get_property(DIMENSIONS_PROP, Dict[str, Any])), self, DIMENSIONS_PROP) + map_opt( + lambda d: {k: Dimension.from_dict(v) for k, v in d.items()}, + self._get_property(DIMENSIONS_PROP, Dict[str, Any]), + ), + self, + DIMENSIONS_PROP, + ) @dimensions.setter def dimensions(self, v: Dict[str, Dimension]) -> None: @@ -321,7 +340,9 @@ def ext(obj: T) -> "DatacubeExtension[T]": elif isinstance(obj, ps.Asset): return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) else: - raise ExtensionException(f"Datacube extension does not apply to type {type(obj)}") + raise ExtensionException( + f"Datacube extension does not apply to type {type(obj)}" + ) class CollectionDatacubeExtension(DatacubeExtension[ps.Collection]): @@ -330,7 +351,7 @@ def __init__(self, collection: ps.Collection): self.properties = collection.extra_fields def __repr__(self) -> str: - return ''.format(self.collection.id) + return "".format(self.collection.id) class ItemDatacubeExtension(DatacubeExtension[ps.Item]): @@ -339,7 +360,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetDatacubeExtension(DatacubeExtension[ps.Asset]): @@ -350,14 +371,15 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class DatacubeExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['datacube']) + prev_extension_ids: Set[str] = set(["datacube"]) stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + ) DATACUBE_EXTENSION_HOOKS = DatacubeExtensionHooks() diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 0e4061bca..5c4c08801 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -3,14 +3,18 @@ from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast import pystac as ps -from pystac.extensions.base import (ExtensionException, ExtensionManagementMixin, - PropertiesExtension, SummariesExtension) +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, + SummariesExtension, +) from pystac.extensions.hooks import ExtensionHooks from pystac.extensions import view from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.utils import get_required, map_opt -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -23,15 +27,18 @@ class Band: Use Band.create to create a new Band. """ + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties - def apply(self, - name: str, - common_name: Optional[str] = None, - description: Optional[str] = None, - center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None) -> None: + def apply( + self, + name: str, + common_name: Optional[str] = None, + description: Optional[str] = None, + center_wavelength: Optional[float] = None, + full_width_half_max: Optional[float] = None, + ) -> None: """ Sets the properties for this Band. @@ -52,12 +59,14 @@ def apply(self, self.full_width_half_max = full_width_half_max @classmethod - def create(cls, - name: str, - common_name: Optional[str] = None, - description: Optional[str] = None, - center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None) -> "Band": + def create( + cls, + name: str, + common_name: Optional[str] = None, + description: Optional[str] = None, + center_wavelength: Optional[float] = None, + full_width_half_max: Optional[float] = None, + ) -> "Band": """ Creates a new band. @@ -72,11 +81,13 @@ def create(cls, as measured at half the maximum transmission, in micrometers (μm). """ b = cls({}) - b.apply(name=name, - common_name=common_name, - description=description, - center_wavelength=center_wavelength, - full_width_half_max=full_width_half_max) + b.apply( + name=name, + common_name=common_name, + description=description, + center_wavelength=center_wavelength, + full_width_half_max=full_width_half_max, + ) return b @property @@ -86,11 +97,11 @@ def name(self) -> str: Returns: str """ - return get_required(self.properties['name'], self, 'name') + return get_required(self.properties["name"], self, "name") @name.setter def name(self, v: str) -> None: - self.properties['name'] = v + self.properties["name"] = v @property def common_name(self) -> Optional[str]: @@ -101,14 +112,14 @@ def common_name(self) -> Optional[str]: Returns: Optional[str] """ - return self.properties.get('common_name') + return self.properties.get("common_name") @common_name.setter def common_name(self, v: Optional[str]) -> None: if v is not None: - self.properties['common_name'] = v + self.properties["common_name"] = v else: - self.properties.pop('common_name', None) + self.properties.pop("common_name", None) @property def description(self) -> Optional[str]: @@ -118,14 +129,14 @@ def description(self) -> Optional[str]: Returns: str """ - return self.properties.get('description') + return self.properties.get("description") @description.setter def description(self, v: Optional[str]) -> None: if v is not None: - self.properties['description'] = v + self.properties["description"] = v else: - self.properties.pop('description', None) + self.properties.pop("description", None) @property def center_wavelength(self) -> Optional[float]: @@ -134,14 +145,14 @@ def center_wavelength(self) -> Optional[float]: Returns: float """ - return self.properties.get('center_wavelength') + return self.properties.get("center_wavelength") @center_wavelength.setter def center_wavelength(self, v: Optional[float]) -> None: if v is not None: - self.properties['center_wavelength'] = v + self.properties["center_wavelength"] = v else: - self.properties.pop('center_wavelength', None) + self.properties.pop("center_wavelength", None) @property def full_width_half_max(self) -> Optional[float]: @@ -151,17 +162,17 @@ def full_width_half_max(self) -> Optional[float]: Returns: [float] """ - return self.properties.get('full_width_half_max') + return self.properties.get("full_width_half_max") @full_width_half_max.setter def full_width_half_max(self, v: Optional[float]) -> None: if v is not None: - self.properties['full_width_half_max'] = v + self.properties["full_width_half_max"] = v else: - self.properties.pop('full_width_half_max', None) + self.properties.pop("full_width_half_max", None) def __repr__(self) -> str: - return ''.format(self.name) + return "".format(self.name) def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this Band. @@ -181,24 +192,24 @@ def band_range(common_name: str) -> Optional[Tuple[float, float]]: Returns: Tuple[float, float] or None: The band range for this name as (min, max), or None if this is not a recognized common name. - """ # noqa E501 + """ # noqa E501 name_to_range = { - 'coastal': (0.40, 0.45), - 'blue': (0.45, 0.50), - 'green': (0.50, 0.60), - 'red': (0.60, 0.70), - 'yellow': (0.58, 0.62), - 'pan': (0.50, 0.70), - 'rededge': (0.70, 0.75), - 'nir': (0.75, 1.00), - 'nir08': (0.75, 0.90), - 'nir09': (0.85, 1.05), - 'cirrus': (1.35, 1.40), - 'swir16': (1.55, 1.75), - 'swir22': (2.10, 2.30), - 'lwir': (10.5, 12.5), - 'lwir11': (10.5, 11.5), - 'lwir12': (11.5, 12.5) + "coastal": (0.40, 0.45), + "blue": (0.45, 0.50), + "green": (0.50, 0.60), + "red": (0.60, 0.70), + "yellow": (0.58, 0.62), + "pan": (0.50, 0.70), + "rededge": (0.70, 0.75), + "nir": (0.75, 1.00), + "nir08": (0.75, 0.90), + "nir09": (0.85, 1.05), + "cirrus": (1.35, 1.40), + "swir16": (1.55, 1.75), + "swir22": (2.10, 2.30), + "lwir": (10.5, 12.5), + "lwir11": (10.5, 11.5), + "lwir12": (11.5, 12.5), } return name_to_range.get(common_name) @@ -213,7 +224,7 @@ def band_description(common_name: str) -> Optional[str]: Returns: str or None: If a recognized common name, returns a description including the band range. Otherwise returns None. - """ # noqa E501 + """ # noqa E501 r = Band.band_range(common_name) if r is not None: return "Common name: {}, Range: {} to {}".format(common_name, r[0], r[1]) @@ -234,6 +245,7 @@ class EOExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.I Using EOItemExt to directly wrap an item will add the 'eo' extension ID to the item's stac_extensions. """ + def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: """Applies label extension properties to the extended Item. @@ -249,17 +261,21 @@ def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: @property def bands(self) -> Optional[List[Band]]: """Get or sets a list of :class:`~pystac.Band` objects that represent - the available bands. + the available bands. """ return self._get_bands() def _get_bands(self) -> Optional[List[Band]]: - return map_opt(lambda bands: [Band(b) for b in bands], - self._get_property(BANDS_PROP, List[Dict[str, Any]])) + return map_opt( + lambda bands: [Band(b) for b in bands], + self._get_property(BANDS_PROP, List[Dict[str, Any]]), + ) @bands.setter def bands(self, v: Optional[List[Band]]) -> None: - self._set_property(BANDS_PROP, map_opt(lambda bands: [b.to_dict() for b in bands], v)) + self._set_property( + BANDS_PROP, map_opt(lambda bands: [b.to_dict() for b in bands], v) + ) @property def cloud_cover(self) -> Optional[float]: @@ -300,7 +316,7 @@ def __init__(self, item: ps.Item): def _get_bands(self) -> Optional[List[Band]]: """Get or sets a list of :class:`~pystac.Band` objects that represent - the available bands. + the available bands. """ bands = self._get_property(BANDS_PROP, List[Dict[str, Any]]) @@ -309,7 +325,9 @@ def _get_bands(self) -> Optional[List[Band]]: asset_bands: List[Dict[str, Any]] = [] for _, value in self.item.get_assets().items(): if BANDS_PROP in value.properties: - asset_bands.extend(cast(List[Dict[str, Any]], value.properties.get(BANDS_PROP))) + asset_bands.extend( + cast(List[Dict[str, Any]], value.properties.get(BANDS_PROP)) + ) if any(asset_bands): bands = asset_bands @@ -318,7 +336,7 @@ def _get_bands(self) -> Optional[List[Band]]: return None def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetEOExtension(EOExtension[ps.Asset]): @@ -329,17 +347,19 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class SummariesEOExtension(SummariesExtension): @property def bands(self) -> Optional[List[Band]]: """Get or sets a list of :class:`~pystac.Band` objects that represent - the available bands. + the available bands. """ - return map_opt(lambda bands: [Band(b) for b in bands], - self.summaries.get_list(BANDS_PROP, Dict[str, Any])) + return map_opt( + lambda bands: [Band(b) for b in bands], + self.summaries.get_list(BANDS_PROP, Dict[str, Any]), + ) @bands.setter def bands(self, v: Optional[List[Band]]) -> None: @@ -357,88 +377,105 @@ def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: class EOExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['eo']) + prev_extension_ids: Set[str] = set(["eo"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - if version < '0.5': - if 'eo:crs' in obj['properties']: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: + if version < "0.5": + if "eo:crs" in obj["properties"]: # Try to pull out the EPSG code. # Otherwise, just leave it alone. - wkt = obj['properties']['eo:crs'] + wkt = obj["properties"]["eo:crs"] matches = list(re.finditer(r'AUTHORITY\[[^\]]*\"(\d+)"\]', wkt)) if len(matches) > 0: epsg_code = matches[-1].group(1) - obj['properties'].pop('eo:crs') - obj['properties']['eo:epsg'] = int(epsg_code) + obj["properties"].pop("eo:crs") + obj["properties"]["eo:epsg"] = int(epsg_code) - if version < '0.6': + if version < "0.6": # Change eo:bands from a dict to a list. eo:bands on an asset # is an index instead of a dict key. eo:bands is in properties. - bands_dict = obj['eo:bands'] + bands_dict = obj["eo:bands"] keys_to_indices: Dict[str, int] = {} bands: List[Dict[str, Any]] = [] for i, (k, band) in enumerate(bands_dict.items()): keys_to_indices[k] = i bands.append(band) - obj.pop('eo:bands') - obj['properties']['eo:bands'] = bands - for k, asset in obj['assets'].items(): - if 'eo:bands' in asset: + obj.pop("eo:bands") + obj["properties"]["eo:bands"] = bands + for k, asset in obj["assets"].items(): + if "eo:bands" in asset: asset_band_indices: List[int] = [] - for bk in asset['eo:bands']: + for bk in asset["eo:bands"]: asset_band_indices.append(keys_to_indices[bk]) - asset['eo:bands'] = sorted(asset_band_indices) + asset["eo:bands"] = sorted(asset_band_indices) - if version < '0.9': + if version < "0.9": # Some eo fields became common_metadata - if 'eo:platform' in obj['properties'] and 'platform' not in obj['properties']: - obj['properties']['platform'] = obj['properties']['eo:platform'] - del obj['properties']['eo:platform'] - - if 'eo:instrument' in obj['properties'] and 'instruments' not in obj['properties']: - obj['properties']['instruments'] = [obj['properties']['eo:instrument']] - del obj['properties']['eo:instrument'] - - if 'eo:constellation' in obj['properties'] and 'constellation' not in obj['properties']: - obj['properties']['constellation'] = obj['properties']['eo:constellation'] - del obj['properties']['eo:constellation'] + if ( + "eo:platform" in obj["properties"] + and "platform" not in obj["properties"] + ): + obj["properties"]["platform"] = obj["properties"]["eo:platform"] + del obj["properties"]["eo:platform"] + + if ( + "eo:instrument" in obj["properties"] + and "instruments" not in obj["properties"] + ): + obj["properties"]["instruments"] = [obj["properties"]["eo:instrument"]] + del obj["properties"]["eo:instrument"] + + if ( + "eo:constellation" in obj["properties"] + and "constellation" not in obj["properties"] + ): + obj["properties"]["constellation"] = obj["properties"][ + "eo:constellation" + ] + del obj["properties"]["eo:constellation"] # Some eo fields became view extension fields eo_to_view_fields = [ - 'off_nadir', 'azimuth', 'incidence_angle', 'sun_azimuth', 'sun_elevation' + "off_nadir", + "azimuth", + "incidence_angle", + "sun_azimuth", + "sun_elevation", ] for field in eo_to_view_fields: - if 'eo:{}'.format(field) in obj['properties']: - if 'stac_extensions' not in obj: - obj['stac_extensions'] = [] - if view.SCHEMA_URI not in obj['stac_extensions']: - obj['stac_extensions'].append(view.SCHEMA_URI) - if not 'view:{}'.format(field) in obj['properties']: - obj['properties']['view:{}'.format(field)] = \ - obj['properties']['eo:{}'.format(field)] - del obj['properties']['eo:{}'.format(field)] - - if version < '1.0.0-beta.1' and info.object_type == ps.STACObjectType.ITEM: + if "eo:{}".format(field) in obj["properties"]: + if "stac_extensions" not in obj: + obj["stac_extensions"] = [] + if view.SCHEMA_URI not in obj["stac_extensions"]: + obj["stac_extensions"].append(view.SCHEMA_URI) + if not "view:{}".format(field) in obj["properties"]: + obj["properties"]["view:{}".format(field)] = obj["properties"][ + "eo:{}".format(field) + ] + del obj["properties"]["eo:{}".format(field)] + + if version < "1.0.0-beta.1" and info.object_type == ps.STACObjectType.ITEM: # gsd moved from eo to common metadata - if 'eo:gsd' in obj['properties']: - obj['properties']['gsd'] = obj['properties']['eo:gsd'] - del obj['properties']['eo:gsd'] + if "eo:gsd" in obj["properties"]: + obj["properties"]["gsd"] = obj["properties"]["eo:gsd"] + del obj["properties"]["eo:gsd"] # The way bands were declared in assets changed. # In 1.0.0-beta.1 they are inlined into assets as # opposed to having indices back into a property-level array. - if 'eo:bands' in obj['properties']: - bands = obj['properties']['eo:bands'] - for asset in obj['assets'].values(): - if 'eo:bands' in asset: + if "eo:bands" in obj["properties"]: + bands = obj["properties"]["eo:bands"] + for asset in obj["assets"].values(): + if "eo:bands" in asset: new_bands: List[Dict[str, Any]] = [] - for band_index in asset['eo:bands']: + for band_index in asset["eo:bands"]: new_bands.append(bands[band_index]) - asset['eo:bands'] = new_bands + asset["eo:bands"] = new_bands super().migrate(obj, version, info) diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index bd733f7dc..73123a9de 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -1,21 +1,29 @@ import enum -from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID) +from pystac.serialization.identify import ( + OldExtensionShortIDs, + STACJSONDescription, + STACVersionID, +) from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import (ExtensionException, ExtensionManagementMixin, - PropertiesExtension, SummariesExtension) +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, + SummariesExtension, +) from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/file/v1.0.0/schema.json" -DATA_TYPE_PROP = 'file:data_type' +DATA_TYPE_PROP = "file:data_type" SIZE_PROP = "file:size" -NODATA_PROP = 'file:nodata' -CHECKSUM_PROP = 'file:checksum' +NODATA_PROP = "file:nodata" +CHECKSUM_PROP = "file:checksum" class FileDataType(str, enum.Enum): @@ -54,11 +62,14 @@ class FileExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps Using FileItemExt to directly wrap an item will add the 'file' extension ID to the item's stac_extensions. """ - def apply(self, - data_type: Optional[FileDataType] = None, - size: Optional[int] = None, - nodata: Optional[List[Any]] = None, - checksum: Optional[str] = None) -> None: + + def apply( + self, + data_type: Optional[FileDataType] = None, + size: Optional[int] = None, + nodata: Optional[List[Any]] = None, + checksum: Optional[str] = None, + ) -> None: """Applies file extension properties to the extended Item. Args: @@ -80,7 +91,9 @@ def data_type(self) -> Optional[FileDataType]: Returns: FileDataType """ - return map_opt(lambda s: FileDataType(s), self._get_property(DATA_TYPE_PROP, str)) + return map_opt( + lambda s: FileDataType(s), self._get_property(DATA_TYPE_PROP, str) + ) @data_type.setter def data_type(self, v: Optional[FileDataType]) -> None: @@ -132,7 +145,9 @@ def ext(obj: T) -> "FileExtension[T]": elif isinstance(obj, ps.Asset): return cast(FileExtension[T], AssetFileExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) @staticmethod def summaries(obj: ps.Collection) -> "SummariesFileExtension": @@ -145,7 +160,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetFileExtension(FileExtension[ps.Asset]): @@ -156,7 +171,7 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class SummariesFileExtension(SummariesExtension): @@ -167,8 +182,10 @@ def data_type(self) -> Optional[List[FileDataType]]: Returns: FileDataType """ - return map_opt(lambda x: [FileDataType(t) for t in x], - self.summaries.get_list(DATA_TYPE_PROP, str)) + return map_opt( + lambda x: [FileDataType(t) for t in x], + self.summaries.get_list(DATA_TYPE_PROP, str), + ) @data_type.setter def data_type(self, v: Optional[List[FileDataType]]) -> None: @@ -199,42 +216,43 @@ def nodata(self, v: Optional[List[Any]]) -> None: class FileExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['file']) + prev_extension_ids: Set[str] = set(["file"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: # The checksum field was previously it's own extension. old_checksum: Optional[Dict[str, str]] = None - if info.version_range.latest_valid_version() < 'v1.0.0-rc.2': + if info.version_range.latest_valid_version() < "v1.0.0-rc.2": if OldExtensionShortIDs.CHECKSUM.value in info.extensions: - old_item_checksum = obj['properties'].get('checksum:multihash') + old_item_checksum = obj["properties"].get("checksum:multihash") if old_item_checksum is not None: if old_checksum is None: old_checksum = {} - old_checksum['__item__'] = old_item_checksum - for asset_key, asset in obj['assets'].items(): - old_asset_checksum = asset.get('checksum:multihash') + old_checksum["__item__"] = old_item_checksum + for asset_key, asset in obj["assets"].items(): + old_asset_checksum = asset.get("checksum:multihash") if old_asset_checksum is not None: if old_checksum is None: old_checksum = {} old_checksum[asset_key] = old_asset_checksum try: - obj['stac_extensions'].remove(OldExtensionShortIDs.CHECKSUM.value) + obj["stac_extensions"].remove(OldExtensionShortIDs.CHECKSUM.value) except ValueError: pass super().migrate(obj, version, info) if old_checksum is not None: - if SCHEMA_URI not in obj['stac_extensions']: - obj['stac_extensions'].append(SCHEMA_URI) + if SCHEMA_URI not in obj["stac_extensions"]: + obj["stac_extensions"].append(SCHEMA_URI) for key in old_checksum: - if key == '__item__': - obj['properties'][CHECKSUM_PROP] = old_checksum[key] + if key == "__item__": + obj["properties"][CHECKSUM_PROP] = old_checksum[key] else: - obj['assets'][key][CHECKSUM_PROP] = old_checksum[key] + obj["assets"][key][CHECKSUM_PROP] = old_checksum[key] FILE_EXTENSION_HOOKS = FileExtensionHooks() diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index 3d60b557b..9fffa34e2 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -41,8 +41,9 @@ def _get_stac_object_types(self) -> Set[str]: def get_object_links(self, obj: "STACObject_Type") -> Optional[List[str]]: return None - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: """Migrate a STAC Object in dict format from a previous version. The base implementation will update the stac_extensions to the latest schema ID. This method will only be called for STAC objects that have been @@ -54,10 +55,10 @@ def migrate(self, obj: Dict[str, Any], version: STACVersionID, for prev_id in self.prev_extension_ids: if prev_id in info.extensions: try: - i = obj['stac_extensions'].index(prev_id) - obj['stac_extensions'][i] = self.schema_uri + i = obj["stac_extensions"].index(prev_id) + obj["stac_extensions"][i] = self.schema_uri except ValueError: - obj['stac_extensions'].append(self.schema_uri) + obj["stac_extensions"].append(self.schema_uri) break @@ -68,7 +69,9 @@ def __init__(self, hooks: Iterable[ExtensionHooks]): def add_extension_hooks(self, hooks: ExtensionHooks) -> None: e_id = hooks.schema_uri if e_id in self.hooks: - raise ExtensionError("ExtensionDefinition with id '{}' already exists.".format(e_id)) + raise ExtensionError( + "ExtensionDefinition with id '{}' already exists.".format(e_id) + ) self.hooks[e_id] = hooks @@ -88,8 +91,9 @@ def get_extended_object_links(self, obj: "STACObject_Type") -> List[str]: result.extend(ext_result) return result or [] - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: for hooks in self.hooks.values(): if info.object_type in hooks._get_stac_object_types(): hooks.migrate(obj, version, info) diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index 72799d218..9e0fdc955 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -73,9 +73,18 @@ def create_asset(self, href: str) -> ps.Asset: roles=self.roles, properties={ k: v - for k, v in self.properties if k not in set( - [ASSET_TITLE_PROP, ASSET_DESC_PROP, ASSET_TYPE_PROP, ASSET_ROLES_PROP]) - }) + for k, v in self.properties + if k + not in set( + [ + ASSET_TITLE_PROP, + ASSET_DESC_PROP, + ASSET_TYPE_PROP, + ASSET_ROLES_PROP, + ] + ) + }, + ) class ItemAssetsExtension(ExtensionManagementMixin[ps.Collection]): @@ -84,15 +93,15 @@ def __init__(self, collection: ps.Collection) -> None: @property def item_assets(self) -> Dict[str, AssetDefinition]: - result = get_required(self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, - ITEM_ASSETS_PROP) + result = get_required( + self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, ITEM_ASSETS_PROP + ) return {k: AssetDefinition(v) for k, v in result.items()} @item_assets.setter def item_assets(self, v: Dict[str, AssetDefinition]) -> None: self.collection.extra_fields[ITEM_ASSETS_PROP] = { - k: asset_def.properties - for k, asset_def in v.items() + k: asset_def.properties for k, asset_def in v.items() } def __repr__(self) -> str: @@ -109,17 +118,18 @@ def ext(cls, collection: ps.Collection) -> "ItemAssetsExtension": class ItemAssetsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['asset', 'item-assets']) + prev_extension_ids: Set[str] = set(["asset", "item-assets"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.COLLECTION]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: # Handle that the "item-assets" extension had the id of "assets", before # collection assets (since removed) took over the ID of "assets" - if version < '1.0.0-beta.1' and 'asset' in info.extensions: - if 'assets' in obj: - obj['item_assets'] = obj['assets'] - del obj['assets'] + if version < "1.0.0-beta.1" and "asset" in info.extensions: + if "assets" in obj: + obj["item_assets"] = obj["assets"] + del obj["assets"] super().migrate(obj, version, info) diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 052b579c2..5854d1c31 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -13,11 +13,12 @@ class LabelType(str, Enum): """Enumerates valid label types (RASTER or VECTOR).""" + def __str__(self) -> str: return str(self.value) - VECTOR = 'vector' - RASTER = 'raster' + VECTOR = "vector" + RASTER = "raster" ALL = [VECTOR, RASTER] """Convenience attribute for checking if values are valid label types""" @@ -28,12 +29,15 @@ class LabelClasses: Use LabelClasses.create to create a new instance of LabelClasses from property values. """ + def __init__(self, properties: Dict[str, Any]): self.properties = properties - def apply(self, - classes: Union[List[str], List[int], List[float]], - name: Optional[str] = None) -> None: + def apply( + self, + classes: Union[List[str], List[int], List[float]], + name: Optional[str] = None, + ) -> None: """Sets the properties for this LabelClasses. Args: @@ -45,9 +49,11 @@ class labels. If labels are raster-formatted, do not supply; required otherwise. self.name = name @classmethod - def create(cls, - classes: Union[List[str], List[int], List[float]], - name: Optional[str] = None) -> "LabelClasses": + def create( + cls, + classes: Union[List[str], List[int], List[float]], + name: Optional[str] = None, + ) -> "LabelClasses": """Creates a new LabelClasses. Args: @@ -69,9 +75,11 @@ def classes(self) -> Union[List[str], List[int], List[float]]: Returns: List[str] or List[int] or List[float] """ - result = self.properties.get('classes') + result = self.properties.get("classes") if result is None: - raise ps.STACError(f'LabelClasses does not contain classes property: {self.properties}') + raise ps.STACError( + f"LabelClasses does not contain classes property: {self.properties}" + ) return result @classes.setter @@ -79,24 +87,26 @@ def classes(self, v: Union[List[str], List[int], List[float]]) -> None: if not type(v) is list: raise ps.STACError("classes must be a list! Invalid input: {}".format(v)) - self.properties['classes'] = v + self.properties["classes"] = v @property def name(self) -> Optional[str]: """Get or sets the property key within the asset's each Feature corresponding to class labels. If labels are raster-formatted, do not supply; required otherwise. """ - return self.properties.get('name') + return self.properties.get("name") @name.setter def name(self, v: Optional[str]) -> None: if v is not None: - self.properties['name'] = v + self.properties["name"] = v else: - self.properties.pop('name', None) + self.properties.pop("name", None) def __repr__(self) -> str: - return ''.format(','.join([str(x) for x in self.classes])) + return "".format( + ",".join([str(x) for x in self.classes]) + ) def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this LabelClasses. @@ -112,6 +122,7 @@ class LabelCount: Use LabelCount.create to create a new LabelCount """ + def __init__(self, properties: Dict[str, Any]): self.properties = properties @@ -144,14 +155,14 @@ def name(self) -> str: Returns: str """ - result = self.properties.get('name') + result = self.properties.get("name") if result is None: raise ps.STACError(f"Label count has no name property: {self.properties}") return result @name.setter def name(self, v: str) -> None: - self.properties['name'] = v + self.properties["name"] = v @property def count(self) -> int: @@ -160,14 +171,14 @@ def count(self) -> int: Returns: int """ - result = self.properties.get('count') + result = self.properties.get("count") if result is None: raise ps.STACError(f"Label count has no count property: {self.properties}") return result @count.setter def count(self, v: int) -> None: - self.properties['count'] = v + self.properties["count"] = v def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this LabelCount. @@ -175,7 +186,7 @@ def to_dict(self) -> Dict[str, Any]: Returns: dict: The wrapped dict of the LabelCount that can be written out as JSON. """ - return {'name': self.name, 'count': self.count} + return {"name": self.name, "count": self.count} class LabelStatistics: @@ -183,6 +194,7 @@ class LabelStatistics: Use LabelStatistics.create to create a new instance. """ + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties @@ -215,14 +227,16 @@ def name(self) -> str: Returns: str """ - result = self.properties.get('name') + result = self.properties.get("name") if result is None: - raise ps.STACError(f"Label statistics has no name property: {self.properties}") + raise ps.STACError( + f"Label statistics has no name property: {self.properties}" + ) return result @name.setter def name(self, v: str) -> None: - self.properties['name'] = v + self.properties["name"] = v @property def value(self) -> float: @@ -231,14 +245,16 @@ def value(self) -> float: Returns: float """ - result = self.properties.get('value') + result = self.properties.get("value") if result is None: - raise ps.STACError(f"Label statistics has no value property: {self.properties}") + raise ps.STACError( + f"Label statistics has no value property: {self.properties}" + ) return result @value.setter def value(self, v: float) -> None: - self.properties['value'] = v + self.properties["value"] = v def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this LabelStatistics. @@ -246,7 +262,7 @@ def to_dict(self) -> Dict[str, Any]: Returns: dict: The wrapped dict of the LabelStatistics that can be written out as JSON. """ - return {'name': self.name, 'value': self.value} + return {"name": self.name, "value": self.value} class LabelOverview: @@ -255,13 +271,16 @@ class LabelOverview: Use LabelOverview.create to create a new LabelOverview. """ + def __init__(self, properties: Dict[str, Any]): self.properties = properties - def apply(self, - property_key: Optional[str], - counts: Optional[List[LabelCount]] = None, - statistics: Optional[List[LabelStatistics]] = None) -> None: + def apply( + self, + property_key: Optional[str], + counts: Optional[List[LabelCount]] = None, + statistics: Optional[List[LabelStatistics]] = None, + ) -> None: """Sets the properties for this LabelOverview. Either ``counts`` or ``statistics``, or both, can be placed in an overview; @@ -281,10 +300,12 @@ def apply(self, self.statistics = statistics @classmethod - def create(cls, - property_key: Optional[str], - counts: Optional[List[LabelCount]] = None, - statistics: Optional[List[LabelStatistics]] = None) -> "LabelOverview": + def create( + cls, + property_key: Optional[str], + counts: Optional[List[LabelCount]] = None, + statistics: Optional[List[LabelStatistics]] = None, + ) -> "LabelOverview": """Creates a new LabelOverview. Either ``counts`` or ``statistics``, or both, can be placed in an overview; @@ -308,11 +329,11 @@ def property_key(self) -> Optional[str]: Returns: str """ - return self.properties.get('property_key') + return self.properties.get("property_key") @property_key.setter def property_key(self, v: Optional[str]) -> None: - self.properties['property_key'] = v + self.properties["property_key"] = v @property def counts(self) -> Optional[List[LabelCount]]: @@ -321,7 +342,7 @@ def counts(self) -> Optional[List[LabelCount]]: Returns: List[LabelCount] """ - counts = self.properties.get('counts') + counts = self.properties.get("counts") if counts is None: return None return [LabelCount(c) for c in counts] @@ -329,12 +350,12 @@ def counts(self) -> Optional[List[LabelCount]]: @counts.setter def counts(self, v: Optional[List[LabelCount]]) -> None: if v is None: - self.properties.pop('counts', None) + self.properties.pop("counts", None) else: if not isinstance(v, list): raise ps.STACError("counts must be a list! Invalid input: {}".format(v)) - self.properties['counts'] = [c.to_dict() for c in v] + self.properties["counts"] = [c.to_dict() for c in v] @property def statistics(self) -> Optional[List[LabelStatistics]]: @@ -344,7 +365,7 @@ def statistics(self) -> Optional[List[LabelStatistics]]: Returns: List[Statistics] """ - statistics = self.properties.get('statistics') + statistics = self.properties.get("statistics") if statistics is None: return None @@ -353,12 +374,14 @@ def statistics(self) -> Optional[List[LabelStatistics]]: @statistics.setter def statistics(self, v: Optional[List[LabelStatistics]]) -> None: if v is None: - self.properties.pop('statistics', None) + self.properties.pop("statistics", None) else: if not isinstance(v, list): - raise ps.STACError("statistics must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "statistics must be a list! Invalid input: {}".format(v) + ) - self.properties['statistics'] = [s.to_dict() for s in v] + self.properties["statistics"] = [s.to_dict() for s in v] def merge_counts(self, other: "LabelOverview") -> "LabelOverview": """Merges the counts associated with this overview with another overview. @@ -371,7 +394,7 @@ def merge_counts(self, other: "LabelOverview") -> "LabelOverview": LabelOverview: A new LabelOverview with the counts merged. This will drop any statistics associated with either of the LabelOverviews. """ - assert (self.property_key == other.property_key) + assert self.property_key == other.property_key new_counts = None if self.counts is None: @@ -420,20 +443,22 @@ class LabelExtension(ExtensionManagementMixin[ps.Item]): Note: Using LabelItemExt to directly wrap an item will add the 'label' extension ID to the item's stac_extensions. - """ # noqa E501 + """ # noqa E501 def __init__(self, item: ps.Item) -> None: self.obj = item self.schema_uri = SCHEMA_URI - def apply(self, - label_description: str, - label_type: LabelType, - label_properties: Optional[List[str]] = None, - label_classes: Optional[List[LabelClasses]] = None, - label_tasks: Optional[List[str]] = None, - label_methods: Optional[List[str]] = None, - label_overviews: Optional[List[LabelOverview]] = None) -> None: + def apply( + self, + label_description: str, + label_type: LabelType, + label_properties: Optional[List[str]] = None, + label_classes: Optional[List[LabelClasses]] = None, + label_tasks: Optional[List[str]] = None, + label_methods: Optional[List[str]] = None, + label_overviews: Optional[List[LabelOverview]] = None, + ) -> None: """Applies label extension properties to the extended Item. Args: @@ -455,7 +480,7 @@ def apply(self, label_overviews (List[LabelOverview]): Optional list of LabelOverview classes that store counts (for classification-type data) or summary statistics (for continuous numerical/regression data). - """ # noqa E501 + """ # noqa E501 self.label_description = label_description self.label_type = label_type self.label_properties = label_properties @@ -472,20 +497,19 @@ def label_description(self) -> str: Returns: str """ - result = self.obj.properties.get('label:description') + result = self.obj.properties.get("label:description") if result is None: raise ps.STACError(f"label:description not set for item {self.obj.id}") return result @label_description.setter def label_description(self, v: str) -> None: - self.obj.properties['label:description'] = v + self.obj.properties["label:description"] = v @property def label_type(self) -> LabelType: - """Gets or sets an ENUM of either vector label type or raster label type. - """ - result = self.obj.properties.get('label:type') + """Gets or sets an ENUM of either vector label type or raster label type.""" + result = self.obj.properties.get("label:type") if result is None: raise ps.STACError(f"label:type is not set for item {self.obj.id}") return LabelType(result) @@ -493,10 +517,12 @@ def label_type(self) -> LabelType: @label_type.setter def label_type(self, v: LabelType) -> None: if v not in LabelType.ALL: - raise ps.STACError("label_type must be one of " - "{}. Invalid input: {}".format(LabelType.ALL, v)) + raise ps.STACError( + "label_type must be one of " + "{}. Invalid input: {}".format(LabelType.ALL, v) + ) - self.obj.properties['label:type'] = v + self.obj.properties["label:type"] = v @property def label_properties(self) -> Optional[List[str]]: @@ -510,15 +536,17 @@ def label_properties(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.obj.properties.get('label:properties') + return self.obj.properties.get("label:properties") @label_properties.setter def label_properties(self, v: Optional[List[str]]) -> None: if v is not None: if not isinstance(v, list): - raise ps.STACError("label_properties must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "label_properties must be a list! Invalid input: {}".format(v) + ) - self.obj.properties['label:properties'] = v + self.obj.properties["label:properties"] = v @property def label_classes(self) -> Optional[List[LabelClasses]]: @@ -530,7 +558,7 @@ def label_classes(self) -> Optional[List[LabelClasses]]: Returns: List[LabelClasses] or None """ - label_classes = self.obj.properties.get('label:classes') + label_classes = self.obj.properties.get("label:classes") if label_classes is not None: return [LabelClasses(classes) for classes in label_classes] else: @@ -539,13 +567,15 @@ def label_classes(self) -> Optional[List[LabelClasses]]: @label_classes.setter def label_classes(self, v: Optional[List[LabelClasses]]) -> None: if v is None: - self.obj.properties.pop('label:classes', None) + self.obj.properties.pop("label:classes", None) else: if not isinstance(v, list): - raise ps.STACError("label_classes must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "label_classes must be a list! Invalid input: {}".format(v) + ) classes = [x.to_dict() for x in v] - self.obj.properties['label:classes'] = classes + self.obj.properties["label:classes"] = classes @property def label_tasks(self) -> Optional[List[str]]: @@ -555,17 +585,19 @@ def label_tasks(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.obj.properties.get('label:tasks') + return self.obj.properties.get("label:tasks") @label_tasks.setter def label_tasks(self, v: Optional[List[str]]) -> None: if v is None: - self.obj.properties.pop('label:tasks', None) + self.obj.properties.pop("label:tasks", None) else: if not isinstance(v, list): - raise ps.STACError("label_tasks must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "label_tasks must be a list! Invalid input: {}".format(v) + ) - self.obj.properties['label:tasks'] = v + self.obj.properties["label:tasks"] = v @property def label_methods(self) -> Optional[List[str]]: @@ -575,17 +607,19 @@ def label_methods(self) -> Optional[List[str]]: Returns: List[str] or None """ - return self.obj.properties.get('label:methods') + return self.obj.properties.get("label:methods") @label_methods.setter def label_methods(self, v: Optional[List[str]]) -> None: if v is None: - self.obj.properties.pop('label:methods', None) + self.obj.properties.pop("label:methods", None) else: if not isinstance(v, list): - raise ps.STACError("label_methods must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "label_methods must be a list! Invalid input: {}".format(v) + ) - self.obj.properties['label:methods'] = v + self.obj.properties["label:methods"] = v @property def label_overviews(self) -> Optional[List[LabelOverview]]: @@ -596,7 +630,7 @@ def label_overviews(self) -> Optional[List[LabelOverview]]: Returns: List[LabelOverview] or None """ - overviews = self.obj.properties.get('label:overviews') + overviews = self.obj.properties.get("label:overviews") if overviews is not None: return [LabelOverview(overview) for overview in overviews] else: @@ -605,21 +639,25 @@ def label_overviews(self) -> Optional[List[LabelOverview]]: @label_overviews.setter def label_overviews(self, v: Optional[List[LabelOverview]]) -> None: if v is None: - self.obj.properties.pop('label:overviews', None) + self.obj.properties.pop("label:overviews", None) else: if not isinstance(v, list): - raise ps.STACError("label_overviews must be a list! Invalid input: {}".format(v)) + raise ps.STACError( + "label_overviews must be a list! Invalid input: {}".format(v) + ) overviews = [x.to_dict() for x in v] - self.obj.properties['label:overviews'] = overviews + self.obj.properties["label:overviews"] = overviews def __repr__(self) -> str: - return ''.format(self.obj.id) - - def add_source(self, - source_item: ps.Item, - title: Optional[str] = None, - assets: Optional[List[str]] = None) -> None: + return "".format(self.obj.id) + + def add_source( + self, + source_item: ps.Item, + title: Optional[str] = None, + assets: Optional[List[str]] = None, + ) -> None: """Adds a link to a source item. Args: @@ -630,12 +668,14 @@ def add_source(self, """ properties = None if assets is not None: - properties = {'label:assets': assets} - link = ps.Link('source', - source_item, - title=title, - media_type='application/json', - properties=properties) + properties = {"label:assets": assets} + link = ps.Link( + "source", + source_item, + title=title, + media_type="application/json", + properties=properties, + ) self.obj.add_link(link) def get_sources(self) -> Iterable[ps.Item]: @@ -646,13 +686,15 @@ def get_sources(self) -> Iterable[ps.Item]: Generator[Items]: A possibly empty list of source imagery items. Determined by links of this LabelItem that have ``rel=='source'``. """ - return map(lambda x: cast(ps.Item, x), self.obj.get_stac_objects('source')) + return map(lambda x: cast(ps.Item, x), self.obj.get_stac_objects("source")) - def add_labels(self, - href: str, - title: Optional[str] = None, - media_type: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None) -> None: + def add_labels( + self, + href: str, + title: Optional[str] = None, + media_type: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: """Adds a label asset to this LabelItem. Args: @@ -666,13 +708,18 @@ def add_labels(self, """ self.obj.add_asset( - "labels", ps.Asset(href=href, title=title, media_type=media_type, - properties=properties)) - - def add_geojson_labels(self, - href: str, - title: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None) -> None: + "labels", + ps.Asset( + href=href, title=title, media_type=media_type, properties=properties + ), + ) + + def add_geojson_labels( + self, + href: str, + title: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: """Adds a GeoJSON label asset to this LabelItem. Args: @@ -682,7 +729,9 @@ def add_geojson_labels(self, extensions as a way to serialize and deserialize properties on asset object JSON. """ - self.add_labels(href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON) + self.add_labels( + href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON + ) @classmethod def get_schema_uri(cls) -> str: @@ -695,35 +744,36 @@ def ext(cls, obj: ps.Item) -> "LabelExtension": class LabelExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['label']) + prev_extension_ids: Set[str] = set(["label"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: if isinstance(so, ps.Item): - return ['source'] + return ["source"] return None - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - if info.object_type == ps.STACObjectType.ITEM and version < '1.0.0': - props = obj['properties'] + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: + if info.object_type == ps.STACObjectType.ITEM and version < "1.0.0": + props = obj["properties"] # Migrate 0.8.0-rc1 non-pluralized forms # As it's a common mistake, convert for any pre-1.0.0 version. - if 'label:property' in props and 'label:properties' not in props: - props['label:properties'] = props['label:property'] - del props['label:property'] + if "label:property" in props and "label:properties" not in props: + props["label:properties"] = props["label:property"] + del props["label:property"] - if 'label:task' in props and 'label:tasks' not in props: - props['label:tasks'] = props['label:task'] - del props['label:task'] + if "label:task" in props and "label:tasks" not in props: + props["label:tasks"] = props["label:task"] + del props["label:task"] - if 'label:overview' in props and 'label:overviews' not in props: - props['label:overviews'] = props['label:overview'] - del props['label:overview'] + if "label:overview" in props and "label:overviews" not in props: + props["label:overviews"] = props["label:overview"] + del props["label:overview"] - if 'label:method' in props and 'label:methods' not in props: - props['label:methods'] = props['label:method'] - del props['label:method'] + if "label:method" in props and "label:methods" not in props: + props["label:methods"] = props["label:method"] + del props["label:method"] super().migrate(obj, version, info) diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 17df24830..af8860965 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -2,19 +2,23 @@ from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" -COUNT_PROP = 'pc:count' +COUNT_PROP = "pc:count" TYPE_PROP = "pc:type" -ENCODING_PROP = 'pc:encoding' -SCHEMAS_PROP = 'pc:schemas' -DENSITY_PROP = 'pc:density' -STATISTICS_PROP = 'pc:statistics' +ENCODING_PROP = "pc:encoding" +SCHEMAS_PROP = "pc:schemas" +DENSITY_PROP = "pc:density" +STATISTICS_PROP = "pc:statistics" class PointcloudSchema: @@ -22,6 +26,7 @@ class PointcloudSchema: Use PointCloudSchema.create to create a new instance of PointCloudSchema from properties. """ + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties @@ -33,9 +38,9 @@ def apply(self, name: str, size: int, type: str) -> None: size (int): The size of the dimension in bytes. Whole bytes are supported. type (str): Dimension type. Valid values are `floating`, `unsigned`, and `signed` """ - self.properties['name'] = name - self.properties['size'] = size - self.properties['type'] = type + self.properties["name"] = name + self.properties["size"] = size + self.properties["type"] = type @classmethod def create(cls, name: str, size: int, type: str) -> "PointcloudSchema": @@ -60,9 +65,11 @@ def size(self) -> int: Returns: int """ - result = self.properties.get('size') + result = self.properties.get("size") if result is None: - raise ps.STACError(f"Pointcloud schema does not have size property: {self.properties}") + raise ps.STACError( + f"Pointcloud schema does not have size property: {self.properties}" + ) return result @size.setter @@ -70,7 +77,7 @@ def size(self, v: int) -> None: if not isinstance(v, int): raise ps.STACError("size must be an int! Invalid input: {}".format(v)) - self.properties['size'] = v + self.properties["size"] = v @property def name(self) -> str: @@ -79,14 +86,16 @@ def name(self) -> str: Returns: str """ - result = self.properties.get('name') + result = self.properties.get("name") if result is None: - raise ps.STACError(f"Pointcloud schema does not have name property: {self.properties}") + raise ps.STACError( + f"Pointcloud schema does not have name property: {self.properties}" + ) return result @name.setter def name(self, v: str) -> None: - self.properties['name'] = v + self.properties["name"] = v @property def type(self) -> str: @@ -95,17 +104,21 @@ def type(self) -> str: Returns: str """ - result = self.properties.get('type') + result = self.properties.get("type") if result is None: - raise ps.STACError(f"Pointcloud schema has no type property: {self.properties}") + raise ps.STACError( + f"Pointcloud schema has no type property: {self.properties}" + ) return result @type.setter def type(self, v: str) -> None: - self.properties['type'] = v + self.properties["type"] = v def __repr__(self) -> str: - return ''.format(self.name, self.size, self.type) + return "".format( + self.name, self.size, self.type + ) def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointCloudSchema. @@ -121,18 +134,21 @@ class PointcloudStatistic: Use PointcloudStatistic.create to create a new instance of LabelClasses from property values. """ + def __init__(self, properties: Dict[str, Any]) -> None: self.properties = properties - def apply(self, - name: str, - position: Optional[int] = None, - average: Optional[float] = None, - count: Optional[int] = None, - maximum: Optional[float] = None, - minimum: Optional[float] = None, - stddev: Optional[float] = None, - variance: Optional[float] = None) -> None: + def apply( + self, + name: str, + position: Optional[int] = None, + average: Optional[float] = None, + count: Optional[int] = None, + maximum: Optional[float] = None, + minimum: Optional[float] = None, + stddev: Optional[float] = None, + variance: Optional[float] = None, + ) -> None: """Sets the properties for this PointcloudStatistic. Args: @@ -145,25 +161,27 @@ def apply(self, stddev (float): The standard deviation of the channel. variance (float): The variance of the channel. """ - self.properties['name'] = name - self.properties['position'] = position - self.properties['average'] = average - self.properties['count'] = count - self.properties['maximum'] = maximum - self.properties['minimum'] = minimum - self.properties['stddev'] = stddev - self.properties['variance'] = variance + self.properties["name"] = name + self.properties["position"] = position + self.properties["average"] = average + self.properties["count"] = count + self.properties["maximum"] = maximum + self.properties["minimum"] = minimum + self.properties["stddev"] = stddev + self.properties["variance"] = variance @classmethod - def create(cls, - name: str, - position: Optional[int] = None, - average: Optional[float] = None, - count: Optional[int] = None, - maximum: Optional[float] = None, - minimum: Optional[float] = None, - stddev: Optional[float] = None, - variance: Optional[float] = None) -> "PointcloudStatistic": + def create( + cls, + name: str, + position: Optional[int] = None, + average: Optional[float] = None, + count: Optional[int] = None, + maximum: Optional[float] = None, + minimum: Optional[float] = None, + stddev: Optional[float] = None, + variance: Optional[float] = None, + ) -> "PointcloudStatistic": """Creates a new PointcloudStatistic class. Args: @@ -180,14 +198,16 @@ def create(cls, LabelClasses """ c = cls({}) - c.apply(name=name, - position=position, - average=average, - count=count, - maximum=maximum, - minimum=minimum, - stddev=stddev, - variance=variance) + c.apply( + name=name, + position=position, + average=average, + count=count, + maximum=maximum, + minimum=minimum, + stddev=stddev, + variance=variance, + ) return c @property @@ -197,18 +217,19 @@ def name(self) -> str: Returns: str """ - result = self.properties.get('name') + result = self.properties.get("name") if result is None: raise ps.STACError( - f"Pointcloud statistics does not have name property: {self.properties}") + f"Pointcloud statistics does not have name property: {self.properties}" + ) return result @name.setter def name(self, v: str) -> None: if v is not None: - self.properties['name'] = v + self.properties["name"] = v else: - self.properties.pop('name', None) + self.properties.pop("name", None) @property def position(self) -> Optional[int]: @@ -217,14 +238,14 @@ def position(self) -> Optional[int]: Returns: int """ - return self.properties.get('position') + return self.properties.get("position") @position.setter def position(self, v: Optional[int]) -> None: if v is not None: - self.properties['position'] = v + self.properties["position"] = v else: - self.properties.pop('position', None) + self.properties.pop("position", None) @property def average(self) -> Optional[float]: @@ -233,14 +254,14 @@ def average(self) -> Optional[float]: Returns: float """ - return self.properties.get('average') + return self.properties.get("average") @average.setter def average(self, v: Optional[float]) -> None: if v is not None: - self.properties['average'] = v + self.properties["average"] = v else: - self.properties.pop('average', None) + self.properties.pop("average", None) @property def count(self) -> Optional[int]: @@ -249,14 +270,14 @@ def count(self) -> Optional[int]: Returns: int """ - return self.properties.get('count') + return self.properties.get("count") @count.setter def count(self, v: Optional[int]) -> None: if v is not None: - self.properties['count'] = v + self.properties["count"] = v else: - self.properties.pop('count', None) + self.properties.pop("count", None) @property def maximum(self) -> Optional[float]: @@ -265,14 +286,14 @@ def maximum(self) -> Optional[float]: Returns: float """ - return self.properties.get('maximum') + return self.properties.get("maximum") @maximum.setter def maximum(self, v: Optional[float]) -> None: if v is not None: - self.properties['maximum'] = v + self.properties["maximum"] = v else: - self.properties.pop('maximum', None) + self.properties.pop("maximum", None) @property def minimum(self) -> Optional[float]: @@ -281,14 +302,14 @@ def minimum(self) -> Optional[float]: Returns: float """ - return self.properties.get('minimum') + return self.properties.get("minimum") @minimum.setter def minimum(self, v: Optional[float]) -> None: if v is not None: - self.properties['minimum'] = v + self.properties["minimum"] = v else: - self.properties.pop('minimum', None) + self.properties.pop("minimum", None) @property def stddev(self) -> Optional[float]: @@ -297,14 +318,14 @@ def stddev(self) -> Optional[float]: Returns: float """ - return self.properties.get('stddev') + return self.properties.get("stddev") @stddev.setter def stddev(self, v: Optional[float]) -> None: if v is not None: - self.properties['stddev'] = v + self.properties["stddev"] = v else: - self.properties.pop('stddev', None) + self.properties.pop("stddev", None) @property def variance(self) -> Optional[float]: @@ -313,17 +334,17 @@ def variance(self) -> Optional[float]: Returns: float """ - return self.properties.get('variance') + return self.properties.get("variance") @variance.setter def variance(self, v: Optional[float]) -> None: if v is not None: - self.properties['variance'] = v + self.properties["variance"] = v else: - self.properties.pop('variance', None) + self.properties.pop("variance", None) def __repr__(self) -> str: - return ''.format(str(self.properties)) + return "".format(str(self.properties)) def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointcloudStatistic. @@ -334,7 +355,9 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class PointcloudExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class PointcloudExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] +): """PointcloudItemExt is the extension of an Item in the PointCloud Extension. The Pointclout extension adds pointcloud information to STAC Items. @@ -345,14 +368,17 @@ class PointcloudExtension(Generic[T], PropertiesExtension, ExtensionManagementMi item (Item): The Item that is being extended. """ - def apply(self, - count: int, - type: str, - encoding: str, - schemas: List[PointcloudSchema], - density: Optional[float] = None, - statistics: Optional[List[PointcloudStatistic]] = None, - epsg: Optional[int] = None) -> None: # TODO: Remove epsg per spec + + def apply( + self, + count: int, + type: str, + encoding: str, + schemas: List[PointcloudSchema], + density: Optional[float] = None, + statistics: Optional[List[PointcloudStatistic]] = None, + epsg: Optional[int] = None, + ) -> None: # TODO: Remove epsg per spec """Applies Pointcloud extension properties to the extended Item. Args: @@ -491,7 +517,9 @@ def ext(obj: T) -> "PointcloudExtension[T]": elif isinstance(obj, ps.Asset): return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class ItemPointcloudExtension(PointcloudExtension[ps.Item]): @@ -500,7 +528,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetPointcloudExtension(PointcloudExtension[ps.Asset]): @@ -514,12 +542,12 @@ def __init__(self, asset: ps.Asset): self.repr_id = f"href={asset.href}" def __repr__(self) -> str: - return f'' + return f"" class PointcloudExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['pointcloud']) + prev_extension_ids: Set[str] = set(["pointcloud"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 0f2a0d0cc..2d859bbfb 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,24 +1,30 @@ from pystac.extensions.hooks import ExtensionHooks -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast import pystac as ps -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/projection/v1.0.0/schema.json" -EPSG_PROP = 'proj:epsg' -WKT2_PROP = 'proj:wkt2' -PROJJSON_PROP = 'proj:projjson' -GEOM_PROP = 'proj:geometry' -BBOX_PROP = 'proj:bbox' -CENTROID_PROP = 'proj:centroid' -SHAPE_PROP = 'proj:shape' -TRANSFORM_PROP = 'proj:transform' +EPSG_PROP = "proj:epsg" +WKT2_PROP = "proj:wkt2" +PROJJSON_PROP = "proj:projjson" +GEOM_PROP = "proj:geometry" +BBOX_PROP = "proj:bbox" +CENTROID_PROP = "proj:centroid" +SHAPE_PROP = "proj:shape" +TRANSFORM_PROP = "proj:transform" -class ProjectionExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class ProjectionExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] +): """ProjectionItemExt is the extension of an Item in the Projection Extension. The Projection extension adds projection information to STAC Items. @@ -32,18 +38,21 @@ class ProjectionExtension(Generic[T], PropertiesExtension, ExtensionManagementMi Using ProjectionItemExt to directly wrap an item will add the 'proj' extension ID to the item's stac_extensions. """ + def __init__(self, item: ps.Item) -> None: self.item = item - def apply(self, - epsg: Optional[int], - wkt2: Optional[str] = None, - projjson: Optional[Dict[str, Any]] = None, - geometry: Optional[Dict[str, Any]] = None, - bbox: Optional[List[float]] = None, - centroid: Optional[Dict[str, float]] = None, - shape: Optional[List[int]] = None, - transform: Optional[List[float]] = None) -> None: + def apply( + self, + epsg: Optional[int], + wkt2: Optional[str] = None, + projjson: Optional[Dict[str, Any]] = None, + geometry: Optional[Dict[str, Any]] = None, + bbox: Optional[List[float]] = None, + centroid: Optional[Dict[str, float]] = None, + shape: Optional[List[int]] = None, + transform: Optional[List[float]] = None, + ) -> None: """Applies Projection extension properties to the extended Item. Args: @@ -245,7 +254,9 @@ def ext(obj: T) -> "ProjectionExtension[T]": elif isinstance(obj, ps.Asset): return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class ItemProjectionExtension(ProjectionExtension[ps.Item]): @@ -254,7 +265,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetProjectionExtension(ProjectionExtension[ps.Asset]): @@ -265,12 +276,12 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class ProjectionExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['proj', 'projection']) + prev_extension_ids: Set[str] = set(["proj", "projection"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 087e184fc..47f9e0274 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -13,52 +13,54 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required, map_opt -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/sar/v1.0.0/schema.json" # Required -INSTRUMENT_MODE: str = 'sar:instrument_mode' -FREQUENCY_BAND: str = 'sar:frequency_band' -POLARIZATIONS: str = 'sar:polarizations' -PRODUCT_TYPE: str = 'sar:product_type' +INSTRUMENT_MODE: str = "sar:instrument_mode" +FREQUENCY_BAND: str = "sar:frequency_band" +POLARIZATIONS: str = "sar:polarizations" +PRODUCT_TYPE: str = "sar:product_type" # Not required -CENTER_FREQUENCY: str = 'sar:center_frequency' -RESOLUTION_RANGE: str = 'sar:resolution_range' -RESOLUTION_AZIMUTH: str = 'sar:resolution_azimuth' -PIXEL_SPACING_RANGE: str = 'sar:pixel_spacing_range' -PIXEL_SPACING_AZIMUTH: str = 'sar:pixel_spacing_azimuth' -LOOKS_RANGE: str = 'sar:looks_range' -LOOKS_AZIMUTH: str = 'sar:looks_azimuth' -LOOKS_EQUIVALENT_NUMBER: str = 'sar:looks_equivalent_number' -OBSERVATION_DIRECTION: str = 'sar:observation_direction' +CENTER_FREQUENCY: str = "sar:center_frequency" +RESOLUTION_RANGE: str = "sar:resolution_range" +RESOLUTION_AZIMUTH: str = "sar:resolution_azimuth" +PIXEL_SPACING_RANGE: str = "sar:pixel_spacing_range" +PIXEL_SPACING_AZIMUTH: str = "sar:pixel_spacing_azimuth" +LOOKS_RANGE: str = "sar:looks_range" +LOOKS_AZIMUTH: str = "sar:looks_azimuth" +LOOKS_EQUIVALENT_NUMBER: str = "sar:looks_equivalent_number" +OBSERVATION_DIRECTION: str = "sar:observation_direction" class FrequencyBand(str, enum.Enum): - P = 'P' - L = 'L' - S = 'S' - C = 'C' - X = 'X' - KU = 'Ku' - K = 'K' - KA = 'Ka' + P = "P" + L = "L" + S = "S" + C = "C" + X = "X" + KU = "Ku" + K = "K" + KA = "Ka" class Polarization(enum.Enum): - HH = 'HH' - VV = 'VV' - HV = 'HV' - VH = 'VH' + HH = "HH" + VV = "VV" + HV = "HV" + VH = "VH" class ObservationDirection(enum.Enum): - LEFT = 'left' - RIGHT = 'right' + LEFT = "left" + RIGHT = "right" -class SarExtension(Generic[T], ProjectionExtension[T], ExtensionManagementMixin[ps.Item]): +class SarExtension( + Generic[T], ProjectionExtension[T], ExtensionManagementMixin[ps.Item] +): """SarItemExt extends Item to add sar properties to a STAC Item. Args: @@ -71,20 +73,23 @@ class SarExtension(Generic[T], ProjectionExtension[T], ExtensionManagementMixin[ Using SarItemExt to directly wrap an item will add the 'sar' extension ID to the item's stac_extensions. """ - def apply(self, - instrument_mode: str, - frequency_band: FrequencyBand, - polarizations: List[Polarization], - product_type: str, - center_frequency: Optional[float] = None, - resolution_range: Optional[float] = None, - resolution_azimuth: Optional[float] = None, - pixel_spacing_range: Optional[float] = None, - pixel_spacing_azimuth: Optional[float] = None, - looks_range: Optional[int] = None, - looks_azimuth: Optional[int] = None, - looks_equivalent_number: Optional[float] = None, - observation_direction: Optional[ObservationDirection] = None) -> None: + + def apply( + self, + instrument_mode: str, + frequency_band: FrequencyBand, + polarizations: List[Polarization], + product_type: str, + center_frequency: Optional[float] = None, + resolution_range: Optional[float] = None, + resolution_azimuth: Optional[float] = None, + pixel_spacing_range: Optional[float] = None, + pixel_spacing_azimuth: Optional[float] = None, + looks_range: Optional[int] = None, + looks_azimuth: Optional[int] = None, + looks_equivalent_number: Optional[float] = None, + observation_direction: Optional[ObservationDirection] = None, + ) -> None: """Applies sar extension properties to the extended Item. Args: @@ -145,7 +150,9 @@ def instrument_mode(self) -> str: Returns: str """ - return get_required(self._get_property(INSTRUMENT_MODE, str), self, INSTRUMENT_MODE) + return get_required( + self._get_property(INSTRUMENT_MODE, str), self, INSTRUMENT_MODE + ) @instrument_mode.setter def instrument_mode(self, v: str) -> None: @@ -159,8 +166,12 @@ def frequency_band(self) -> FrequencyBand: FrequencyBand """ return get_required( - map_opt(lambda x: FrequencyBand(x), self._get_property(FREQUENCY_BAND, str)), self, - FREQUENCY_BAND) + map_opt( + lambda x: FrequencyBand(x), self._get_property(FREQUENCY_BAND, str) + ), + self, + FREQUENCY_BAND, + ) @frequency_band.setter def frequency_band(self, v: FrequencyBand) -> None: @@ -174,8 +185,13 @@ def polarizations(self) -> List[Polarization]: List[Polarization] """ return get_required( - map_opt(lambda values: [Polarization(v) for v in values], - self._get_property(POLARIZATIONS, List[str])), self, POLARIZATIONS) + map_opt( + lambda values: [Polarization(v) for v in values], + self._get_property(POLARIZATIONS, List[str]), + ), + self, + POLARIZATIONS, + ) @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: @@ -291,7 +307,9 @@ def ext(obj: T) -> "SarExtension[T]": elif isinstance(obj, ps.Asset): return cast(SarExtension[T], AssetSarExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class ItemSarExtension(SarExtension[ps.Item]): @@ -300,7 +318,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetSarExtension(SarExtension[ps.Asset]): @@ -311,30 +329,41 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class SarExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI - prev_extension_ids: Set[str] = set(['sar']) + prev_extension_ids: Set[str] = set(["sar"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - if version < '0.9': + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: + if version < "0.9": # Some sar fields became common_metadata - if 'sar:platform' in obj['properties'] and 'platform' not in obj['properties']: - obj['properties']['platform'] = obj['properties']['sar:platform'] - del obj['properties']['sar:platform'] - - if 'sar:instrument' in obj['properties'] and 'instruments' not in obj['properties']: - obj['properties']['instruments'] = [obj['properties']['sar:instrument']] - del obj['properties']['sar:instrument'] - - if ('sar:constellation' in obj['properties'] - and 'constellation' not in obj['properties']): - obj['properties']['constellation'] = obj['properties']['sar:constellation'] - del obj['properties']['sar:constellation'] + if ( + "sar:platform" in obj["properties"] + and "platform" not in obj["properties"] + ): + obj["properties"]["platform"] = obj["properties"]["sar:platform"] + del obj["properties"]["sar:platform"] + + if ( + "sar:instrument" in obj["properties"] + and "instruments" not in obj["properties"] + ): + obj["properties"]["instruments"] = [obj["properties"]["sar:instrument"]] + del obj["properties"]["sar:instrument"] + + if ( + "sar:constellation" in obj["properties"] + and "constellation" not in obj["properties"] + ): + obj["properties"]["constellation"] = obj["properties"][ + "sar:constellation" + ] + del obj["properties"]["sar:constellation"] super().migrate(obj, version, info) diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 4aff5660c..759a78b9b 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -8,21 +8,25 @@ from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.utils import map_opt -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.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' +ORBIT_STATE: str = "sat:orbit_state" +RELATIVE_ORBIT: str = "sat:relative_orbit" class OrbitState(enum.Enum): - ASCENDING = 'ascending' - DESCENDING = 'descending' - GEOSTATIONARY = 'geostationary' + ASCENDING = "ascending" + DESCENDING = "descending" + GEOSTATIONARY = "geostationary" class SatExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): @@ -38,9 +42,12 @@ class SatExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps. Using SatItemExt to directly wrap an item will add the 'sat' extension ID to the item's stac_extensions. """ - def apply(self, - orbit_state: Optional[OrbitState] = None, - relative_orbit: Optional[int] = None) -> None: + + def apply( + self, + orbit_state: Optional[OrbitState] = None, + relative_orbit: Optional[int] = None, + ) -> None: """Applies ext extension properties to the extended Item. Must specify at least one of orbit_state or relative_orbit in order @@ -93,7 +100,9 @@ def ext(obj: T) -> "SatExtension[T]": elif isinstance(obj, ps.Asset): return cast(SatExtension[T], AssetSatExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class ItemSatExtension(SatExtension[ps.Item]): @@ -102,7 +111,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetSatExtension(SatExtension[ps.Asset]): @@ -113,12 +122,12 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class SatExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['sat']) + prev_extension_ids: Set[str] = set(["sat"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 649ee8a9d..5813203d3 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -12,24 +12,28 @@ from urllib import parse import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar('T', ps.Collection, ps.Item) +T = TypeVar("T", ps.Collection, ps.Item) SCHEMA_URI = "https://stac-extensions.github.io/scientific/v1.0.0/schema.json" # STAC fields strings. -PREFIX: str = 'sci:' -DOI: str = PREFIX + 'doi' -CITATION: str = PREFIX + 'citation' -PUBLICATIONS: str = PREFIX + 'publications' +PREFIX: str = "sci:" +DOI: str = PREFIX + "doi" +CITATION: str = PREFIX + "citation" +PUBLICATIONS: str = PREFIX + "publications" # Link type. -CITE_AS: str = 'cite-as' +CITE_AS: str = "cite-as" -DOI_URL_BASE = 'https://doi.org/' +DOI_URL_BASE = "https://doi.org/" def doi_to_url(doi: str) -> str: @@ -38,6 +42,7 @@ def doi_to_url(doi: str) -> str: class Publication: """Helper for Publication entries.""" + def __init__(self, doi: str, citation: str) -> None: self.doi = doi self.citation = citation @@ -49,14 +54,14 @@ def __eq__(self, other: Any) -> bool: return self.doi == other.doi and self.citation == other.citation def __repr__(self) -> str: - return f'' + return f"" def to_dict(self) -> Dict[str, str]: - return copy.deepcopy({'doi': self.doi, 'citation': self.citation}) + return copy.deepcopy({"doi": self.doi, "citation": self.citation}) @staticmethod def from_dict(d: Dict[str, str]) -> "Publication": - return Publication(d['doi'], d['citation']) + return Publication(d["doi"], d["citation"]) def get_link(self) -> ps.Link: return ps.Link(CITE_AS, doi_to_url(self.doi)) @@ -72,16 +77,22 @@ def remove_link(links: List[ps.Link], doi: str) -> None: break -class ScientificExtension(Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): +class ScientificExtension( + Generic[T], + PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]], +): """ScientificItemExt extends Item to add citations and DOIs to a STAC Item.""" + def __init__(self, obj: ps.STACObject) -> None: self.obj = obj - def apply(self, - doi: Optional[str] = None, - citation: Optional[str] = None, - publications: Optional[List[Publication]] = None) -> None: + def apply( + self, + doi: Optional[str] = None, + citation: Optional[str] = None, + publications: Optional[List[Publication]] = None, + ) -> None: """Applies scientific extension properties to the extended Item. Args: @@ -135,12 +146,16 @@ def publications(self) -> Optional[List[Publication]]: Returns: List of Publication instances. """ - return map_opt(lambda pubs: [Publication.from_dict(pub) for pub in pubs], - self._get_property(PUBLICATIONS, List[Dict[str, Any]])) + return map_opt( + lambda pubs: [Publication.from_dict(pub) for pub in pubs], + self._get_property(PUBLICATIONS, List[Dict[str, Any]]), + ) @publications.setter def publications(self, v: Optional[List[Publication]]) -> None: - self._set_property(PUBLICATIONS, map_opt(lambda pubs: [pub.to_dict() for pub in pubs], v)) + self._set_property( + PUBLICATIONS, map_opt(lambda pubs: [pub.to_dict() for pub in pubs], v) + ) if v is not None: for pub in v: self.obj.add_link(pub.get_link()) @@ -183,7 +198,9 @@ def ext(obj: T) -> "ScientificExtension[T]": if isinstance(obj, ps.Item): return cast(ScientificExtension[T], ItemScientificExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class CollectionScientificExtension(ScientificExtension[ps.Collection]): @@ -194,7 +211,7 @@ def __init__(self, collection: ps.Collection): super().__init__(self.collection) def __repr__(self) -> str: - return ''.format(self.collection.id) + return "".format(self.collection.id) class ItemScientificExtension(ScientificExtension[ps.Item]): @@ -205,14 +222,15 @@ def __init__(self, item: ps.Item): super().__init__(self.item) def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class ScientificExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['scientific']) + prev_extension_ids: Set[str] = set(["scientific"]) stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + ) SCIENTIFIC_EXTENSION_HOOKS = ScientificExtensionHooks() diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index e372868ce..4536bdaae 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -3,10 +3,14 @@ from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.utils import datetime_to_str, map_opt, str_to_datetime -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/timestamps/v1.0.0/schema.json" @@ -15,14 +19,19 @@ UNPUBLISHED_PROP = "unpublished" -class TimestampsExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class TimestampsExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] +): """TimestampsItemExt is the extension of an Item in that allows to specify additional timestamps for assets and metadata. """ - def apply(self, - published: Optional[Datetime] = None, - expires: Optional[Datetime] = None, - unpublished: Optional[Datetime] = None) -> None: + + def apply( + self, + published: Optional[Datetime] = None, + expires: Optional[Datetime] = None, + unpublished: Optional[Datetime] = None, + ) -> None: """Applies timestamps extension properties to the extended Item. Args: @@ -110,7 +119,9 @@ def ext(obj: T) -> "TimestampsExtension[T]": elif isinstance(obj, ps.Asset): return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class ItemTimestampsExtension(TimestampsExtension[ps.Item]): @@ -119,7 +130,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetTimestampsExtension(TimestampsExtension[ps.Asset]): @@ -130,12 +141,12 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class TimestampsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['timestamps']) + prev_extension_ids: Set[str] = set(["timestamps"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index c387a1353..995dd53ec 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -9,29 +9,36 @@ from typing import Generic, List, Optional, Set, TypeVar, Union, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) from pystac.extensions.hooks import ExtensionHooks -T = TypeVar('T', ps.Collection, ps.Item) +T = TypeVar("T", ps.Collection, ps.Item) SCHEMA_URI = "https://stac-extensions.github.io/version/v1.0.0/schema.json" # STAC fields - These are unusual for an extension in that they do not have # a prefix. e.g. nothing like "ver:" -VERSION: str = 'version' -DEPRECATED: str = 'deprecated' +VERSION: str = "version" +DEPRECATED: str = "deprecated" # Link "rel" attribute values. -LATEST: str = 'latest-version' -PREDECESSOR: str = 'predecessor-version' -SUCCESSOR: str = 'successor-version' +LATEST: str = "latest-version" +PREDECESSOR: str = "predecessor-version" +SUCCESSOR: str = "successor-version" # Media type for links. -MEDIA_TYPE: str = 'application/json' +MEDIA_TYPE: str = "application/json" -class VersionExtension(Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]]): +class VersionExtension( + Generic[T], + PropertiesExtension, + ExtensionManagementMixin[Union[ps.Collection, ps.Item]], +): """VersionItemExt extends Item to add version and deprecated properties along with links to the latest, predecessor, and successor Items. @@ -45,17 +52,20 @@ class VersionExtension(Generic[T], PropertiesExtension, Using VersionItemExt to directly wrap an item will add the 'version' extension ID to the item's stac_extensions. """ + obj: ps.STACObject def __init__(self, obj: ps.STACObject) -> None: self.obj = obj - def apply(self, - version: str, - deprecated: Optional[bool] = None, - latest: Optional[T] = None, - predecessor: Optional[T] = None, - successor: Optional[T] = None) -> None: + def apply( + self, + version: str, + deprecated: Optional[bool] = None, + latest: Optional[T] = None, + predecessor: Optional[T] = None, + successor: Optional[T] = None, + ) -> None: """Applies version extension properties to the extended Item. Args: @@ -102,7 +112,9 @@ def deprecated(self, v: Optional[bool]) -> None: @property def latest(self) -> Optional[T]: """Get or sets the most recent version.""" - return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(LATEST)), None)) + return map_opt( + lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(LATEST)), None) + ) @latest.setter def latest(self, item: Optional[T]) -> None: @@ -113,8 +125,10 @@ def latest(self, item: Optional[T]) -> None: @property def predecessor(self) -> Optional[T]: """Get or sets the previous item.""" - return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(PREDECESSOR)), - None)) + return map_opt( + lambda x: cast(T, x), + next(iter(self.obj.get_stac_objects(PREDECESSOR)), None), + ) @predecessor.setter def predecessor(self, item: Optional[T]) -> None: @@ -125,7 +139,9 @@ def predecessor(self, item: Optional[T]) -> None: @property def successor(self) -> Optional[T]: """Get or sets the next item.""" - return map_opt(lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(SUCCESSOR)), None)) + return map_opt( + lambda x: cast(T, x), next(iter(self.obj.get_stac_objects(SUCCESSOR)), None) + ) @successor.setter def successor(self, item: Optional[T]) -> None: @@ -144,7 +160,9 @@ def ext(obj: T) -> "VersionExtension[T]": if isinstance(obj, ps.Item): return cast(VersionExtension[T], ItemVersionExtension(obj)) else: - raise ExtensionException(f"File extension does not apply to type {type(obj)}") + raise ExtensionException( + f"File extension does not apply to type {type(obj)}" + ) class CollectionVersionExtension(VersionExtension[ps.Collection]): @@ -155,7 +173,7 @@ def __init__(self, collection: ps.Collection): super().__init__(self.collection) def __repr__(self) -> str: - return ''.format(self.collection.id) + return "".format(self.collection.id) class ItemVersionExtension(VersionExtension[ps.Item]): @@ -166,14 +184,15 @@ def __init__(self, item: ps.Item): super().__init__(self.item) def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class VersionExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI - prev_extension_ids: Set[str] = set(['version']) + prev_extension_ids: Set[str] = set(["version"]) stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + ) def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: if isinstance(so, ps.Collection) or isinstance(so, ps.Item): diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index b00d40e92..c51dd16d6 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -2,17 +2,21 @@ from typing import Generic, Optional, Set, TypeVar, cast import pystac as ps -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.base import ( + ExtensionException, + ExtensionManagementMixin, + PropertiesExtension, +) -T = TypeVar('T', ps.Item, ps.Asset) +T = TypeVar("T", ps.Item, ps.Asset) SCHEMA_URI = "https://stac-extensions.github.io/view/v1.0.0/schema.json" -OFF_NADIR_PROP = 'view:off_nadir' -INCIDENCE_ANGLE_PROP = 'view:incidence_angle' -AZIMUTH_PROP = 'view:azimuth' -SUN_AZIMUTH_PROP = 'view:sun_azimuth' -SUN_ELEVATION_PROP = 'view:sun_elevation' +OFF_NADIR_PROP = "view:off_nadir" +INCIDENCE_ANGLE_PROP = "view:incidence_angle" +AZIMUTH_PROP = "view:azimuth" +SUN_AZIMUTH_PROP = "view:sun_azimuth" +SUN_ELEVATION_PROP = "view:sun_elevation" class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): @@ -31,12 +35,15 @@ class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps Using ViewItemExt to directly wrap an item will add the 'view' extension ID to the item's stac_extensions. """ - def apply(self, - off_nadir: Optional[float] = None, - incidence_angle: Optional[float] = None, - azimuth: Optional[float] = None, - sun_azimuth: Optional[float] = None, - sun_elevation: Optional[float] = None) -> None: + + def apply( + self, + off_nadir: Optional[float] = None, + incidence_angle: Optional[float] = None, + azimuth: Optional[float] = None, + sun_azimuth: Optional[float] = None, + sun_elevation: Optional[float] = None, + ) -> None: """Applies View Geometry extension properties to the extended Item. Args: @@ -142,7 +149,9 @@ def ext(obj: T) -> "ViewExtension[T]": elif isinstance(obj, ps.Asset): return cast(ViewExtension[T], AssetViewExtension(obj)) else: - raise ExtensionException(f"View extension does not apply to type {type(obj)}") + raise ExtensionException( + f"View extension does not apply to type {type(obj)}" + ) class ItemViewExtension(ViewExtension[ps.Item]): @@ -151,7 +160,7 @@ def __init__(self, item: ps.Item): self.properties = item.properties def __repr__(self) -> str: - return ''.format(self.item.id) + return "".format(self.item.id) class AssetViewExtension(ViewExtension[ps.Asset]): @@ -162,12 +171,12 @@ def __init__(self, asset: ps.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return ''.format(self.asset_href) + return "".format(self.asset_href) class ViewExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI - prev_extension_ids: Set[str] = set(['view']) + prev_extension_ids: Set[str] = set(["view"]) stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) diff --git a/pystac/item.py b/pystac/item.py index 6d74957e0..034d5299b 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -6,12 +6,17 @@ import dateutil.parser import pystac as ps -from pystac import (STACError, STACObjectType) +from pystac import STACError, STACObjectType from pystac.asset import Asset from pystac.link import Link from pystac.stac_object import STACObject -from pystac.utils import (is_absolute_href, make_absolute_href, make_relative_href, datetime_to_str, - str_to_datetime) +from pystac.utils import ( + is_absolute_href, + make_absolute_href, + make_relative_href, + datetime_to_str, + str_to_datetime, +) from pystac.collection import Collection, Provider @@ -23,6 +28,7 @@ class CommonMetadata: Args: properties (dict): Dictionary of attributes that is the Item's properties """ + def __init__(self, properties: Dict[str, Any]): self.properties = properties @@ -34,11 +40,11 @@ def title(self) -> Optional[str]: Returns: str: Human readable title describing the item """ - return self.properties.get('title') + return self.properties.get("title") @title.setter def title(self, v: Optional[str]) -> None: - self.properties['title'] = v + self.properties["title"] = v @property def description(self) -> Optional[str]: @@ -47,11 +53,11 @@ def description(self) -> Optional[str]: Returns: str: Detailed description of the item """ - return self.properties.get('description') + return self.properties.get("description") @description.setter def description(self, v: Optional[str]) -> None: - self.properties['description'] = v + self.properties["description"] = v # Date and Time Range @property @@ -76,31 +82,32 @@ def get_start_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime Returns: datetime """ - if asset is None or 'start_datetime' not in asset.properties: - start_datetime = self.properties.get('start_datetime') + if asset is None or "start_datetime" not in asset.properties: + start_datetime = self.properties.get("start_datetime") else: - start_datetime = asset.properties.get('start_datetime') + start_datetime = asset.properties.get("start_datetime") if start_datetime: start_datetime = str_to_datetime(start_datetime) return start_datetime - def set_start_datetime(self, - start_datetime: Optional[Datetime], - asset: Optional[Asset] = None) -> None: + def set_start_datetime( + self, start_datetime: Optional[Datetime], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset start_datetime. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['start_datetime'] = None if start_datetime is None else datetime_to_str( - start_datetime) + self.properties["start_datetime"] = ( + None if start_datetime is None else datetime_to_str(start_datetime) + ) else: - asset.properties[ - 'start_datetime'] = None if start_datetime is None else datetime_to_str( - start_datetime) + asset.properties["start_datetime"] = ( + None if start_datetime is None else datetime_to_str(start_datetime) + ) @property def end_datetime(self) -> Optional[Datetime]: @@ -126,30 +133,32 @@ def get_end_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime """ - if asset is None or 'end_datetime' not in asset.properties: - end_datetime = self.properties.get('end_datetime') + if asset is None or "end_datetime" not in asset.properties: + end_datetime = self.properties.get("end_datetime") else: - end_datetime = asset.properties.get('end_datetime') + end_datetime = asset.properties.get("end_datetime") if end_datetime: end_datetime = str_to_datetime(end_datetime) return end_datetime - def set_end_datetime(self, - end_datetime: Optional[Datetime], - asset: Optional[Asset] = None) -> None: + def set_end_datetime( + self, end_datetime: Optional[Datetime], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset end_datetime. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['end_datetime'] = None if end_datetime is None else datetime_to_str( - end_datetime) + self.properties["end_datetime"] = ( + None if end_datetime is None else datetime_to_str(end_datetime) + ) else: - asset.properties['end_datetime'] = None if end_datetime is None else datetime_to_str( - end_datetime) + asset.properties["end_datetime"] = ( + None if end_datetime is None else datetime_to_str(end_datetime) + ) # License @property @@ -174,21 +183,23 @@ def get_license(self, asset: Optional[Asset] = None) -> Optional[str]: Returns: str """ - if asset is None or 'license' not in asset.properties: - return self.properties.get('license') + if asset is None or "license" not in asset.properties: + return self.properties.get("license") else: - return asset.properties.get('license') + return asset.properties.get("license") - def set_license(self, license: Optional[str], asset: Optional[Asset] = None) -> None: + def set_license( + self, license: Optional[str], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset license. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['license'] = license + self.properties["license"] = license else: - asset.properties['license'] = license + asset.properties["license"] = license # Providers @property @@ -215,19 +226,19 @@ def get_providers(self, asset: Optional[Asset] = None) -> Optional[List[Provider Returns: List[Provider] """ - if asset is None or 'providers' not in asset.properties: - providers = self.properties.get('providers') + if asset is None or "providers" not in asset.properties: + providers = self.properties.get("providers") else: - providers = asset.properties.get('providers') + providers = asset.properties.get("providers") if providers is not None: providers = [Provider.from_dict(d) for d in providers] return providers - def set_providers(self, - providers: Optional[List[Provider]], - asset: Optional[Asset] = None) -> None: + def set_providers( + self, providers: Optional[List[Provider]], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset providers. If an Asset is supplied, sets the property on the Asset. @@ -235,16 +246,16 @@ def set_providers(self, """ if asset is None: if providers is None: - self.properties.pop('providers', None) + self.properties.pop("providers", None) else: providers_dicts = [d.to_dict() for d in providers] - self.properties['providers'] = providers_dicts + self.properties["providers"] = providers_dicts else: if providers is None: - asset.properties.pop('providers', None) + asset.properties.pop("providers", None) else: providers_dicts = [d.to_dict() for d in providers] - asset.properties['providers'] = providers_dicts + asset.properties["providers"] = providers_dicts # Instrument @property @@ -270,21 +281,23 @@ def get_platform(self, asset: Optional[Asset] = None) -> Optional[str]: Returns: str """ - if asset is None or 'platform' not in asset.properties: - return self.properties.get('platform') + if asset is None or "platform" not in asset.properties: + return self.properties.get("platform") else: - return asset.properties.get('platform') + return asset.properties.get("platform") - def set_platform(self, platform: Optional[str], asset: Optional[Asset] = None) -> None: + def set_platform( + self, platform: Optional[str], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset platform. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['platform'] = platform + self.properties["platform"] = platform else: - asset.properties['platform'] = platform + asset.properties["platform"] = platform @property def instruments(self) -> Optional[List[str]]: @@ -308,23 +321,23 @@ def get_instruments(self, asset: Optional[Asset] = None) -> Optional[List[str]]: Returns: Optional[List[str]] """ - if asset is None or 'instruments' not in asset.properties: - return self.properties.get('instruments') + if asset is None or "instruments" not in asset.properties: + return self.properties.get("instruments") else: - return asset.properties.get('instruments') + return asset.properties.get("instruments") - def set_instruments(self, - instruments: Optional[List[str]], - asset: Optional[Asset] = None) -> None: + def set_instruments( + self, instruments: Optional[List[str]], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset instruments. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['instruments'] = instruments + self.properties["instruments"] = instruments else: - asset.properties['instruments'] = instruments + asset.properties["instruments"] = instruments @property def constellation(self) -> Optional[str]: @@ -348,23 +361,23 @@ def get_constellation(self, asset: Optional[Asset] = None) -> Optional[str]: Returns: str """ - if asset is None or 'constellation' not in asset.properties: - return self.properties.get('constellation') + if asset is None or "constellation" not in asset.properties: + return self.properties.get("constellation") else: - return asset.properties.get('constellation') + return asset.properties.get("constellation") - def set_constellation(self, - constellation: Optional[str], - asset: Optional[Asset] = None) -> None: + def set_constellation( + self, constellation: Optional[str], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset constellation. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['constellation'] = constellation + self.properties["constellation"] = constellation else: - asset.properties['constellation'] = constellation + asset.properties["constellation"] = constellation @property def mission(self) -> Optional[str]: @@ -388,21 +401,23 @@ def get_mission(self, asset: Optional[Asset] = None) -> Optional[str]: Returns: str """ - if asset is None or 'mission' not in asset.properties: - return self.properties.get('mission') + if asset is None or "mission" not in asset.properties: + return self.properties.get("mission") else: - return asset.properties.get('mission') + return asset.properties.get("mission") - def set_mission(self, mission: Optional[str], asset: Optional[Asset] = None) -> None: + def set_mission( + self, mission: Optional[str], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset mission. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['mission'] = mission + self.properties["mission"] = mission else: - asset.properties['mission'] = mission + asset.properties["mission"] = mission @property def gsd(self) -> Optional[float]: @@ -426,10 +441,10 @@ def get_gsd(self, asset: Optional[Asset] = None) -> Optional[float]: Returns: float """ - if asset is None or 'gsd' not in asset.properties: - return self.properties.get('gsd') + if asset is None or "gsd" not in asset.properties: + return self.properties.get("gsd") else: - return asset.properties.get('gsd') + return asset.properties.get("gsd") def set_gsd(self, gsd: Optional[float], asset: Optional[Asset] = None) -> None: """Set an Item or an Asset gsd. @@ -438,9 +453,9 @@ def set_gsd(self, gsd: Optional[float], asset: Optional[Asset] = None) -> None: Otherwise sets the Item's value. """ if asset is None: - self.properties['gsd'] = gsd + self.properties["gsd"] = gsd else: - asset.properties['gsd'] = gsd + asset.properties["gsd"] = gsd # Metadata @property @@ -474,26 +489,32 @@ def get_created(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime """ - if asset is None or 'created' not in asset.properties: - created = self.properties.get('created') + if asset is None or "created" not in asset.properties: + created = self.properties.get("created") else: - created = asset.properties.get('created') + created = asset.properties.get("created") if created: created = str_to_datetime(created) return created - def set_created(self, created: Optional[Datetime], asset: Optional[Asset] = None) -> None: + def set_created( + self, created: Optional[Datetime], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset created time. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['created'] = None if created is None else datetime_to_str(created) + self.properties["created"] = ( + None if created is None else datetime_to_str(created) + ) else: - asset.properties['created'] = None if created is None else datetime_to_str(created) + asset.properties["created"] = ( + None if created is None else datetime_to_str(created) + ) @property def updated(self) -> Optional[Datetime]: @@ -535,26 +556,32 @@ def get_updated(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime """ - if asset is None or 'updated' not in asset.properties: - updated = self.properties.get('updated') + if asset is None or "updated" not in asset.properties: + updated = self.properties.get("updated") else: - updated = asset.properties.get('updated') + updated = asset.properties.get("updated") if updated: updated = str_to_datetime(updated) return updated - def set_updated(self, updated: Optional[Datetime], asset: Optional[Asset] = None) -> None: + def set_updated( + self, updated: Optional[Datetime], asset: Optional[Asset] = None + ) -> None: """Set an Item or an Asset updated time. If an Asset is supplied, sets the property on the Asset. Otherwise sets the Item's value. """ if asset is None: - self.properties['updated'] = None if updated is None else datetime_to_str(updated) + self.properties["updated"] = ( + None if updated is None else datetime_to_str(updated) + ) else: - asset.properties['updated'] = None if updated is None else datetime_to_str(updated) + asset.properties["updated"] = ( + None if updated is None else datetime_to_str(updated) + ) class Item(STACObject): @@ -606,16 +633,18 @@ class Item(STACObject): STAC_OBJECT_TYPE = STACObjectType.ITEM - def __init__(self, - id: str, - geometry: Optional[Dict[str, Any]], - bbox: Optional[List[float]], - datetime: Optional[Datetime], - properties: Dict[str, Any], - stac_extensions: Optional[List[str]] = None, - href: Optional[str] = None, - collection: Optional[Union[str, Collection]] = None, - extra_fields: Optional[Dict[str, Any]] = None): + def __init__( + self, + id: str, + geometry: Optional[Dict[str, Any]], + bbox: Optional[List[float]], + datetime: Optional[Datetime], + properties: Dict[str, Any], + stac_extensions: Optional[List[str]] = None, + href: Optional[str] = None, + collection: Optional[Union[str, Collection]] = None, + extra_fields: Optional[Dict[str, Any]] = None, + ): super().__init__(stac_extensions or []) self.id = id @@ -631,12 +660,13 @@ def __init__(self, self.datetime: Optional[Datetime] = None if datetime is None: - if 'start_datetime' not in properties or \ - 'end_datetime' not in properties: - raise STACError('Invalid Item: If datetime is None, ' - 'a start_datetime and end_datetime ' - 'must be supplied in ' - 'the properties.') + if "start_datetime" not in properties or "end_datetime" not in properties: + raise STACError( + "Invalid Item: If datetime is None, " + "a start_datetime and end_datetime " + "must be supplied in " + "the properties." + ) self.datetime = None else: self.datetime = datetime @@ -652,7 +682,7 @@ def __init__(self, self.collection_id = collection def __repr__(self) -> str: - return ''.format(self.id) + return "".format(self.id) def set_self_href(self, href: Optional[str]) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` @@ -693,10 +723,10 @@ def get_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: Returns: datetime or None """ - if asset is None or 'datetime' not in asset.properties: + if asset is None or "datetime" not in asset.properties: return self.datetime else: - asset_dt = asset.properties.get('datetime') + asset_dt = asset.properties.get("datetime") if asset_dt is None: return None else: @@ -711,7 +741,7 @@ def set_datetime(self, datetime: Datetime, asset: Optional[Asset] = None) -> Non if asset is None: self.datetime = datetime else: - asset.properties['datetime'] = datetime_to_str(datetime) + asset.properties["datetime"] = datetime_to_str(datetime) def get_assets(self) -> Dict[str, Asset]: """Get this item's assets. @@ -745,8 +775,10 @@ def make_asset_hrefs_relative(self) -> "Item": if self_href is None: self_href = self.get_self_href() if self_href is None: - raise STACError('Cannot make asset HREFs relative ' - 'if no self_href is set.') + raise STACError( + "Cannot make asset HREFs relative " + "if no self_href is set." + ) asset.href = make_relative_href(asset.href, self_href) return self @@ -766,8 +798,10 @@ def make_asset_hrefs_absolute(self) -> "Item": if self_href is None: self_href = self.get_self_href() if self_href is None: - raise STACError('Cannot make relative asset HREFs absolute ' - 'if no self_href is set.') + raise STACError( + "Cannot make relative asset HREFs absolute " + "if no self_href is set." + ) asset.href = make_absolute_href(asset.href, self_href) return self @@ -785,7 +819,7 @@ def set_collection(self, collection: Optional[Collection]) -> "Item": Returns: Item: self """ - self.remove_links('collection') + self.remove_links("collection") self.collection_id = None if collection is not None: self.add_link(Link.collection(collection)) @@ -800,7 +834,7 @@ def get_collection(self) -> Optional[Collection]: Collection or None: If this item belongs to a collection, returns a reference to the collection. Otherwise returns None. """ - collection_link = self.get_single_link('collection') + collection_link = self.get_single_link("collection") if collection_link is None: return None else: @@ -809,33 +843,33 @@ def get_collection(self) -> Optional[Collection]: def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: links = self.links if not include_self_link: - links = [x for x in links if x.rel != 'self'] + links = [x for x in links if x.rel != "self"] assets = {k: v.to_dict() for k, v in self.assets.items()} if self.datetime is not None: - self.properties['datetime'] = datetime_to_str(self.datetime) + self.properties["datetime"] = datetime_to_str(self.datetime) else: - self.properties['datetime'] = None + self.properties["datetime"] = None d: Dict[str, Any] = { - 'type': 'Feature', - 'stac_version': ps.get_stac_version(), - 'id': self.id, - 'properties': self.properties, - 'geometry': self.geometry, - 'links': [link.to_dict() for link in links], - 'assets': assets + "type": "Feature", + "stac_version": ps.get_stac_version(), + "id": self.id, + "properties": self.properties, + "geometry": self.geometry, + "links": [link.to_dict() for link in links], + "assets": assets, } if self.bbox is not None: - d['bbox'] = self.bbox + d["bbox"] = self.bbox if self.stac_extensions is not None: - d['stac_extensions'] = self.stac_extensions + d["stac_extensions"] = self.stac_extensions if self.collection_id: - d['collection'] = self.collection_id + d["collection"] = self.collection_id for key in self.extra_fields: d[key] = self.extra_fields[key] @@ -843,13 +877,15 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: return d def clone(self) -> "Item": - clone = Item(id=self.id, - geometry=deepcopy(self.geometry), - bbox=copy(self.bbox), - datetime=copy(self.datetime), - properties=deepcopy(self.properties), - stac_extensions=deepcopy(self.stac_extensions), - collection=self.collection_id) + clone = Item( + id=self.id, + geometry=deepcopy(self.geometry), + bbox=copy(self.bbox), + datetime=copy(self.datetime), + properties=deepcopy(self.properties), + stac_extensions=deepcopy(self.stac_extensions), + collection=self.collection_id, + ) for link in self.links: clone.add_link(link.clone()) @@ -859,14 +895,16 @@ def clone(self) -> "Item": return clone def _object_links(self) -> List[str]: - return ['collection'] + (ps.EXTENSION_HOOKS.get_extended_object_links(self)) + return ["collection"] + (ps.EXTENSION_HOOKS.get_extended_object_links(self)) @classmethod - def from_dict(cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, - migrate: bool = False) -> "Item": + def from_dict( + cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional[Catalog] = None, + migrate: bool = False, + ) -> "Item": if migrate: result = ps.read_dict(d, href=href, root=root) if not isinstance(result, Item): @@ -874,34 +912,36 @@ def from_dict(cls, return result d = deepcopy(d) - id = d.pop('id') - geometry = d.pop('geometry') - properties = d.pop('properties') - bbox = d.pop('bbox', None) - stac_extensions = d.get('stac_extensions') - collection_id = d.pop('collection', None) - - datetime = properties.get('datetime') + id = d.pop("id") + geometry = d.pop("geometry") + properties = d.pop("properties") + bbox = d.pop("bbox", None) + stac_extensions = d.get("stac_extensions") + collection_id = d.pop("collection", None) + + datetime = properties.get("datetime") if datetime is not None: datetime = dateutil.parser.parse(datetime) - links = d.pop('links') - assets = d.pop('assets') - - d.pop('type') - d.pop('stac_version') - - item = cls(id=id, - geometry=geometry, - bbox=bbox, - datetime=datetime, - properties=properties, - stac_extensions=stac_extensions, - collection=collection_id, - extra_fields=d) + links = d.pop("links") + assets = d.pop("assets") + + d.pop("type") + d.pop("stac_version") + + item = cls( + id=id, + geometry=geometry, + bbox=bbox, + datetime=datetime, + properties=properties, + stac_extensions=stac_extensions, + collection=collection_id, + extra_fields=d, + ) has_self_link = False for link in links: - has_self_link |= link['rel'] == 'self' + has_self_link |= link["rel"] == "self" item.add_link(Link.from_dict(link)) if not has_self_link and href is not None: @@ -923,9 +963,9 @@ def common_metadata(self) -> CommonMetadata: """ return CommonMetadata(self.properties) - def full_copy(self, - root: Optional["Catalog"] = None, - parent: Optional["Catalog"] = None) -> "Item": + def full_copy( + self, root: Optional["Catalog"] = None, parent: Optional["Catalog"] = None + ) -> "Item": return cast(Item, super().full_copy(root, parent)) @classmethod diff --git a/pystac/layout.py b/pystac/layout.py index b2ac8f750..c1b664a65 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -1,4 +1,4 @@ -from abc import (abstractmethod, ABC) +from abc import abstractmethod, ABC from collections import OrderedDict import os from string import Formatter @@ -17,6 +17,7 @@ class TemplateError(Exception): """Exception thrown when an error occurs during converting a template string into data for :class:`~pystac.layout.LayoutTemplate` """ + pass @@ -71,7 +72,7 @@ class LayoutTemplate: """ # Special template vars specific to Items - ITEM_TEMPLATE_VARS = ['date', 'year', 'month', 'day', 'collection'] + ITEM_TEMPLATE_VARS = ["date", "year", "month", "day", "collection"] def __init__(self, template: str, defaults: Dict[str, str] = None) -> None: self.template = template @@ -82,12 +83,14 @@ def __init__(self, template: str, defaults: Dict[str, str] = None) -> None: for formatter_parse_result in Formatter().parse(template): v = formatter_parse_result[1] if v is not None: - if formatter_parse_result[2] != '': - v = '{}:{}'.format(v, formatter_parse_result[2]) + if formatter_parse_result[2] != "": + v = "{}:{}".format(v, formatter_parse_result[2]) template_vars.append(v) self.template_vars = template_vars - def _get_template_value(self, stac_object: "STACObject_Type", template_var: str) -> Any: + def _get_template_value( + self, stac_object: "STACObject_Type", template_var: str + ) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: if isinstance(stac_object, ps.Item): # Datetime @@ -95,35 +98,46 @@ def _get_template_value(self, stac_object: "STACObject_Type", template_var: str) if dt is None: dt = stac_object.common_metadata.start_datetime if dt is None: - raise TemplateError('Item {} does not have a datetime or ' - 'datetime range set; cannot template {} in {}'.format( - stac_object, template_var, self.template)) + raise TemplateError( + "Item {} does not have a datetime or " + "datetime range set; cannot template {} in {}".format( + stac_object, template_var, self.template + ) + ) - if template_var == 'year': + if template_var == "year": return dt.year - if template_var == 'month': + if template_var == "month": return dt.month - if template_var == 'day': + if template_var == "day": return dt.day - if template_var == 'date': + if template_var == "date": return dt.date().isoformat() # Collection - if template_var == 'collection': + if template_var == "collection": if stac_object.collection_id is not None: return stac_object.collection_id raise TemplateError( - 'Item {} does not have a collection ID set; cannot template {} in {}'. - format(stac_object, template_var, self.template)) + "Item {} does not have a collection ID set; cannot template {} in {}".format( + stac_object, template_var, self.template + ) + ) else: - raise TemplateError('"{}" cannot be used to template non-Item {} in {}'.format( - template_var, stac_object, self.template)) + raise TemplateError( + '"{}" cannot be used to template non-Item {} in {}'.format( + template_var, stac_object, self.template + ) + ) # Allow dot-notation properties for arbitrary object values. - props = template_var.split('.') + props = template_var.split(".") prop_source: Optional[Union[ps.STACObject, Dict[str, Any]]] = None - error = TemplateError('Cannot find property {} on {} for template {}'.format( - template_var, stac_object, self.template)) + error = TemplateError( + "Cannot find property {} on {} for template {}".format( + template_var, stac_object, self.template + ) + ) try: @@ -131,12 +145,16 @@ def _get_template_value(self, stac_object: "STACObject_Type", template_var: str) prop_source = stac_object if prop_source is None and hasattr(stac_object, "properties"): - obj_props: Optional[Dict[str, Any]] = stac_object.properties # type:ignore + obj_props: Optional[ + Dict[str, Any] + ] = stac_object.properties # type:ignore if obj_props is not None and props[0] in obj_props: prop_source = obj_props if prop_source is None and hasattr(stac_object, "extra_fields"): - extra_fields: Optional[Dict[str, Any]] = stac_object.extra_fields # type:ignore + extra_fields: Optional[ + Dict[str, Any] + ] = stac_object.extra_fields # type:ignore if extra_fields is not None and props[0] in extra_fields: prop_source = extra_fields @@ -144,7 +162,7 @@ def _get_template_value(self, stac_object: "STACObject_Type", template_var: str) raise error v: Any = prop_source - for prop in template_var.split('.'): + for prop in template_var.split("."): if type(v) is dict: if prop not in v: raise error @@ -180,8 +198,9 @@ def get_template_values(self, stac_object: "STACObject_Type") -> Dict[str, Any]: derived from the stac object and there is no default, this error will be raised. """ - return OrderedDict([(k, self._get_template_value(stac_object, k)) - for k in self.template_vars]) + return OrderedDict( + [(k, self._get_template_value(stac_object, k)) for k in self.template_vars] + ) def substitute(self, stac_object: "STACObject_Type") -> str: """Substitutes the values derived from @@ -206,16 +225,16 @@ def substitute(self, stac_object: "STACObject_Type") -> str: s = self.template for key, value in parts.items(): - s = s.replace('${' + '{}'.format(key) + '}', '{}'.format(value)) + s = s.replace("${" + "{}".format(key) + "}", "{}".format(value)) return s class HrefLayoutStrategy(ABC): """Base class for HREF Layout strategies.""" - def get_href(self, - stac_object: "STACObject_Type", - parent_dir: str, - is_root: bool = False) -> str: + + def get_href( + self, stac_object: "STACObject_Type", parent_dir: str, is_root: bool = False + ) -> str: if isinstance(stac_object, ps.Item): return self.get_item_href(stac_object, parent_dir) elif isinstance(stac_object, ps.Collection): @@ -223,14 +242,18 @@ def get_href(self, elif isinstance(stac_object, ps.Catalog): return self.get_catalog_href(stac_object, parent_dir, is_root) else: - raise ps.STACError('Unknown STAC object type {}'.format(stac_object)) + raise ps.STACError("Unknown STAC object type {}".format(stac_object)) @abstractmethod - def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: + def get_catalog_href( + self, cat: "Catalog_Type", parent_dir: str, is_root: bool + ) -> str: pass @abstractmethod - def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: + def get_collection_href( + self, col: "Collection_Type", parent_dir: str, is_root: bool + ) -> str: pass @abstractmethod @@ -258,11 +281,14 @@ class CustomLayoutStrategy(HrefLayoutStrategy): use if a function is not provided for a stac object type. Defaults to :class:`~pystac.layout.BestPracticesLayoutStrategy` """ - def __init__(self, - catalog_func: Optional[Callable[["Catalog_Type", str, bool], str]] = None, - collection_func: Optional[Callable[["Collection_Type", str, bool], str]] = None, - item_func: Optional[Callable[["Item_Type", str], str]] = None, - fallback_strategy: Optional[HrefLayoutStrategy] = None): + + def __init__( + self, + catalog_func: Optional[Callable[["Catalog_Type", str, bool], str]] = None, + collection_func: Optional[Callable[["Collection_Type", str, bool], str]] = None, + item_func: Optional[Callable[["Item_Type", str], str]] = None, + fallback_strategy: Optional[HrefLayoutStrategy] = None, + ): self.item_func = item_func self.collection_func = collection_func self.catalog_func = catalog_func @@ -270,14 +296,18 @@ def __init__(self, fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: + def get_catalog_href( + self, cat: "Catalog_Type", parent_dir: str, is_root: bool + ) -> str: if self.catalog_func is not None: result = self.catalog_func(cat, parent_dir, is_root) if result is not None: return result return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) - def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: + def get_collection_href( + self, col: "Collection_Type", parent_dir: str, is_root: bool + ) -> str: if self.collection_func is not None: result = self.collection_func(col, parent_dir, is_root) if result is not None: @@ -316,37 +346,50 @@ class TemplateLayoutStrategy(HrefLayoutStrategy): use if a template is not provided. Defaults to :class:`~pystac.layout.BestPracticesLayoutStrategy` """ - def __init__(self, - catalog_template: Optional[str] = None, - collection_template: Optional[str] = None, - item_template: Optional[str] = None, - fallback_strategy: Optional[HrefLayoutStrategy] = None): - self.catalog_template = LayoutTemplate( - catalog_template) if catalog_template is not None else None - self.collection_template = LayoutTemplate( - collection_template) if collection_template is not None else None - self.item_template = LayoutTemplate(item_template) if item_template is not None else None + + def __init__( + self, + catalog_template: Optional[str] = None, + collection_template: Optional[str] = None, + item_template: Optional[str] = None, + fallback_strategy: Optional[HrefLayoutStrategy] = None, + ): + self.catalog_template = ( + LayoutTemplate(catalog_template) if catalog_template is not None else None + ) + self.collection_template = ( + LayoutTemplate(collection_template) + if collection_template is not None + else None + ) + self.item_template = ( + LayoutTemplate(item_template) if item_template is not None else None + ) if fallback_strategy is None: fallback_strategy = BestPracticesLayoutStrategy() self.fallback_strategy: HrefLayoutStrategy = fallback_strategy - def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: + def get_catalog_href( + self, cat: "Catalog_Type", parent_dir: str, is_root: bool + ) -> str: if is_root or self.catalog_template is None: return self.fallback_strategy.get_catalog_href(cat, parent_dir, is_root) else: template_path = self.catalog_template.substitute(cat) - if not template_path.endswith('.json'): + if not template_path.endswith(".json"): template_path = os.path.join(template_path, cat.DEFAULT_FILE_NAME) return os.path.join(parent_dir, template_path) - def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: + def get_collection_href( + self, col: "Collection_Type", parent_dir: str, is_root: bool + ) -> str: if is_root or self.collection_template is None: return self.fallback_strategy.get_collection_href(col, parent_dir, is_root) else: template_path = self.collection_template.substitute(col) - if not template_path.endswith('.json'): + if not template_path.endswith(".json"): template_path = os.path.join(template_path, col.DEFAULT_FILE_NAME) return os.path.join(parent_dir, template_path) @@ -356,8 +399,8 @@ def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: return self.fallback_strategy.get_item_href(item, parent_dir) else: template_path = self.item_template.substitute(item) - if not template_path.endswith('.json'): - template_path = os.path.join(template_path, '{}.json'.format(item.id)) + if not template_path.endswith(".json"): + template_path = os.path.join(template_path, "{}.json".format(item.id)) return os.path.join(parent_dir, template_path) @@ -376,23 +419,28 @@ class BestPracticesLayoutStrategy(HrefLayoutStrategy): All paths are appended to the parent directory. """ - def get_catalog_href(self, cat: "Catalog_Type", parent_dir: str, is_root: bool) -> str: + + def get_catalog_href( + self, cat: "Catalog_Type", parent_dir: str, is_root: bool + ) -> str: if is_root: cat_root = parent_dir else: - cat_root = os.path.join(parent_dir, '{}'.format(cat.id)) + cat_root = os.path.join(parent_dir, "{}".format(cat.id)) return os.path.join(cat_root, cat.DEFAULT_FILE_NAME) - def get_collection_href(self, col: "Collection_Type", parent_dir: str, is_root: bool) -> str: + def get_collection_href( + self, col: "Collection_Type", parent_dir: str, is_root: bool + ) -> str: if is_root: col_root = parent_dir else: - col_root = os.path.join(parent_dir, '{}'.format(col.id)) + col_root = os.path.join(parent_dir, "{}".format(col.id)) return os.path.join(col_root, col.DEFAULT_FILE_NAME) def get_item_href(self, item: "Item_Type", parent_dir: str) -> str: - item_root = os.path.join(parent_dir, '{}'.format(item.id)) + item_root = os.path.join(parent_dir, "{}".format(item.id)) - return os.path.join(item_root, '{}.json'.format(item.id)) + return os.path.join(item_root, "{}.json".format(item.id)) diff --git a/pystac/link.py b/pystac/link.py index 281159baa..75ba7a62e 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -2,7 +2,7 @@ from typing import Any, Dict, Optional, TYPE_CHECKING, Union, cast import pystac as ps -from pystac.utils import (make_absolute_href, make_relative_href, is_absolute_href) +from pystac.utils import make_absolute_href, make_relative_href, is_absolute_href if TYPE_CHECKING: from pystac.stac_object import STACObject as STACObject_Type @@ -10,7 +10,7 @@ from pystac.catalog import Catalog as Catalog_Type from pystac.collection import Collection as Collection_Type -HIERARCHICAL_LINKS = ['root', 'child', 'parent', 'collection', 'item', 'items'] +HIERARCHICAL_LINKS = ["root", "child", "parent", "collection", "item", "items"] class Link: @@ -55,12 +55,15 @@ class Link: to resolve objects, and will create absolute HREFs from relative HREFs against the owner's self HREF. """ - def __init__(self, - rel: str, - target: Union[str, "STACObject_Type"], - media_type: Optional[str] = None, - title: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None) -> None: + + def __init__( + self, + rel: str, + target: Union[str, "STACObject_Type"], + media_type: Optional[str] = None, + title: Optional[str] = None, + properties: Optional[Dict[str, Any]] = None, + ) -> None: self.rel = rel self.target: Union[str, "STACObject_Type"] = target # An object or an href self.media_type = media_type @@ -86,7 +89,7 @@ def href(self) -> str: """ result = self.get_href() if result is None: - raise ValueError(f'{self} does not have an HREF set.') + raise ValueError(f"{self} does not have an HREF set.") return result def get_href(self) -> Optional[str]: @@ -106,8 +109,10 @@ def get_href(self) -> Optional[str]: if href and is_absolute_href(href) and self.owner and self.owner.get_root(): root = self.owner.get_root() - rel_links = HIERARCHICAL_LINKS + \ - ps.EXTENSION_HOOKS.get_extended_object_links(self.owner) + rel_links = ( + HIERARCHICAL_LINKS + + ps.EXTENSION_HOOKS.get_extended_object_links(self.owner) + ) # if a hierarchical link with an owner and root, and relative catalog if root and root.is_relative() and self.rel in rel_links: owner_href = self.owner.get_self_href() @@ -125,7 +130,7 @@ def absolute_href(self) -> str: """ result = self.get_absolute_href() if result is None: - raise ValueError(f'{self} does not have an HREF set.') + raise ValueError(f"{self} does not have an HREF set.") return result def get_absolute_href(self) -> Optional[str]: @@ -147,7 +152,7 @@ def get_absolute_href(self) -> Optional[str]: return href def __repr__(self) -> str: - return ''.format(self.rel, self.target) + return "".format(self.rel, self.target) def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": """Resolves a STAC object from the HREF of this link, if the link is not @@ -164,13 +169,17 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": # If it's a relative link, base it off the parent. if not is_absolute_href(target_href): if self.owner is None: - raise ps.STACError('Relative path {} encountered ' - 'without owner or start_href.'.format(target_href)) + raise ps.STACError( + "Relative path {} encountered " + "without owner or start_href.".format(target_href) + ) start_href = self.owner.get_self_href() if start_href is None: - raise ps.STACError('Relative path {} encountered ' - 'without owner "self" link set.'.format(target_href)) + raise ps.STACError( + "Relative path {} encountered " + 'without owner "self" link set.'.format(target_href) + ) target_href = make_absolute_href(target_href, start_href) obj = None @@ -200,7 +209,11 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": self.target = obj - if self.owner and self.rel in ['child', 'item'] and isinstance(self.owner, ps.Catalog): + if ( + self.owner + and self.rel in ["child", "item"] + and isinstance(self.owner, ps.Catalog) + ): self.target.set_parent(self.owner) return self @@ -220,15 +233,15 @@ def to_dict(self) -> Dict[str, Any]: dict: A serialization of the Link that can be written out as JSON. """ - d: Dict[str, Any] = {'rel': self.rel} + d: Dict[str, Any] = {"rel": self.rel} - d['href'] = self.get_href() + d["href"] = self.get_href() if self.media_type is not None: - d['type'] = self.media_type + d["type"] = self.media_type if self.title is not None: - d['title'] = self.title + d["title"] = self.title if self.properties: for k, v in self.properties.items(): @@ -246,7 +259,12 @@ def clone(self) -> "Link": Link: The cloned link. """ - return Link(rel=self.rel, target=self.target, media_type=self.media_type, title=self.title) + return Link( + rel=self.rel, + target=self.target, + media_type=self.media_type, + title=self.title, + ) @staticmethod def from_dict(d: Dict[str, Any]) -> "Link": @@ -259,43 +277,49 @@ def from_dict(d: Dict[str, Any]) -> "Link": Link: Link instance constructed from the dict. """ d = copy(d) - rel = d.pop('rel') - href = d.pop('href') - media_type = d.pop('type', None) - title = d.pop('title', None) + rel = d.pop("rel") + href = d.pop("href") + media_type = d.pop("type", None) + title = d.pop("title", None) properties = None if any(d): properties = d - return Link(rel=rel, target=href, media_type=media_type, title=title, properties=properties) + return Link( + rel=rel, + target=href, + media_type=media_type, + title=title, + properties=properties, + ) @staticmethod def root(c: "Catalog_Type") -> "Link": """Creates a link to a root Catalog or Collection.""" - return Link('root', c, media_type='application/json') + return Link("root", c, media_type="application/json") @staticmethod def parent(c: "Catalog_Type") -> "Link": """Creates a link to a parent Catalog or Collection.""" - return Link('parent', c, media_type='application/json') + return Link("parent", c, media_type="application/json") @staticmethod def collection(c: "Collection_Type") -> "Link": """Creates a link to an item's Collection.""" - return Link('collection', c, media_type='application/json') + return Link("collection", c, media_type="application/json") @staticmethod def self_href(href: str) -> "Link": """Creates a self link to a file's location.""" - return Link('self', href, media_type='application/json') + return Link("self", href, media_type="application/json") @staticmethod def child(c: "Catalog_Type", title: Optional[str] = None) -> "Link": """Creates a link to a child Catalog or Collection.""" - return Link('child', c, title=title, media_type='application/json') + return Link("child", c, title=title, media_type="application/json") @staticmethod def item(item: "Item_Type", title: Optional[str] = None) -> "Link": """Creates a link to an Item.""" - return Link('item', item, title=title, media_type='application/json') + return Link("item", item, title=title, media_type="application/json") diff --git a/pystac/media_type.py b/pystac/media_type.py index d9e2d50e0..b74c5f51d 100644 --- a/pystac/media_type.py +++ b/pystac/media_type.py @@ -2,21 +2,21 @@ class MediaType(str, Enum): - """A list of common media types that can be used in STAC Asset and Link metadata. - """ + """A list of common media types that can be used in STAC Asset and Link metadata.""" + def __str__(self) -> str: return str(self.value) - COG = 'image/tiff; application=geotiff; profile=cloud-optimized' - GEOJSON = 'application/geo+json' - GEOPACKAGE = 'application/geopackage+sqlite3' - GEOTIFF = 'image/tiff; application=geotiff' - HDF = 'application/x-hdf' # Hierarchical Data Format versions 4 and earlier. - HDF5 = 'application/x-hdf5' # Hierarchical Data Format version 5 - JPEG = 'image/jpeg' - JPEG2000 = 'image/jp2' - JSON = 'application/json' - PNG = 'image/png' - TEXT = 'text/plain' - TIFF = 'image/tiff' - XML = 'application/xml' + COG = "image/tiff; application=geotiff; profile=cloud-optimized" + GEOJSON = "application/geo+json" + GEOPACKAGE = "application/geopackage+sqlite3" + GEOTIFF = "image/tiff; application=geotiff" + HDF = "application/x-hdf" # Hierarchical Data Format versions 4 and earlier. + HDF5 = "application/x-hdf5" # Hierarchical Data Format version 5 + JPEG = "image/jpeg" + JPEG2000 = "image/jp2" + JSON = "application/json" + PNG = "image/png" + TEXT = "text/plain" + TIFF = "image/tiff" + XML = "application/xml" diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index 45e9403dd..aba1e8276 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -5,7 +5,8 @@ from pystac.serialization.identify import ( STACVersionRange, # type:ignore identify_stac_object, - identify_stac_object_type) + identify_stac_object_type, +) from pystac.serialization.common_properties import merge_common_properties from pystac.serialization.migrate import migrate_to_latest @@ -14,9 +15,9 @@ from pystac.catalog import Catalog -def stac_object_from_dict(d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog"] = None) -> "STACObject": +def stac_object_from_dict( + d: Dict[str, Any], href: Optional[str] = None, root: Optional["Catalog"] = None +) -> "STACObject": """Determines how to deserialize a dictionary into a STAC object. Args: diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index 283ee42fb..b3c811d97 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -6,9 +6,11 @@ from pystac.utils import make_absolute_href -def merge_common_properties(item_dict: Dict[str, Any], - collection_cache: Optional[CollectionCache] = None, - json_href: Optional[str] = None) -> bool: +def merge_common_properties( + item_dict: Dict[str, Any], + collection_cache: Optional[CollectionCache] = None, + json_href: Optional[str] = None, +) -> bool: """Merges Collection properties into an Item. Note: This is only applicable to reading old STAC versions (pre 1.0.0-beta.1). @@ -30,26 +32,29 @@ def merge_common_properties(item_dict: Dict[str, Any], collection_id: Optional[str] = None collection_href: Optional[str] = None - stac_version = item_dict.get('stac_version') + stac_version = item_dict.get("stac_version") # The commons extension was removed in 1.0.0-beta.1, so if this is an earlier STAC # item we don't have to bother with merging. - if stac_version is not None and STACVersionID(stac_version) > '0.9.0': # type:ignore + if ( + stac_version is not None + and STACVersionID(stac_version) > "0.9.0" # type:ignore + ): return False # Check to see if this is a 0.9.0 item that # doesn't extend the commons extension, in which case # we don't have to merge. - if stac_version is not None and stac_version == '0.9.0': - stac_extensions = item_dict.get('stac_extensions') + if stac_version is not None and stac_version == "0.9.0": + stac_extensions = item_dict.get("stac_extensions") if isinstance(stac_extensions, list): - if 'commons' not in stac_extensions: + if "commons" not in stac_extensions: return False else: return False # Try the cache if we have a collection ID. - collection_id = item_dict.get('collection') + collection_id = item_dict.get("collection") if collection_id is not None: if collection_cache is not None: collection = collection_cache.get_by_id(collection_id) @@ -57,14 +62,16 @@ def merge_common_properties(item_dict: Dict[str, Any], # Next, try the collection link. if collection is None: # Account for 0.5 links, which were dicts - if isinstance(item_dict['links'], dict): - links = list(cast(Iterable[Dict[str, Any]], item_dict['links'].values())) + if isinstance(item_dict["links"], dict): + links = list(cast(Iterable[Dict[str, Any]], item_dict["links"].values())) else: - links = cast(List[Dict[str, Any]], item_dict['links']) + links = cast(List[Dict[str, Any]], item_dict["links"]) - collection_link = next((link for link in links if link['rel'] == 'collection'), None) + collection_link = next( + (link for link in links if link["rel"] == "collection"), None + ) if collection_link is not None: - collection_href = cast(Dict[str, Any], collection_link).get('href') + collection_href = cast(Dict[str, Any], collection_link).get("href") if collection_href is not None: if json_href is not None: collection_href = make_absolute_href(collection_href, json_href) @@ -81,21 +88,26 @@ def merge_common_properties(item_dict: Dict[str, Any], collection_id = collection.id collection_props = collection.extra_fields.get("properties") elif isinstance(collection, dict): - collection_id = collection['id'] - if 'properties' in collection: - collection_props = collection['properties'] + collection_id = collection["id"] + if "properties" in collection: + collection_props = collection["properties"] else: - raise ValueError('{} is expected to be a Collection or ' - 'dict but is neither.'.format(collection)) + raise ValueError( + "{} is expected to be a Collection or " + "dict but is neither.".format(collection) + ) if collection_props is not None: for k in collection_props: - if k not in item_dict['properties']: + if k not in item_dict["properties"]: properties_merged = True - item_dict['properties'][k] = collection_props[k] + item_dict["properties"][k] = collection_props[k] - if (collection_cache is not None and collection_id is not None - and not collection_cache.contains_id(collection_id)): + if ( + collection_cache is not None + and collection_id is not None + and not collection_cache.contains_id(collection_id) + ): collection_cache.cache(collection, href=collection_href) return properties_merged diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index f746d64bd..821817c59 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -11,23 +11,24 @@ class OldExtensionShortIDs(Enum): """Enumerates the IDs of common extensions.""" - CHECKSUM = 'checksum' - COLLECTION_ASSETS = 'collection-assets' - DATACUBE = 'datacube' # TODO - EO = 'eo' - ITEM_ASSETS = 'item-assets' # TODO - LABEL = 'label' - POINTCLOUD = 'pointcloud' - PROJECTION = 'projection' - SAR = 'sar' - SAT = 'sat' - SCIENTIFIC = 'scientific' - SINGLE_FILE_STAC = 'single-file-stac' - TILED_ASSETS = 'tiled-assets' - TIMESTAMPS = 'timestamps' - VERSION = 'version' - VIEW = 'view' - FILE = 'file' + + CHECKSUM = "checksum" + COLLECTION_ASSETS = "collection-assets" + DATACUBE = "datacube" # TODO + EO = "eo" + ITEM_ASSETS = "item-assets" # TODO + LABEL = "label" + POINTCLOUD = "pointcloud" + PROJECTION = "projection" + SAR = "sar" + SAT = "sat" + SCIENTIFIC = "scientific" + SINGLE_FILE_STAC = "single-file-stac" + TILED_ASSETS = "tiled-assets" + TIMESTAMPS = "timestamps" + VERSION = "version" + VIEW = "view" + FILE = "file" @total_ordering @@ -35,16 +36,17 @@ class STACVersionID: """Defines STAC versions in an object that is orderable based on version number. For instance, ``1.0.0-beta.2 < 1.0.0`` """ + def __init__(self, version_string: str) -> None: self.version_string = version_string # Account for RC or beta releases in version - version_parts = version_string.split('-') + version_parts = version_string.split("-") self.version_core = version_parts[0] if len(version_parts) == 1: self.version_prerelease = None else: - self.version_prerelease = '-'.join(version_parts[1:]) + self.version_prerelease = "-".join(version_parts[1:]) def __str__(self) -> str: return self.version_string @@ -67,14 +69,18 @@ def __lt__(self, other: Any) -> bool: else: return self.version_prerelease is not None and ( other.version_prerelease is None - or other.version_prerelease > self.version_prerelease) + or other.version_prerelease > self.version_prerelease + ) class STACVersionRange: """Defines a range of STAC versions.""" - def __init__(self, - min_version: Union[str, STACVersionID] = '0.4.0', - max_version: Optional[Union[str, STACVersionID]] = None): + + def __init__( + self, + min_version: Union[str, STACVersionID] = "0.4.0", + max_version: Optional[Union[str, STACVersionID]] = None, + ): if isinstance(min_version, str): self.min_version = STACVersionID(min_version) else: @@ -128,7 +134,7 @@ def is_later_than(self, v: Union[str, STACVersionID]) -> bool: return v < self.min_version def __repr__(self) -> str: - return ''.format(self.min_version, self.max_version) + return "".format(self.min_version, self.max_version) class STACJSONDescription: @@ -141,19 +147,26 @@ class STACJSONDescription: extensions (List[str]): List of extension schema URIs for extensions this object implements """ - def __init__(self, object_type: "STACObjectType_Type", version_range: STACVersionRange, - extensions: Set[str]) -> None: + + def __init__( + self, + object_type: "STACObjectType_Type", + version_range: STACVersionRange, + extensions: Set[str], + ) -> None: self.object_type = object_type self.version_range = version_range self.extensions = extensions def __repr__(self) -> str: - return '<{} {} ext={}>'.format(self.object_type, self.version_range, - ','.join(self.extensions)) + return "<{} {} ext={}>".format( + self.object_type, self.version_range, ",".join(self.extensions) + ) -def _identify_stac_extensions(object_type: str, d: Dict[str, Any], - version_range: STACVersionRange) -> List[str]: +def _identify_stac_extensions( + object_type: str, d: Dict[str, Any], version_range: STACVersionRange +) -> List[str]: """Identifies extensions for STAC Objects that don't list their extensions in a 'stac_extensions' property. @@ -165,108 +178,121 @@ def _identify_stac_extensions(object_type: str, d: Dict[str, Any], # assets (collection assets) if object_type == ps.STACObjectType.ITEMCOLLECTION: - if 'assets' in d: - stac_extensions.add('assets') - version_range.set_min(STACVersionID('0.8.0')) + if "assets" in d: + stac_extensions.add("assets") + version_range.set_min(STACVersionID("0.8.0")) # checksum - if 'links' in d: + if "links" in d: found_checksum = False - for link in d['links']: + for link in d["links"]: # Account for old links as dicts if isinstance(link, str): - link_props = cast(Dict[str, Any], d['links'][link]).keys() + link_props = cast(Dict[str, Any], d["links"][link]).keys() else: link_props = cast(Dict[str, Any], link).keys() - if any(prop.startswith('checksum:') for prop in link_props): + if any(prop.startswith("checksum:") for prop in link_props): found_checksum = True stac_extensions.add(OldExtensionShortIDs.CHECKSUM.value) if not found_checksum: - if 'assets' in d: - for asset in d['assets'].values(): + if "assets" in d: + for asset in d["assets"].values(): asset_props = cast(Dict[str, Any], asset).keys() - if any(prop.startswith('checksum:') for prop in asset_props): + if any(prop.startswith("checksum:") for prop in asset_props): found_checksum = True stac_extensions.add(OldExtensionShortIDs.CHECKSUM.value) if found_checksum: - version_range.set_min(STACVersionID('0.6.2')) + version_range.set_min(STACVersionID("0.6.2")) # datacube if object_type == ps.STACObjectType.ITEM: - if any(k.startswith('cube:') for k in cast(Dict[str, Any], d['properties'])): + if any(k.startswith("cube:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.DATACUBE.value) - version_range.set_min(STACVersionID('0.6.1')) + version_range.set_min(STACVersionID("0.6.1")) # datetime-range (old extension) if object_type == ps.STACObjectType.ITEM: - if 'dtr:start_datetime' in d['properties']: - stac_extensions.add('datetime-range') - version_range.set_min(STACVersionID('0.6.0')) + if "dtr:start_datetime" in d["properties"]: + stac_extensions.add("datetime-range") + version_range.set_min(STACVersionID("0.6.0")) # eo if object_type == ps.STACObjectType.ITEM: - if any(k.startswith('eo:') for k in cast(Dict[str, Any], d['properties'])): + if any(k.startswith("eo:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.EO.value) - if 'eo:epsg' in d['properties']: - if d['properties']['eo:epsg'] is None: - version_range.set_min(STACVersionID('0.6.1')) - if 'eo:crs' in d['properties']: - version_range.set_max(STACVersionID('0.4.1')) - if 'eo:constellation' in d['properties']: - version_range.set_min(STACVersionID('0.6.0')) - if 'eo:bands' in d: + if "eo:epsg" in d["properties"]: + if d["properties"]["eo:epsg"] is None: + version_range.set_min(STACVersionID("0.6.1")) + if "eo:crs" in d["properties"]: + version_range.set_max(STACVersionID("0.4.1")) + if "eo:constellation" in d["properties"]: + version_range.set_min(STACVersionID("0.6.0")) + if "eo:bands" in d: stac_extensions.add(OldExtensionShortIDs.EO.value) - version_range.set_max(STACVersionID('0.5.2')) + version_range.set_max(STACVersionID("0.5.2")) # pointcloud if object_type == ps.STACObjectType.ITEM: - if any(k.startswith('pc:') for k in cast(Dict[str, Any], d['properties'])): + if any(k.startswith("pc:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.POINTCLOUD.value) - version_range.set_min(STACVersionID('0.6.2')) + version_range.set_min(STACVersionID("0.6.2")) # sar if object_type == ps.STACObjectType.ITEM: - if any(k.startswith('sar:') for k in cast(Dict[str, Any], d['properties'])): + if any(k.startswith("sar:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.SAR.value) - version_range.set_min(STACVersionID('0.6.2')) - if version_range.contains('0.6.2'): + version_range.set_min(STACVersionID("0.6.2")) + if version_range.contains("0.6.2"): for prop in [ - 'sar:absolute_orbit', 'sar:resolution', 'sar:pixel_spacing', 'sar:looks' + "sar:absolute_orbit", + "sar:resolution", + "sar:pixel_spacing", + "sar:looks", ]: - if prop in d['properties']: - if isinstance(d['properties'][prop], list): - version_range.set_max(STACVersionID('0.6.2')) - if version_range.contains('0.7.0'): + if prop in d["properties"]: + if isinstance(d["properties"][prop], list): + version_range.set_max(STACVersionID("0.6.2")) + if version_range.contains("0.7.0"): for prop in [ - 'sar:incidence_angle', 'sar:relative_orbit', 'sar:observation_direction', - 'sar:resolution_range', 'sar:resolution_azimuth', 'sar:pixel_spacing_range', - 'sar:pixel_spacing_azimuth', 'sar:looks_range', 'sar:looks_azimuth', - 'sar:looks_equivalent_number' + "sar:incidence_angle", + "sar:relative_orbit", + "sar:observation_direction", + "sar:resolution_range", + "sar:resolution_azimuth", + "sar:pixel_spacing_range", + "sar:pixel_spacing_azimuth", + "sar:looks_range", + "sar:looks_azimuth", + "sar:looks_equivalent_number", ]: - if prop in d['properties']: - version_range.set_min(STACVersionID('0.7.0')) - if 'sar:absolute_orbit' in d['properties'] and not isinstance( - d['properties']['sar:absolute_orbit'], list): - version_range.set_min(STACVersionID('0.7.0')) - if 'sar:off_nadir' in d['properties']: - version_range.set_max(STACVersionID('0.6.2')) + if prop in d["properties"]: + version_range.set_min(STACVersionID("0.7.0")) + if "sar:absolute_orbit" in d["properties"] and not isinstance( + d["properties"]["sar:absolute_orbit"], list + ): + version_range.set_min(STACVersionID("0.7.0")) + if "sar:off_nadir" in d["properties"]: + version_range.set_max(STACVersionID("0.6.2")) # scientific - if object_type == ps.STACObjectType.ITEM or object_type == ps.STACObjectType.COLLECTION: - if 'properties' in d: - prop_keys = cast(Dict[str, Any], d['properties']).keys() - if any(k.startswith('sci:') for k in prop_keys): + if ( + object_type == ps.STACObjectType.ITEM + or object_type == ps.STACObjectType.COLLECTION + ): + if "properties" in d: + prop_keys = cast(Dict[str, Any], d["properties"]).keys() + if any(k.startswith("sci:") for k in prop_keys): stac_extensions.add(OldExtensionShortIDs.SCIENTIFIC.value) - version_range.set_min(STACVersionID('0.6.0')) + version_range.set_min(STACVersionID("0.6.0")) # Single File STAC if object_type == ps.STACObjectType.ITEMCOLLECTION: - if 'collections' in d: + if "collections" in d: stac_extensions.add(OldExtensionShortIDs.SINGLE_FILE_STAC.value) - version_range.set_min(STACVersionID('0.8.0')) - if 'stac_extensions' not in d: - version_range.set_max(STACVersionID('0.8.1')) + version_range.set_min(STACVersionID("0.8.0")) + if "stac_extensions" not in d: + version_range.set_max(STACVersionID("0.8.1")) return list(stac_extensions) @@ -282,22 +308,24 @@ def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectType_Type """ object_type = None - if 'type' in json_dict: # Try to identify using 'type' property + if "type" in json_dict: # Try to identify using 'type' property for t in ps.STACObjectType: - if json_dict['type'].lower() == t.value.lower(): + if json_dict["type"].lower() == t.value.lower(): object_type = t break if object_type is None: # Use old-approach based on other properties # Identify pre-1.0 ITEMCOLLECTION (since removed) - if 'type' in json_dict and 'assets' not in json_dict: - if 'stac_version' in json_dict and json_dict['stac_version'].startswith('0'): - if json_dict['type'] == 'FeatureCollection': + if "type" in json_dict and "assets" not in json_dict: + if "stac_version" in json_dict and json_dict["stac_version"].startswith( + "0" + ): + if json_dict["type"] == "FeatureCollection": object_type = ps.STACObjectType.ITEMCOLLECTION - if 'extent' in json_dict: + if "extent" in json_dict: object_type = ps.STACObjectType.COLLECTION - elif 'assets' in json_dict: + elif "assets" in json_dict: object_type = ps.STACObjectType.ITEM else: object_type = ps.STACObjectType.CATALOG @@ -319,32 +347,37 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: version_range = STACVersionRange() - stac_version = json_dict.get('stac_version') - stac_extensions = json_dict.get('stac_extensions', None) + stac_version = json_dict.get("stac_version") + stac_extensions = json_dict.get("stac_extensions", None) if stac_version is None: - if (object_type == ps.STACObjectType.CATALOG - or object_type == ps.STACObjectType.COLLECTION): - version_range.set_max(STACVersionID('0.5.2')) + if ( + object_type == ps.STACObjectType.CATALOG + or object_type == ps.STACObjectType.COLLECTION + ): + version_range.set_max(STACVersionID("0.5.2")) elif object_type == ps.STACObjectType.ITEM: - version_range.set_max(STACVersionID('0.7.0')) + version_range.set_max(STACVersionID("0.7.0")) else: # ItemCollection - version_range.set_min(STACVersionID('0.8.0')) + version_range.set_min(STACVersionID("0.8.0")) else: version_range.set_to_single(stac_version) if stac_extensions is not None: - version_range.set_min(STACVersionID('0.8.0')) + version_range.set_min(STACVersionID("0.8.0")) if stac_extensions is None: # If this is post-0.8, we can assume there are no extensions # if the stac_extensions property doesn't exist for everything # but ItemCollection (except after 0.9.0, when ItemCollection also got # the stac_extensions property). - if version_range.is_earlier_than('0.8.0') or \ - (object_type == ps.STACObjectType.ITEMCOLLECTION and not version_range.is_later_than( - '0.8.1')): - stac_extensions = _identify_stac_extensions(object_type, json_dict, version_range) + if version_range.is_earlier_than("0.8.0") or ( + object_type == ps.STACObjectType.ITEMCOLLECTION + and not version_range.is_later_than("0.8.1") + ): + stac_extensions = _identify_stac_extensions( + object_type, json_dict, version_range + ) else: stac_extensions = [] @@ -359,15 +392,18 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: if not version_range.is_single_version(): # Final Checks - if 'links' in json_dict: + if "links" in json_dict: # links were a dictionary only in 0.5 - if 'links' in json_dict and isinstance(json_dict['links'], dict): - version_range.set_to_single(STACVersionID('0.5.2')) + if "links" in json_dict and isinstance(json_dict["links"], dict): + version_range.set_to_single(STACVersionID("0.5.2")) # self links became non-required in 0.7.0 - if not version_range.is_earlier_than('0.7.0') and \ - not any(filter(lambda l: cast(Dict[str, Any], l)['rel'] == 'self', - json_dict['links'])): - version_range.set_min(STACVersionID('0.7.0')) + if not version_range.is_earlier_than("0.7.0") and not any( + filter( + lambda l: cast(Dict[str, Any], l)["rel"] == "self", + json_dict["links"], + ) + ): + version_range.set_min(STACVersionID("0.7.0")) return STACJSONDescription(object_type, version_range, set(stac_extensions)) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 3a02a4ac0..5f448268f 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -3,85 +3,112 @@ import pystac as ps from pystac.version import STACVersion -from pystac.serialization.identify import (OldExtensionShortIDs, STACJSONDescription, STACVersionID) +from pystac.serialization.identify import ( + OldExtensionShortIDs, + STACJSONDescription, + STACVersionID, +) if TYPE_CHECKING: from pystac import STACObjectType as STACObjectType_Type def _migrate_links(d: Dict[str, Any], version: STACVersionID) -> None: - if version < '0.6': - if 'links' in d: - if isinstance(d['links'], dict): - d['links'] = list(d['links'].values()) + if version < "0.6": + if "links" in d: + if isinstance(d["links"], dict): + d["links"] = list(d["links"].values()) -def _migrate_catalog(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: +def _migrate_catalog( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> None: _migrate_links(d, version) - if version < '0.8': - d['stac_extensions'] = list(info.extensions) + if version < "0.8": + d["stac_extensions"] = list(info.extensions) -def _migrate_collection(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: +def _migrate_collection( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> None: _migrate_catalog(d, version, info) -def _migrate_item(d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription) -> None: +def _migrate_item( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> None: _migrate_links(d, version) - if version < '0.8': - d['stac_extensions'] = list(info.extensions) + if version < "0.8": + d["stac_extensions"] = list(info.extensions) -def _migrate_itemcollection(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: - if version < '0.9.0': - d['stac_extensions'] = list(info.extensions) +def _migrate_itemcollection( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> None: + if version < "0.9.0": + d["stac_extensions"] = list(info.extensions) # Extensions -def _migrate_item_assets(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - if version < '1.0.0-beta.2': +def _migrate_item_assets( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> Optional[Set[str]]: + if version < "1.0.0-beta.2": if info.object_type == ps.STACObjectType.COLLECTION: - if 'assets' in d: - d['item_assets'] = d['assets'] - del d['assets'] + if "assets" in d: + d["item_assets"] = d["assets"] + del d["assets"] return None -def _migrate_datetime_range(d: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> Optional[Set[str]]: - if version < '0.9': +def _migrate_datetime_range( + d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> Optional[Set[str]]: + if version < "0.9": # Datetime range was removed - if 'dtr:start_datetime' in d['properties'] and 'start_datetime' not in d['properties']: - d['properties']['start_datetime'] = d['properties']['dtr:start_datetime'] - del d['properties']['dtr:start_datetime'] - - if 'dtr:end_datetime' in d['properties'] and 'end_datetime' not in d['properties']: - d['properties']['end_datetime'] = d['properties']['dtr:end_datetime'] - del d['properties']['dtr:end_datetime'] + if ( + "dtr:start_datetime" in d["properties"] + and "start_datetime" not in d["properties"] + ): + d["properties"]["start_datetime"] = d["properties"]["dtr:start_datetime"] + del d["properties"]["dtr:start_datetime"] + + if ( + "dtr:end_datetime" in d["properties"] + and "end_datetime" not in d["properties"] + ): + d["properties"]["end_datetime"] = d["properties"]["dtr:end_datetime"] + del d["properties"]["dtr:end_datetime"] return None -def _get_object_migrations( -) -> Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]]: +def _get_object_migrations() -> Dict[ + str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None] +]: return { ps.STACObjectType.CATALOG: _migrate_catalog, ps.STACObjectType.COLLECTION: _migrate_collection, ps.STACObjectType.ITEM: _migrate_item, - ps.STACObjectType.ITEMCOLLECTION: _migrate_itemcollection + ps.STACObjectType.ITEMCOLLECTION: _migrate_itemcollection, } -def _get_removed_extension_migrations( -) -> Dict[str, Tuple[Optional[List["STACObjectType_Type"]], Optional[Callable[ - [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]]]]]]: # noqa +def _get_removed_extension_migrations() -> Dict[ + str, + Tuple[ + Optional[List["STACObjectType_Type"]], + Optional[ + Callable[ + [Dict[str, Any], STACVersionID, STACJSONDescription], Optional[Set[str]] + ] + ], + ], +]: # noqa """Handles removed extensions. This does not handle renamed extension or extensions that were absorbed @@ -95,10 +122,8 @@ def _get_removed_extension_migrations( """ return { # -- Removed in 1.0 - # assets in collections became a core property OldExtensionShortIDs.COLLECTION_ASSETS.value: (None, None), - # Extensions that were placed on Collections that applied to # the 'commons' properties of their Items, but since the commons # property merging has went away these extensions are removed @@ -113,28 +138,27 @@ def _get_removed_extension_migrations( OldExtensionShortIDs.SAT.value: ([ps.STACObjectType.COLLECTION], None), OldExtensionShortIDs.TIMESTAMPS.value: ([ps.STACObjectType.COLLECTION], None), OldExtensionShortIDs.VIEW.value: ([ps.STACObjectType.COLLECTION], None), - # tiled-assets was never a fully published extension; # remove reference to the pre-1.0 RC short ID OldExtensionShortIDs.TILED_ASSETS.value: (None, None), - # Single File STAC is a removed concept; is being reworked as of # STAC 1.0.0-RC.3. Remove short ID from PySTAC as it's unsupported OldExtensionShortIDs.SINGLE_FILE_STAC.value: (None, None), - # -- Removed in 0.9.0 - 'dtr': (None, _migrate_datetime_range), - 'datetime-range': (None, _migrate_datetime_range), - 'commons': (None, None) + "dtr": (None, _migrate_datetime_range), + "datetime-range": (None, _migrate_datetime_range), + "commons": (None, None), } # TODO: Item Assets def _get_extension_renames() -> Dict[str, str]: - return {'asset': 'item-assets'} + return {"asset": "item-assets"} -def migrate_to_latest(json_dict: Dict[str, Any], info: STACJSONDescription) -> Dict[str, Any]: +def migrate_to_latest( + json_dict: Dict[str, Any], info: STACJSONDescription +) -> Dict[str, Any]: """Migrates the STAC JSON to the latest version Args: @@ -155,20 +179,20 @@ def migrate_to_latest(json_dict: Dict[str, Any], info: STACJSONDescription) -> D if version != STACVersion.DEFAULT_STAC_VERSION: object_migrations[info.object_type](result, version, info) - if 'stac_extensions' not in result: + if "stac_extensions" not in result: # Force stac_extensions property, as it makes # downstream migration less complex - result['stac_extensions'] = [] + result["stac_extensions"] = [] ps.EXTENSION_HOOKS.migrate(result, version, info) - for ext in result['stac_extensions'][:]: + for ext in result["stac_extensions"][:]: if ext in removed_extension_migrations: object_types, migration_fn = removed_extension_migrations[ext] if object_types is None or info.object_type in object_types: if migration_fn: migration_fn(result, version, info) - result['stac_extensions'].remove(ext) + result["stac_extensions"].remove(ext) - result['stac_version'] = STACVersion.DEFAULT_STAC_VERSION + result["stac_version"] = STACVersion.DEFAULT_STAC_VERSION return result diff --git a/pystac/stac_io.py b/pystac/stac_io.py index bef68b14a..69ffd2150 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -1,7 +1,17 @@ from abc import ABC, abstractmethod import os import json -from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + TYPE_CHECKING, + Tuple, + Type, + Union, +) from urllib.parse import urlparse from urllib.request import urlopen @@ -26,7 +36,9 @@ class StacIO(ABC): _default_io: Optional[Type["StacIO"]] = None @abstractmethod - def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) -> str: + def read_text( + self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any + ) -> str: """Read text from the given URI. The source to read from can be specified @@ -44,8 +56,9 @@ def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) raise NotImplementedError("read_text not implemented") @abstractmethod - def write_text(self, dest: Union[str, "Link_Type"], txt: str, *args: Any, - **kwargs: Any) -> None: + def write_text( + self, dest: Union[str, "Link_Type"], txt: str, *args: Any, **kwargs: Any + ) -> None: """Write the given text to a file at the given URI. The destination to write to from can be specified @@ -64,16 +77,20 @@ def _json_loads(self, txt: str, source: Union[str, "Link_Type"]) -> Dict[str, An else: return json.loads(self.read_text(txt)) - def _json_dumps(self, json_dict: Dict[str, Any], source: Union[str, "Link_Type"]) -> str: + def _json_dumps( + self, json_dict: Dict[str, Any], source: Union[str, "Link_Type"] + ) -> str: if orjson is not None: - return orjson.dumps(json_dict, option=orjson.OPT_INDENT_2).decode('utf-8') + return orjson.dumps(json_dict, option=orjson.OPT_INDENT_2).decode("utf-8") else: return json.dumps(json_dict, indent=2) - def stac_object_from_dict(self, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + def stac_object_from_dict( + self, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog_Type"] = None, + ) -> "STACObject_Type": result = pystac.serialization.stac_object_from_dict(d, href, root) if isinstance(result, ps.Catalog): # Set the stac_io instance for usage by io operations @@ -97,9 +114,9 @@ def read_json(self, source: Union[str, "Link_Type"]) -> Dict[str, Any]: txt = self.read_text(source) return self._json_loads(txt, source) - def read_stac_object(self, - source: Union[str, "Link_Type"], - root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + def read_stac_object( + self, source: Union[str, "Link_Type"], root: Optional["Catalog_Type"] = None + ) -> "STACObject_Type": """Read a STACObject from a JSON file at the given source. See :func:`StacIO.read_text ` for usage of @@ -119,7 +136,9 @@ def read_stac_object(self, href = source if isinstance(source, str) else source.get_absolute_href() return self.stac_object_from_dict(d, href=href, root=root) - def save_json(self, dest: Union[str, "Link_Type"], json_dict: Dict[str, Any]) -> None: + def save_json( + self, dest: Union[str, "Link_Type"], json_dict: Dict[str, Any] + ) -> None: """Write a dict to the given URI as JSON. See :func:`StacIO.write_text ` for usage of @@ -146,7 +165,9 @@ def default(cls) -> "StacIO": class DefaultStacIO(StacIO): - def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) -> str: + def read_text( + self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any + ) -> str: if isinstance(source, str): href = source else: @@ -155,18 +176,19 @@ def read_text(self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any) raise IOError(f"Could not get an absolute HREF from link {source}") parsed = urlparse(href) - if parsed.scheme != '': + if parsed.scheme != "": try: with urlopen(href) as f: - return f.read().decode('utf-8') + return f.read().decode("utf-8") except HTTPError as e: raise Exception("Could not read uri {}".format(href)) from e else: with open(href) as f: return f.read() - def write_text(self, dest: Union[str, "Link_Type"], txt: str, *args: Any, - **kwargs: Any) -> None: + def write_text( + self, dest: Union[str, "Link_Type"], txt: str, *args: Any, **kwargs: Any + ) -> None: if isinstance(dest, str): href = dest else: @@ -175,9 +197,9 @@ def write_text(self, dest: Union[str, "Link_Type"], txt: str, *args: Any, raise IOError(f"Could not get an absolute HREF from link {dest}") dirname = os.path.dirname(href) - if dirname != '' and not os.path.isdir(dirname): + if dirname != "" and not os.path.isdir(dirname): os.makedirs(dirname) - with open(href, 'w') as f: + with open(href, "w") as f: f.write(txt) @@ -191,18 +213,30 @@ class DuplicateKeyReportingMixin(StacIO): See https://github.com/stac-utils/pystac/issues/313 """ + def _json_loads(self, txt: str, source: Union[str, "Link_Type"]) -> Dict[str, Any]: - return json.loads(txt, object_pairs_hook=self.duplicate_object_names_report_builder(source)) + return json.loads( + txt, object_pairs_hook=self.duplicate_object_names_report_builder(source) + ) @staticmethod def duplicate_object_names_report_builder( - source: Union[str, "Link_Type"]) -> Callable[[List[Tuple[str, Any]]], Dict[str, Any]]: - def report_duplicate_object_names(object_pairs: List[Tuple[str, Any]]) -> Dict[str, Any]: + source: Union[str, "Link_Type"] + ) -> Callable[[List[Tuple[str, Any]]], Dict[str, Any]]: + def report_duplicate_object_names( + object_pairs: List[Tuple[str, Any]] + ) -> Dict[str, Any]: result: Dict[str, Any] = {} for key, value in object_pairs: if key in result: - url = source if isinstance(source, str) else source.get_absolute_href() - raise DuplicateObjectKeyError(f"Found duplicate object name “{key}” in “{url}”") + url = ( + source + if isinstance(source, str) + else source.get_absolute_href() + ) + raise DuplicateObjectKeyError( + f"Found duplicate object name “{key}” in “{url}”" + ) else: result[key] = value return result @@ -218,6 +252,7 @@ class STAC_IO: Note: The static methods of this class are deprecated. Move to using instance methods of a specific instance of StacIO. """ + @staticmethod def read_text_method(uri: str) -> str: return StacIO.default().read_text(uri) @@ -228,9 +263,11 @@ def write_text_method(uri: str, txt: str) -> None: return StacIO.default().write_text(uri, txt) @staticmethod - def stac_object_from_dict(d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + def stac_object_from_dict( + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog_Type"] = None, + ) -> "STACObject_Type": return pystac.serialization.stac_object_from_dict(d, href, root) # This is set in __init__.py @@ -290,7 +327,9 @@ def read_json(cls, uri: str) -> Dict[str, Any]: return json.loads(STAC_IO.read_text(uri)) @classmethod - def read_stac_object(cls, uri: str, root: Optional["Catalog_Type"] = None) -> "STACObject_Type": + def read_stac_object( + cls, uri: str, root: Optional["Catalog_Type"] = None + ) -> "STACObject_Type": """Read a STACObject from a JSON file at the given URI. Args: diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 7653c1084..5e98ffe85 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,11 +1,11 @@ -from abc import (ABC, abstractmethod) +from abc import ABC, abstractmethod from enum import Enum from typing import Any, Dict, Iterable, List, Optional, cast, TYPE_CHECKING import pystac as ps from pystac import STACError from pystac.link import Link -from pystac.utils import (is_absolute_href, make_absolute_href) +from pystac.utils import is_absolute_href, make_absolute_href if TYPE_CHECKING: from pystac.catalog import Catalog as Catalog_Type @@ -15,10 +15,10 @@ class STACObjectType(str, Enum): def __str__(self) -> str: return str(self.value) - CATALOG = 'CATALOG' - COLLECTION = 'COLLECTION' - ITEM = 'ITEM' - ITEMCOLLECTION = 'ITEMCOLLECTION' + CATALOG = "CATALOG" + COLLECTION = "COLLECTION" + ITEM = "ITEM" + ITEMCOLLECTION = "ITEMCOLLECTION" class STACObject(ABC): @@ -31,6 +31,7 @@ class STACObject(ABC): links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this STACObject. """ + id: str STAC_OBJECT_TYPE: STACObjectType @@ -50,6 +51,7 @@ def validate(self) -> List[Any]: STACValidationError """ import pystac.validation + return pystac.validation.validate(self) # type:ignore def add_link(self, link: Link) -> None: @@ -124,7 +126,7 @@ def get_root_link(self) -> Optional[Link]: :class:`~pystac.Link` or None: The root link for this object, or ``None`` if no root link is set. """ - return self.get_single_link('root') + return self.get_single_link("root") @property def self_href(self) -> str: @@ -155,7 +157,7 @@ def get_self_href(self) -> Optional[str]: have the HREF the file was read from set as it's self HREF. All self links have absolute (as opposed to relative) HREFs. """ - self_link = self.get_single_link('self') + self_link = self.get_single_link("self") if self_link: return cast(str, self_link.target) else: @@ -173,14 +175,18 @@ def set_self_href(self, href: Optional[str]) -> None: """ root_link = self.get_root_link() if root_link is not None and root_link.is_resolved(): - cast(ps.Catalog, root_link.target)._resolved_objects.remove(cast(STACObject, self)) + cast(ps.Catalog, root_link.target)._resolved_objects.remove( + cast(STACObject, self) + ) - self.remove_links('self') + self.remove_links("self") if href is not None: self.add_link(Link.self_href(href)) if root_link is not None and root_link.is_resolved(): - cast(ps.Catalog, root_link.target)._resolved_objects.cache(cast(STACObject, self)) + cast(ps.Catalog, root_link.target)._resolved_objects.cache( + cast(STACObject, self) + ) def get_root(self) -> Optional["Catalog_Type"]: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to @@ -209,8 +215,9 @@ def set_root(self, root: Optional["Catalog_Type"]) -> None: root (Catalog, Collection or None): The root object to set. Passing in None will clear the root. """ - root_link_index = next(iter([i for i, link in enumerate(self.links) if link.rel == 'root']), - None) + root_link_index = next( + iter([i for i, link in enumerate(self.links) if link.rel == "root"]), None + ) # Remove from old root resolution cache if root_link_index is not None: @@ -219,7 +226,7 @@ def set_root(self, root: Optional["Catalog_Type"]) -> None: cast(ps.Catalog, root_link.target)._resolved_objects.remove(self) if root is None: - self.remove_links('root') + self.remove_links("root") else: new_root_link = Link.root(root) if root_link_index is not None: @@ -239,7 +246,7 @@ def get_parent(self) -> Optional["Catalog_Type"]: The parent object for this object, or ``None`` if no root link is set. """ - parent_link = self.get_single_link('parent') + parent_link = self.get_single_link("parent") if parent_link: return cast(ps.Catalog, parent_link.resolve_stac_object().target) else: @@ -254,7 +261,7 @@ def set_parent(self, parent: Optional["Catalog_Type"]) -> None: object to set. Passing in None will clear the parent. """ - self.remove_links('parent') + self.remove_links("parent") if parent is not None: self.add_link(Link.parent(parent)) @@ -277,10 +284,12 @@ def get_stac_objects(self, rel: str) -> Iterable["STACObject"]: link.resolve_stac_object(root=self.get_root()) yield cast("STACObject", link.target) - def save_object(self, - include_self_link: bool = True, - dest_href: Optional[str] = None, - stac_io: Optional[ps.StacIO] = None) -> None: + def save_object( + self, + include_self_link: bool = True, + dest_href: Optional[str] = None, + stac_io: Optional[ps.StacIO] = None, + ) -> None: """Saves this STAC Object to it's 'self' HREF. Args: @@ -315,14 +324,17 @@ def save_object(self, self_href = self.get_self_href() if self_href is None: raise STACError( - 'Self HREF must be set before saving without an explicit dest_href.') + "Self HREF must be set before saving without an explicit dest_href." + ) dest_href = self_href stac_io.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) - def full_copy(self, - root: Optional["Catalog_Type"] = None, - parent: Optional["Catalog_Type"] = None) -> "STACObject": + def full_copy( + self, + root: Optional["Catalog_Type"] = None, + parent: Optional["Catalog_Type"] = None, + ) -> "STACObject": """Create a full copy of this STAC object and any stac objects linked to by this object. @@ -356,13 +368,13 @@ def full_copy(self, target = cached_target else: target_parent = None - if link.rel in ['child', 'item'] and isinstance(clone, ps.Catalog): + if link.rel in ["child", "item"] and isinstance(clone, ps.Catalog): target_parent = clone copied_target = target.full_copy(root=root, parent=target_parent) if root is not None: root._resolved_objects.cache(copied_target) target = copied_target - if link.rel in ['child', 'item']: + if link.rel in ["child", "item"]: target.set_root(root) if isinstance(clone, ps.Catalog): target.set_parent(clone) @@ -394,7 +406,7 @@ def resolve_links(self) -> None: This method mutates the entire catalog tree. """ - link_rels = set(self._object_links()) | set(['root', 'parent']) + link_rels = set(self._object_links()) | set(["root", "parent"]) for link in self.links: if link.rel in link_rels: @@ -471,11 +483,13 @@ def from_file(cls, href: str, stac_io: Optional[ps.StacIO] = None) -> "STACObjec @classmethod @abstractmethod - def from_dict(cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog_Type"] = None, - migrate: bool = False) -> "STACObject": + def from_dict( + cls, + d: Dict[str, Any], + href: Optional[str] = None, + root: Optional["Catalog_Type"] = None, + migrate: bool = False, + ) -> "STACObject": """Parses this STACObject from the passed in dictionary. Args: diff --git a/pystac/utils.py b/pystac/utils.py index a874beb18..482873101 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -2,7 +2,7 @@ import posixpath from pystac.errors import RequiredPropertyMissing from typing import Any, Callable, Dict, List, Optional, TypeVar, Union -from urllib.parse import (urlparse, ParseResult as URLParseResult) +from urllib.parse import urlparse, ParseResult as URLParseResult from datetime import datetime, timezone import dateutil.parser @@ -18,13 +18,15 @@ def _urlparse(href: str) -> URLParseResult: This method will take this into account. """ parsed = urlparse(href) - if parsed.scheme != '' and href.lower().startswith('{}:\\'.format(parsed.scheme)): - return URLParseResult(scheme='', - netloc='', - path='{}:{}'.format(parsed.scheme, parsed.path), - params=parsed.params, - query=parsed.query, - fragment=parsed.fragment) + if parsed.scheme != "" and href.lower().startswith("{}:\\".format(parsed.scheme)): + return URLParseResult( + scheme="", + netloc="", + path="{}:{}".format(parsed.scheme, parsed.path), + params=parsed.params, + query=parsed.query, + fragment=parsed.fragment, + ) else: return parsed @@ -41,7 +43,9 @@ def _join(is_path: bool, *args: str) -> str: return posixpath.join(*args) -def make_relative_href(source_href: str, start_href: str, start_is_dir: bool = False) -> str: +def make_relative_href( + source_href: str, start_href: str, start_is_dir: bool = False +) -> str: """Makes a given HREF relative to the given starting HREF. Args: @@ -58,11 +62,13 @@ def make_relative_href(source_href: str, start_href: str, start_is_dir: bool = F parsed_source = _urlparse(source_href) parsed_start = _urlparse(start_href) - if not (parsed_source.scheme == parsed_start.scheme - and parsed_source.netloc == parsed_start.netloc): + if not ( + parsed_source.scheme == parsed_start.scheme + and parsed_source.netloc == parsed_start.netloc + ): return source_href - is_path = parsed_start.scheme == '' + is_path = parsed_start.scheme == "" if start_is_dir: start_dir = parsed_start.path @@ -70,16 +76,16 @@ def make_relative_href(source_href: str, start_href: str, start_is_dir: bool = F start_dir = _pathlib.dirname(parsed_start.path) relpath = _pathlib.relpath(parsed_source.path, start_dir) if not is_path: - relpath = relpath.replace('\\', '/') - if not relpath.startswith('.'): - relpath = _join(is_path, '.', relpath) + relpath = relpath.replace("\\", "/") + if not relpath.startswith("."): + relpath = _join(is_path, ".", relpath) return relpath -def make_absolute_href(source_href: str, - start_href: Optional[str] = None, - start_is_dir: bool = False) -> str: +def make_absolute_href( + source_href: str, start_href: Optional[str] = None, start_is_dir: bool = False +) -> str: """Makes a given HREF absolute based on the given starting HREF. Args: @@ -100,10 +106,10 @@ def make_absolute_href(source_href: str, start_is_dir = True parsed_source = _urlparse(source_href) - if parsed_source.scheme == '': + if parsed_source.scheme == "": if not _pathlib.isabs(parsed_source.path): parsed_start = _urlparse(start_href) - is_path = parsed_start.scheme == '' + is_path = parsed_start.scheme == "" if start_is_dir: start_dir = parsed_start.path else: @@ -116,11 +122,13 @@ def make_absolute_href(source_href: str, if not start_dir == _pathlib.abspath(start_dir): abs_path = abs_path.replace(_pathlib.abspath(start_dir), start_dir) - if parsed_start.scheme != '': + if parsed_start.scheme != "": if not is_path: - abs_path = abs_path.replace('\\', '/') + abs_path = abs_path.replace("\\", "/") - return '{}://{}{}'.format(parsed_start.scheme, parsed_start.netloc, abs_path) + return "{}://{}{}".format( + parsed_start.scheme, parsed_start.netloc, abs_path + ) else: return abs_path else: @@ -139,7 +147,7 @@ def is_absolute_href(href: str) -> bool: bool: True if the given HREF is absolute, False if it is relative. """ parsed = _urlparse(href) - return parsed.scheme != '' or _pathlib.isabs(parsed.path) + return parsed.scheme != "" or _pathlib.isabs(parsed.path) def datetime_to_str(dt: datetime) -> str: @@ -155,9 +163,9 @@ def datetime_to_str(dt: datetime) -> str: dt = dt.replace(tzinfo=timezone.utc) timestamp = dt.isoformat() - zulu = '+00:00' + zulu = "+00:00" if timestamp.endswith(zulu): - timestamp = '{}Z'.format(timestamp[:-len(zulu)]) + timestamp = "{}Z".format(timestamp[: -len(zulu)]) return timestamp @@ -176,7 +184,7 @@ def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]: list: Bounding box of geojson geometry, formatted according to: https://tools.ietf.org/html/rfc7946#section-5 """ - coords = geometry['coordinates'] + coords = geometry["coordinates"] lats: List[float] = [] lons: List[float] = [] @@ -205,8 +213,8 @@ def extract_coords(coords: List[Union[List[float], List[List[Any]]]]) -> None: return bbox -T = TypeVar('T') -U = TypeVar('U') +T = TypeVar("T") +U = TypeVar("U") def map_opt(fn: Callable[[T], U], v: Optional[T]) -> Optional[U]: @@ -217,7 +225,7 @@ def map_opt(fn: Callable[[T], U], v: Optional[T]) -> Optional[U]: def get_opt(option: Optional[T]) -> T: - """ Retrieves the value of the Optional type. + """Retrieves the value of the Optional type. If the Optional is None, this will raise a value error. Use this to get a properly typed value from an optional @@ -234,7 +242,7 @@ def get_opt(option: Optional[T]) -> T: def get_required(option: Optional[T], obj: Union[str, Any], prop: str) -> T: - """ Retrieves an optional value that comes from a required property. + """Retrieves an optional value that comes from a required property. If the option is None, throws an RequiredPropertyError with the given obj and property """ diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 6273fde13..808da0845 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -20,13 +20,14 @@ class STACValidationError(Exception): validation implementation. For the default JsonSchemaValidator this will a the ``jsonschema.ValidationError``. """ + def __init__(self, message: str, source: Optional[Any] = None): super().__init__(message) self.source = source # Import after above class definition -from pystac.validation.stac_validator import (STACValidator, JsonSchemaSTACValidator) +from pystac.validation.stac_validator import STACValidator, JsonSchemaSTACValidator def validate(stac_object: "STACObject_Type") -> List[Any]: @@ -43,18 +44,22 @@ def validate(stac_object: "STACObject_Type") -> List[Any]: Raises: STACValidationError """ - return validate_dict(stac_dict=stac_object.to_dict(), - stac_object_type=stac_object.STAC_OBJECT_TYPE, - stac_version=ps.get_stac_version(), - extensions=stac_object.stac_extensions, - href=stac_object.get_self_href()) - - -def validate_dict(stac_dict: Dict[str, Any], - stac_object_type: Optional["STACObjectType_Type"] = None, - stac_version: Optional[str] = None, - extensions: Optional[List[str]] = None, - href: Optional[str] = None) -> List[Any]: + return validate_dict( + stac_dict=stac_object.to_dict(), + stac_object_type=stac_object.STAC_OBJECT_TYPE, + stac_version=ps.get_stac_version(), + extensions=stac_object.stac_extensions, + href=stac_object.get_self_href(), + ) + + +def validate_dict( + stac_dict: Dict[str, Any], + stac_object_type: Optional["STACObjectType_Type"] = None, + stac_version: Optional[str] = None, + extensions: Optional[List[str]] = None, + href: Optional[str] = None, +) -> List[Any]: """Validate a stac object serialized as JSON into a dict. This method delegates to the call to :meth:`pystac.validation.STACValidator.validate` @@ -103,15 +108,19 @@ def _get_uri(ext: str) -> Optional[str]: return OldExtensionSchemaUriMap.get_extension_schema_uri( ext, stac_object_type, # type:ignore - stac_version_id) + stac_version_id, + ) extensions = [uri for uri in map(_get_uri, extensions) if uri is not None] - return RegisteredValidator.get_validator().validate(stac_dict, stac_object_type, stac_version, - extensions, href) + return RegisteredValidator.get_validator().validate( + stac_dict, stac_object_type, stac_version, extensions, href + ) -def validate_all(stac_dict: Dict[str, Any], href: str, stac_io: Optional[ps.StacIO] = None) -> None: +def validate_all( + stac_dict: Dict[str, Any], href: str, stac_io: Optional[ps.StacIO] = None +) -> None: """Validate STAC JSON and all contained catalogs, collections and items. If this stac_dict represents a catalog or collection, this method will @@ -135,23 +144,27 @@ def validate_all(stac_dict: Dict[str, Any], href: str, stac_io: Optional[ps.Stac info = identify_stac_object(stac_dict) # Validate this object - validate_dict(stac_dict, - stac_object_type=info.object_type, - stac_version=str(info.version_range.latest_valid_version()), - extensions=list(info.extensions), - href=href) + validate_dict( + stac_dict, + stac_object_type=info.object_type, + stac_version=str(info.version_range.latest_valid_version()), + extensions=list(info.extensions), + href=href, + ) if info.object_type != ps.STACObjectType.ITEM: - if 'links' in stac_dict: + if "links" in stac_dict: # Account for 0.6 links - if isinstance(stac_dict['links'], dict): - links: List[Dict[str, Any]] = list(stac_dict['links'].values()) + if isinstance(stac_dict["links"], dict): + links: List[Dict[str, Any]] = list(stac_dict["links"].values()) else: - links = cast(List[Dict[str, Any]], stac_dict.get('links')) + links = cast(List[Dict[str, Any]], stac_dict.get("links")) for link in links: - rel = link.get('rel') - if rel in ['item', 'child']: - link_href = make_absolute_href(cast(str, link.get('href')), start_href=href) + rel = link.get("rel") + if rel in ["item", "child"]: + link_href = make_absolute_href( + cast(str, link.get("href")), start_href=href + ) if link_href is not None: d = stac_io.read_json(link_href) validate_all(d, link_href) @@ -168,8 +181,9 @@ def get_validator(cls) -> STACValidator: except ImportError: raise Exception( 'Cannot validate with default validator because package "jsonschema" ' - 'is not installed. Install pystac with the validation optional requirements ' - '(e.g. pip install pystac[validation]) to install jsonschema') + "is not installed. Install pystac with the validation optional requirements " + "(e.g. pip install pystac[validation]) to install jsonschema" + ) cls._validator = JsonSchemaSTACValidator() return cls._validator @@ -177,7 +191,7 @@ def get_validator(cls) -> STACValidator: @classmethod def set_validator(cls, validator: STACValidator) -> None: if not issubclass(type(validator), STACValidator): - raise Exception('Validator must be a subclass of {}'.format(STACValidator)) + raise Exception("Validator must be a subclass of {}".format(STACValidator)) cls._validator = validator diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 829b5f770..c9ccab928 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,4 +1,4 @@ -from abc import (ABC, abstractmethod) +from abc import ABC, abstractmethod from functools import lru_cache from pystac.serialization.identify import OldExtensionShortIDs, STACVersionID from typing import Any, Callable, Dict, List, Optional, Tuple @@ -9,14 +9,15 @@ class SchemaUriMap(ABC): - """Abstract class defining schema URIs for STAC core objects and extensions. - """ + """Abstract class defining schema URIs for STAC core objects and extensions.""" + def __init__(self) -> None: pass @abstractmethod - def get_object_schema_uri(self, object_type: STACObjectType, - stac_version: str) -> Optional[str]: + def get_object_schema_uri( + self, object_type: STACObjectType, stac_version: str + ) -> Optional[str]: """Get the schema URI for the given object type and stac version. Args: @@ -42,10 +43,16 @@ class DefaultSchemaUriMap(SchemaUriMap): # contains it. If the version it outside of any VersionRange, there is no URI for the # schema. BASE_URIS: List[Tuple[STACVersionRange, Callable[[str], str]]] = [ - (STACVersionRange(min_version='1.0.0-beta.1'), - lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), - (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: - 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version)) + ( + STACVersionRange(min_version="1.0.0-beta.1"), + lambda version: "https://schemas.stacspec.org/v{}".format(version), + ), + ( + STACVersionRange(min_version="0.8.0", max_version="0.9.0"), + lambda version: "https://raw.githubusercontent.com/radiantearth/stac-spec/v{}".format( + version + ), + ), ] # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema URIs @@ -56,37 +63,44 @@ class DefaultSchemaUriMap(SchemaUriMap): # the listed version range is used, or else the URI from the latest version is used if # there are no overrides for previous versions. DEFAULT_SCHEMA_MAP: Dict[str, Any] = { - STACObjectType.CATALOG: ('catalog-spec/json-schema/catalog.json', None), - STACObjectType.COLLECTION: ('collection-spec/json-schema/collection.json', None), - STACObjectType.ITEM: ('item-spec/json-schema/item.json', None), - STACObjectType.ITEMCOLLECTION: (None, [ - STACVersionRange(min_version='v0.8.0-rc1', max_version='0.9.0'), - 'item-spec/json-schema/itemcollection.json' - ]) + STACObjectType.CATALOG: ("catalog-spec/json-schema/catalog.json", None), + STACObjectType.COLLECTION: ( + "collection-spec/json-schema/collection.json", + None, + ), + STACObjectType.ITEM: ("item-spec/json-schema/item.json", None), + STACObjectType.ITEMCOLLECTION: ( + None, + [ + STACVersionRange(min_version="v0.8.0-rc1", max_version="0.9.0"), + "item-spec/json-schema/itemcollection.json", + ], + ), } @classmethod def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str]: # Only append the base URI if it's not already an absolute URI - if '://' not in uri: + if "://" not in uri: base_uri = None for version_range, f in cls.BASE_URIS: if version_range.contains(stac_version): base_uri = f(stac_version) - return '{}/{}'.format(base_uri, uri) + return "{}/{}".format(base_uri, uri) # We don't have JSON schema validation for this version of PySTAC return None else: return uri - def get_object_schema_uri(self, object_type: STACObjectType, - stac_version: str) -> Optional[str]: + def get_object_schema_uri( + self, object_type: STACObjectType, stac_version: str + ) -> Optional[str]: uri = None is_latest = stac_version == ps.get_stac_version() if object_type not in self.DEFAULT_SCHEMA_MAP: - raise KeyError('Unknown STAC object type {}'.format(object_type)) + raise KeyError("Unknown STAC object type {}".format(object_type)) uri = self.DEFAULT_SCHEMA_MAP[object_type][0] if not is_latest: @@ -113,11 +127,21 @@ class OldExtensionSchemaUriMap: # schema. @classmethod @lru_cache() - def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: - return [(STACVersionRange(min_version='1.0.0-beta.1'), - lambda version: 'https://schemas.stacspec.org/v{}'.format(version)), - (STACVersionRange(min_version='0.8.0', max_version='0.9.0'), lambda version: - 'https://raw.githubusercontent.com/radiantearth/stac-spec/v{}'.format(version))] + def get_base_uris( + cls, + ) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: + return [ + ( + STACVersionRange(min_version="1.0.0-beta.1"), + lambda version: "https://schemas.stacspec.org/v{}".format(version), + ), + ( + STACVersionRange(min_version="0.8.0", max_version="0.9.0"), + lambda version: "https://raw.githubusercontent.com/radiantearth/stac-spec/v{}".format( + version + ), + ), + ] # DEFAULT_SCHEMA_MAP contains a structure that matches extension schema URIs # based on the stac object type, extension ID and the stac version. @@ -130,107 +154,137 @@ def get_base_uris(cls) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], @lru_cache() def get_schema_map(cls) -> Dict[str, Any]: return { - OldExtensionShortIDs.CHECKSUM.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/checksum/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/checksum/json-schema/schema.json' - }, None), - OldExtensionShortIDs.COLLECTION_ASSETS.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/collection-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.DATACUBE.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/datacube/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/datacube/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.5.0', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: None, - ps.STACObjectType.ITEM: None - })]), - OldExtensionShortIDs.EO.value: ({ - ps.STACObjectType.ITEM: - 'extensions/eo/json-schema/schema.json' - }, None), - OldExtensionShortIDs.ITEM_ASSETS.value: ({ - ps.STACObjectType.COLLECTION: - 'extensions/item-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.LABEL.value: ({ - ps.STACObjectType.ITEM: - 'extensions/label/json-schema/schema.json' - }, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.8.1'), { - ps.STACObjectType.ITEM: 'extensions/label/schema.json' - })]), + OldExtensionShortIDs.CHECKSUM.value: ( + { + ps.STACObjectType.CATALOG: "extensions/checksum/json-schema/schema.json", + ps.STACObjectType.COLLECTION: "extensions/checksum/json-schema/schema.json", + ps.STACObjectType.ITEM: "extensions/checksum/json-schema/schema.json", + }, + None, + ), + OldExtensionShortIDs.COLLECTION_ASSETS.value: ( + { + ps.STACObjectType.COLLECTION: "extensions/collection-assets/json-schema/schema.json" + }, + None, + ), + OldExtensionShortIDs.DATACUBE.value: ( + { + ps.STACObjectType.COLLECTION: "extensions/datacube/json-schema/schema.json", + ps.STACObjectType.ITEM: "extensions/datacube/json-schema/schema.json", + }, + [ + ( + STACVersionRange(min_version="0.5.0", max_version="0.9.0"), + { + ps.STACObjectType.COLLECTION: None, + ps.STACObjectType.ITEM: None, + }, + ) + ], + ), + OldExtensionShortIDs.EO.value: ( + {ps.STACObjectType.ITEM: "extensions/eo/json-schema/schema.json"}, + None, + ), + OldExtensionShortIDs.ITEM_ASSETS.value: ( + { + ps.STACObjectType.COLLECTION: "extensions/item-assets/json-schema/schema.json" + }, + None, + ), + OldExtensionShortIDs.LABEL.value: ( + {ps.STACObjectType.ITEM: "extensions/label/json-schema/schema.json"}, + [ + ( + STACVersionRange(min_version="0.8.0-rc1", max_version="0.8.1"), + {ps.STACObjectType.ITEM: "extensions/label/schema.json"}, + ) + ], + ), OldExtensionShortIDs.POINTCLOUD.value: ( # Invalid schema None, - None), - OldExtensionShortIDs.PROJECTION.value: ({ - ps.STACObjectType.ITEM: - 'extensions/projection/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAR.value: ({ - ps.STACObjectType.ITEM: - 'extensions/sar/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SAT.value: ({ - ps.STACObjectType.ITEM: - 'extensions/sat/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SCIENTIFIC.value: ({ - ps.STACObjectType.ITEM: - 'extensions/scientific/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/scientific/json-schema/schema.json' - }, None), - OldExtensionShortIDs.SINGLE_FILE_STAC.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/single-file-stac/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TILED_ASSETS.value: ({ - ps.STACObjectType.CATALOG: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/tiled-assets/json-schema/schema.json', - ps.STACObjectType.ITEM: - 'extensions/tiled-assets/json-schema/schema.json' - }, None), - OldExtensionShortIDs.TIMESTAMPS.value: ({ - ps.STACObjectType.ITEM: - 'extensions/timestamps/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VERSION.value: ({ - ps.STACObjectType.ITEM: - 'extensions/version/json-schema/schema.json', - ps.STACObjectType.COLLECTION: - 'extensions/version/json-schema/schema.json' - }, None), - OldExtensionShortIDs.VIEW.value: ({ - ps.STACObjectType.ITEM: - 'extensions/view/json-schema/schema.json' - }, None), - + None, + ), + OldExtensionShortIDs.PROJECTION.value: ( + { + ps.STACObjectType.ITEM: "extensions/projection/json-schema/schema.json" + }, + None, + ), + OldExtensionShortIDs.SAR.value: ( + {ps.STACObjectType.ITEM: "extensions/sar/json-schema/schema.json"}, + None, + ), + OldExtensionShortIDs.SAT.value: ( + {ps.STACObjectType.ITEM: "extensions/sat/json-schema/schema.json"}, + None, + ), + OldExtensionShortIDs.SCIENTIFIC.value: ( + { + ps.STACObjectType.ITEM: "extensions/scientific/json-schema/schema.json", + ps.STACObjectType.COLLECTION: "extensions/scientific/json-schema/schema.json", + }, + None, + ), + OldExtensionShortIDs.SINGLE_FILE_STAC.value: ( + { + ps.STACObjectType.CATALOG: "extensions/single-file-stac/json-schema/schema.json" + }, + None, + ), + OldExtensionShortIDs.TILED_ASSETS.value: ( + { + ps.STACObjectType.CATALOG: "extensions/tiled-assets/json-schema/schema.json", + ps.STACObjectType.COLLECTION: "extensions/tiled-assets/json-schema/schema.json", + ps.STACObjectType.ITEM: "extensions/tiled-assets/json-schema/schema.json", + }, + None, + ), + OldExtensionShortIDs.TIMESTAMPS.value: ( + { + ps.STACObjectType.ITEM: "extensions/timestamps/json-schema/schema.json" + }, + None, + ), + OldExtensionShortIDs.VERSION.value: ( + { + ps.STACObjectType.ITEM: "extensions/version/json-schema/schema.json", + ps.STACObjectType.COLLECTION: "extensions/version/json-schema/schema.json", + }, + None, + ), + OldExtensionShortIDs.VIEW.value: ( + {ps.STACObjectType.ITEM: "extensions/view/json-schema/schema.json"}, + None, + ), # Removed or renamed extensions. - 'dtr': (None, None), # Invalid schema - 'asset': (None, [(STACVersionRange(min_version='0.8.0-rc1', max_version='0.9.0'), { - ps.STACObjectType.COLLECTION: - 'extensions/asset/json-schema/schema.json' - })]), + "dtr": (None, None), # Invalid schema + "asset": ( + None, + [ + ( + STACVersionRange(min_version="0.8.0-rc1", max_version="0.9.0"), + { + ps.STACObjectType.COLLECTION: "extensions/asset/json-schema/schema.json" + }, + ) + ], + ), } @classmethod - def _append_base_uri_if_needed(cls, uri: str, stac_version: STACVersionID) -> Optional[str]: + def _append_base_uri_if_needed( + cls, uri: str, stac_version: STACVersionID + ) -> Optional[str]: # Only append the base URI if it's not already an absolute URI - if '://' not in uri: + if "://" not in uri: base_uri = None for version_range, f in cls.get_base_uris(): if version_range.contains(stac_version): base_uri = f(stac_version) - return '{}/{}'.format(base_uri, uri) + return "{}/{}".format(base_uri, uri) # No JSON Schema for the old extension return None @@ -238,16 +292,16 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: STACVersionID) -> Op return uri @classmethod - def get_extension_schema_uri(cls, extension_id: str, object_type: STACObjectType, - stac_version: STACVersionID) -> Optional[str]: + def get_extension_schema_uri( + cls, extension_id: str, object_type: STACObjectType, stac_version: STACVersionID + ) -> Optional[str]: uri = None is_latest = stac_version == ps.get_stac_version() ext_map = cls.get_schema_map() if extension_id in ext_map: - if ext_map[extension_id][0] and \ - object_type in ext_map[extension_id][0]: + if ext_map[extension_id][0] and object_type in ext_map[extension_id][0]: uri = ext_map[extension_id][0][object_type] if not is_latest: diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 3ada2e782..f5354a09e 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -1,4 +1,4 @@ -from abc import (ABC, abstractmethod) +from abc import ABC, abstractmethod import logging import json from pystac.stac_object import STACObjectType @@ -25,12 +25,15 @@ class STACValidator(ABC): STACValidator implementation and set that validator to be used by pystac by using the :func:`~pystac.validation.set_validator` method. """ + @abstractmethod - def validate_core(self, - stac_dict: Dict[str, Any], - stac_object_type: STACObjectType, - stac_version: str, - href: Optional[str] = None) -> Any: + def validate_core( + self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + href: Optional[str] = None, + ) -> Any: """Validate a core stac object. Return value can be None or specific to the implementation. @@ -45,12 +48,14 @@ def validate_core(self, pass @abstractmethod - def validate_extension(self, - stac_dict: Dict[str, Any], - stac_object_type: STACObjectType, - stac_version: str, - extension_id: str, - href: Optional[str] = None) -> Any: + def validate_extension( + self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + extension_id: str, + href: Optional[str] = None, + ) -> Any: """Validate an extension stac object. Return value can be None or specific to the implementation. @@ -65,12 +70,14 @@ def validate_extension(self, """ pass - def validate(self, - stac_dict: Dict[str, Any], - stac_object_type: STACObjectType, - stac_version: str, - extensions: List[str], - href: Optional[str] = None) -> List[Any]: + def validate( + self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + extensions: List[str], + href: Optional[str] = None, + ) -> List[Any]: """Validate a STAC object JSON. Args: @@ -92,13 +99,16 @@ def validate(self, # 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) + 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(json_dict, stac_object_type, stac_version, - extension_id, href) + 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) @@ -121,9 +131,10 @@ class JsonSchemaSTACValidator(STACValidator): Note: This class requires the ``jsonschema`` library to be installed. """ + def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: if jsonschema is None: - raise Exception('Cannot instantiate, requires jsonschema package') + raise Exception("Cannot instantiate, requires jsonschema package") if schema_uri_map is not None: self.schema_uri_map = schema_uri_map @@ -139,38 +150,47 @@ def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: schema = self.schema_cache[schema_uri] - resolver = jsonschema.validators.RefResolver(base_uri=schema_uri, - referrer=schema, - store=self.schema_cache) + resolver = jsonschema.validators.RefResolver( + base_uri=schema_uri, referrer=schema, store=self.schema_cache + ) return (schema, resolver) def _validate_from_uri(self, stac_dict: Dict[str, Any], schema_uri: str) -> None: schema, resolver = self.get_schema_from_uri(schema_uri) - jsonschema.validate(instance=stac_dict, schema=schema, resolver=resolver) # type:ignore + jsonschema.validate( + instance=stac_dict, schema=schema, resolver=resolver + ) # type:ignore for uri in resolver.store: if uri not in self.schema_cache: self.schema_cache[uri] = resolver.store[uri] - def _get_error_message(self, schema_uri: str, stac_object_type: STACObjectType, - extension_id: Optional[str], href: Optional[str], - stac_id: Optional[str]) -> str: - s = 'Validation failed for {} '.format(stac_object_type) + def _get_error_message( + self, + schema_uri: str, + stac_object_type: STACObjectType, + extension_id: Optional[str], + href: Optional[str], + stac_id: Optional[str], + ) -> str: + s = "Validation failed for {} ".format(stac_object_type) if href is not None: - s += 'at {} '.format(href) + s += "at {} ".format(href) if stac_id is not None: - s += 'with ID {} '.format(stac_id) - s += 'against schema at {}'.format(schema_uri) + s += "with ID {} ".format(stac_id) + s += "against schema at {}".format(schema_uri) if extension_id is not None: s += " for STAC extension '{}'".format(extension_id) return s - def validate_core(self, - stac_dict: Dict[str, Any], - stac_object_type: STACObjectType, - stac_version: str, - href: Optional[str] = None) -> Optional[str]: + def validate_core( + self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + href: Optional[str] = None, + ) -> Optional[str]: """Validate a core stac object. Return value can be None or specific to the implementation. @@ -186,7 +206,9 @@ def validate_core(self, str: URI for the JSON schema that was validated against, or None if no validation occurred. """ - schema_uri = self.schema_uri_map.get_object_schema_uri(stac_object_type, stac_version) + schema_uri = self.schema_uri_map.get_object_schema_uri( + stac_object_type, stac_version + ) if schema_uri is None: return None @@ -195,16 +217,19 @@ def validate_core(self, self._validate_from_uri(stac_dict, schema_uri) return schema_uri except jsonschema.exceptions.ValidationError as e: - msg = self._get_error_message(schema_uri, stac_object_type, None, href, - stac_dict.get('id')) + msg = self._get_error_message( + schema_uri, stac_object_type, None, href, stac_dict.get("id") + ) raise STACValidationError(msg, source=e) from e - def validate_extension(self, - stac_dict: Dict[str, Any], - stac_object_type: STACObjectType, - stac_version: str, - extension_id: str, - href: Optional[str] = None) -> Optional[str]: + def validate_extension( + self, + stac_dict: Dict[str, Any], + stac_object_type: STACObjectType, + stac_version: str, + extension_id: str, + href: Optional[str] = None, + ) -> Optional[str]: """Validate an extension stac object. Return value can be None or specific to the implementation. @@ -230,8 +255,9 @@ def validate_extension(self, self._validate_from_uri(stac_dict, schema_uri) return schema_uri except jsonschema.exceptions.ValidationError as e: - msg = self._get_error_message(schema_uri, stac_object_type, extension_id, href, - stac_dict.get('id')) + msg = self._get_error_message( + schema_uri, stac_object_type, extension_id, href, stac_dict.get("id") + ) raise STACValidationError(msg, source=e) from e except Exception as e: logger.error(f"Exception while validating {stac_object_type} href: {href}") diff --git a/pystac/version.py b/pystac/version.py index 091eb4d9a..426589ab2 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -1,18 +1,18 @@ import os from typing import Optional -__version__ = '0.5.6' +__version__ = "0.5.6" """Library version""" class STACVersion: - DEFAULT_STAC_VERSION = '1.0.0-rc.3' + DEFAULT_STAC_VERSION = "1.0.0-rc.3" """Latest STAC version supported by PySTAC""" # Version that holds a user-set STAC version to use. _override_version: Optional[str] = None - OVERRIDE_VERSION_ENV_VAR = 'PYSTAC_STAC_VERSION_OVERRIDE' + OVERRIDE_VERSION_ENV_VAR = "PYSTAC_STAC_VERSION_OVERRIDE" @classmethod def get_stac_version(cls) -> str: diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index aaf579398..af85a1891 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -17,22 +17,28 @@ def migrate(path: str) -> None: with open(path) as f: stac_json = json.load(f) except json.decoder.JSONDecodeError: - print(f'Cannot read {path}') + print(f"Cannot read {path}") raise - if 'stac_version' in stac_json: - cur_ver = stac_json['stac_version'] + if "stac_version" in stac_json: + cur_ver = stac_json["stac_version"] if not cur_ver == TARGET_VERSION: - print(' - Migrating {} from {} to {}...'.format(path, cur_ver, TARGET_VERSION)) + print( + " - Migrating {} from {} to {}...".format( + path, cur_ver, TARGET_VERSION + ) + ) obj = ps.read_dict(stac_json, href=path) migrated = obj.to_dict(include_self_link=False) - with open(path, 'w') as f: + with open(path, "w") as f: json.dump(migrated, f, indent=2) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('--file', metavar='FILE', help='Only migrate this specific file.') +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Process some integers.") + parser.add_argument( + "--file", metavar="FILE", help="Only migrate this specific file." + ) args = parser.parse_args() @@ -44,16 +50,18 @@ def migrate(path: str) -> None: # Skip examples directory, which contains version specific STACs... dirs_to_check = [ - os.path.join(data_files_dir, x) for x in os.listdir(data_files_dir) if x != 'examples' + os.path.join(data_files_dir, x) + for x in os.listdir(data_files_dir) + if x != "examples" ] for d in dirs_to_check: - print(f'checking in {d}..') + print(f"checking in {d}..") for root, _, files in os.walk(d): # Skip directories with a version tag if re.match(r".*-v\d.*", root): continue for fname in files: - if fname.endswith('.json'): + if fname.endswith(".json"): path = os.path.join(root, fname) migrate(path) diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index 3f3e46c0a..a0c3ab015 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -15,40 +15,54 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: - links: Optional[List[Dict[str, Any]]] = js.get('links') + links: Optional[List[Dict[str, Any]]] = js.get("links") if links is not None: filtered_links: List[Dict[str, Any]] = [] for link in links: - rel = link.get('rel') - if rel is not None and rel == 'collection': - href: str = link['href'] + rel = link.get("rel") + if rel is not None and rel == "collection": + href: str = link["href"] try: json.loads(ps.StacIO.default().read_text(href)) filtered_links.append(link) except (HTTPError, FileNotFoundError, json.decoder.JSONDecodeError): - print('===REMOVING UNREADABLE COLLECTION AT {}'.format(href)) + print("===REMOVING UNREADABLE COLLECTION AT {}".format(href)) else: filtered_links.append(link) - js['links'] = filtered_links + js["links"] = filtered_links return js -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Get examples from the stac-spec repo.') +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Get examples from the stac-spec repo." + ) parser.add_argument( - 'previous_version', - metavar='PREVIOUS_VERSION', - help='The previous STAC_VERSION that examples have already been pulled from.') + "previous_version", + metavar="PREVIOUS_VERSION", + help="The previous STAC_VERSION that examples have already been pulled from.", + ) args = parser.parse_args() - stac_repo = 'https://github.com/radiantearth/stac-spec' - stac_spec_tag = 'v{}'.format(ps.get_stac_version()) + stac_repo = "https://github.com/radiantearth/stac-spec" + stac_spec_tag = "v{}".format(ps.get_stac_version()) - examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'examples')) + examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "examples")) with TemporaryDirectory() as tmp_dir: - call(['git', 'clone', '--depth', '1', '--branch', stac_spec_tag, stac_repo, tmp_dir]) + call( + [ + "git", + "clone", + "--depth", + "1", + "--branch", + stac_spec_tag, + stac_repo, + tmp_dir, + ] + ) example_dirs: List[str] = [] for root, _, _ in os.walk(tmp_dir): @@ -59,7 +73,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: for example_dir in example_dirs: for root, _, files in os.walk(example_dir): for fname in files: - if fname.endswith('.json'): + if fname.endswith(".json"): path = os.path.join(root, fname) with open(path) as f: try: @@ -67,14 +81,18 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: except json.decoder.JSONDecodeError: # Account for bad examples that can't be parsed. js = {} - example_version = js.get('stac_version') - if example_version is not None and \ - example_version > args.previous_version: - relpath = '{}/{}'.format(ps.get_stac_version(), - path.replace('{}/'.format(tmp_dir), '')) + example_version = js.get("stac_version") + if ( + example_version is not None + and example_version > args.previous_version + ): + relpath = "{}/{}".format( + ps.get_stac_version(), + path.replace("{}/".format(tmp_dir), ""), + ) target_path = os.path.join(examples_dir, relpath) - print('Creating example at {}'.format(target_path)) + print("Creating example at {}".format(target_path)) info = identify_stac_object(js) @@ -87,18 +105,20 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: if not os.path.isdir(d): os.makedirs(d) - with open(target_path, 'w') as f: + with open(target_path, "w") as f: f.write(json.dumps(js, indent=4)) # Add info to the new example-info.csv lines line_info: List[str] = [ - relpath, info.object_type, example_version, - '|'.join(info.extensions) + relpath, + info.object_type, + example_version, + "|".join(info.extensions), ] line = '"{}"'.format('","'.join(line_info)) example_csv_lines.add(line) # Write the new example-info.csv lines into a temp file for inspection - with open(os.path.join(examples_dir, 'examples-info-NEW.csv'), 'w') as f: - txt = '\n'.join(sorted(example_csv_lines)) + with open(os.path.join(examples_dir, "examples-info-NEW.csv"), "w") as f: + txt = "\n".join(sorted(example_csv_lines)) f.write(txt) diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index acec366b1..767335dee 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -7,10 +7,14 @@ import pystac as ps from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions import ExtensionError -from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension, SummariesExtension +from pystac.extensions.base import ( + ExtensionManagementMixin, + PropertiesExtension, + SummariesExtension, +) from pystac.extensions.hooks import ExtensionHooks -T = TypeVar('T', ps.Catalog, ps.Collection, ps.Item, ps.Asset) +T = TypeVar("T", ps.Catalog, ps.Collection, ps.Item, ps.Asset) SCHEMA_URI = "https://example.com/v2.0/custom-schema.json" @@ -18,8 +22,11 @@ TEST_LINK_REL = "test-link" -class CustomExtension(Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Catalog, ps.Collection, ps.Item]]): +class CustomExtension( + Generic[T], + PropertiesExtension, + ExtensionManagementMixin[Union[ps.Catalog, ps.Collection, ps.Item]], +): def __init__(self, obj: Optional[ps.STACObject]) -> None: self.obj = obj @@ -38,7 +45,7 @@ def add_link(self, target: ps.STACObject) -> None: if self.obj is not None: self.obj.add_link(ps.Link(TEST_LINK_REL, target)) else: - raise ExtensionError(f'{self} does not support links') + raise ExtensionError(f"{self} does not support links") @classmethod def get_schema_uri(cls) -> str: @@ -55,7 +62,7 @@ def custom_ext(obj: T) -> "CustomExtension[T]": if isinstance(obj, ps.Catalog): return cast(CustomExtension[T], CatalogCustomExtension(obj)) - raise ExtensionError(f'Custom extension does not apply to {type(obj)}') + raise ExtensionError(f"Custom extension does not apply to {type(obj)}") @staticmethod def summaries(obj: ps.Collection) -> "SummariesCustomExtension": @@ -107,18 +114,26 @@ def test_prop(self, v: Optional[RangeSummary[str]]) -> None: class CustomExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set(['custom', 'https://example.com/v1.0/custom-schema.json']) + prev_extension_ids: Set[str] = set( + ["custom", "https://example.com/v1.0/custom-schema.json"] + ) stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.CATALOG, ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM]) + [ + ps.STACObjectType.CATALOG, + ps.STACObjectType.COLLECTION, + ps.STACObjectType.ITEM, + ] + ) def get_object_links(self, obj: ps.STACObject) -> Optional[List[str]]: return [TEST_LINK_REL] - def migrate(self, obj: Dict[str, Any], version: STACVersionID, - info: STACJSONDescription) -> None: + def migrate( + self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + ) -> None: if version < "1.0.0-rc2" and info.object_type == ps.STACObjectType.ITEM: - if 'test:old-prop-name' in obj['properties']: - obj['properties'][TEST_PROP] = obj['properties']['test:old-prop-name'] + if "test:old-prop-name" in obj["properties"]: + obj["properties"][TEST_PROP] = obj["properties"]["test:old-prop-name"] super().migrate(obj, version, info) diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index 65f5a9983..f0815690d 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -6,13 +6,15 @@ from pystac.collection import RangeSummary from pystac.utils import get_opt from pystac.extensions.eo import EOExtension, Band -from tests.utils import (TestCases, test_to_from_dict) +from tests.utils import TestCases, test_to_from_dict class EOTest(unittest.TestCase): - LANDSAT_EXAMPLE_URI = TestCases.get_path('data-files/eo/eo-landsat-example.json') - BANDS_IN_ITEM_URI = TestCases.get_path('data-files/eo/sample-bands-in-item-properties.json') - EO_COLLECTION_URI = TestCases.get_path('data-files/eo/eo-collection.json') + LANDSAT_EXAMPLE_URI = TestCases.get_path("data-files/eo/eo-landsat-example.json") + BANDS_IN_ITEM_URI = TestCases.get_path( + "data-files/eo/sample-bands-in-item-properties.json" + ) + EO_COLLECTION_URI = TestCases.get_path("data-files/eo/eo-collection.json") def setUp(self): self.maxDiff = None @@ -35,7 +37,9 @@ def test_bands(self): self.assertIn("eo:bands", item.properties) bands = EOExtension.ext(item).bands assert bands is not None - self.assertEqual(list(map(lambda x: x.name, bands)), ['band1', 'band2', 'band3', 'band4']) + self.assertEqual( + list(map(lambda x: x.name, bands)), ["band1", "band2", "band3", "band4"] + ) # Set new_bands = [ @@ -45,8 +49,10 @@ def test_bands(self): ] EOExtension.ext(item).bands = new_bands - self.assertEqual('Common name: red, Range: 0.6 to 0.7', - item.properties['eo:bands'][0]['description']) + self.assertEqual( + "Common name: red, Range: 0.6 to 0.7", + item.properties["eo:bands"][0]["description"], + ) self.assertEqual(len(EOExtension.ext(item).bands or []), 3) item.validate() @@ -55,13 +61,13 @@ def test_asset_bands(self): # Get - b1_asset = item.assets['B1'] + b1_asset = item.assets["B1"] asset_bands = EOExtension.ext(b1_asset).bands assert asset_bands is not None self.assertEqual(len(asset_bands), 1) - self.assertEqual(asset_bands[0].name, 'B1') + self.assertEqual(asset_bands[0].name, "B1") - index_asset = item.assets['index'] + index_asset = item.assets["index"] asset_bands = EOExtension.ext(index_asset).bands self.assertIs(None, asset_bands) @@ -70,13 +76,13 @@ def test_asset_bands(self): self.assertIsNot(None, item_bands) # Set - b2_asset = item.assets['B2'] + b2_asset = item.assets["B2"] self.assertEqual(get_opt(EOExtension.ext(b2_asset).bands)[0].name, "B2") EOExtension.ext(b2_asset).bands = EOExtension.ext(b1_asset).bands - new_b2_asset_bands = EOExtension.ext(item.assets['B2']).bands + new_b2_asset_bands = EOExtension.ext(item.assets["B2"]).bands - self.assertEqual(get_opt(new_b2_asset_bands)[0].name, 'B1') + self.assertEqual(get_opt(new_b2_asset_bands)[0].name, "B1") item.validate() @@ -102,13 +108,15 @@ def test_cloud_cover(self): # Set EOExtension.ext(item).cloud_cover = 50 - self.assertEqual(item.properties['eo:cloud_cover'], 50) + self.assertEqual(item.properties["eo:cloud_cover"], 50) # Get from Asset - b2_asset = item.assets['B2'] - self.assertEqual(EOExtension.ext(b2_asset).cloud_cover, EOExtension.ext(item).cloud_cover) + b2_asset = item.assets["B2"] + self.assertEqual( + EOExtension.ext(b2_asset).cloud_cover, EOExtension.ext(item).cloud_cover + ) - b3_asset = item.assets['B3'] + b3_asset = item.assets["B3"] self.assertEqual(EOExtension.ext(b3_asset).cloud_cover, 20) # Set on Asset @@ -134,33 +142,39 @@ def test_summaries(self): # Set eo_summaries.cloud_cover = RangeSummary(1.0, 2.0) - eo_summaries.bands = [Band.create(name='test')] + eo_summaries.bands = [Band.create(name="test")] col_dict = col.to_dict() - self.assertEqual(len(col_dict['summaries']['eo:bands']), 1) - self.assertEqual(col_dict['summaries']['eo:cloud_cover']['minimum'], 1.0) + self.assertEqual(len(col_dict["summaries"]["eo:bands"]), 1) + self.assertEqual(col_dict["summaries"]["eo:cloud_cover"]["minimum"], 1.0) def test_read_pre_09_fields_into_common_metadata(self): eo_item = ps.Item.from_file( - TestCases.get_path('data-files/examples/0.8.1/item-spec/examples/' - 'landsat8-sample.json')) + TestCases.get_path( + "data-files/examples/0.8.1/item-spec/examples/" "landsat8-sample.json" + ) + ) self.assertEqual(eo_item.common_metadata.platform, "landsat-8") self.assertEqual(eo_item.common_metadata.instruments, ["oli_tirs"]) def test_reads_asset_bands_in_pre_1_0_version(self): item = ps.Item.from_file( - TestCases.get_path('data-files/examples/0.9.0/item-spec/examples/' - 'landsat8-sample.json')) + TestCases.get_path( + "data-files/examples/0.9.0/item-spec/examples/" "landsat8-sample.json" + ) + ) - bands = EOExtension.ext(item.assets['B9']).bands + bands = EOExtension.ext(item.assets["B9"]).bands self.assertEqual(len(bands or []), 1) - self.assertEqual(get_opt(bands)[0].common_name, 'cirrus') + self.assertEqual(get_opt(bands)[0].common_name, "cirrus") def test_reads_gsd_in_pre_1_0_version(self): eo_item = ps.Item.from_file( - TestCases.get_path('data-files/examples/0.9.0/item-spec/examples/' - 'landsat8-sample.json')) + TestCases.get_path( + "data-files/examples/0.9.0/item-spec/examples/" "landsat8-sample.json" + ) + ) self.assertEqual(eo_item.common_metadata.gsd, 30.0) diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index 6b3fa7d23..104ea2dd9 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -2,12 +2,12 @@ import unittest import pystac as ps -from tests.utils import (TestCases, test_to_from_dict) +from tests.utils import TestCases, test_to_from_dict from pystac.extensions.file import FileExtension, FileDataType class FileTest(unittest.TestCase): - FILE_EXAMPLE_URI = TestCases.get_path('data-files/file/file-example.json') + FILE_EXAMPLE_URI = TestCases.get_path("data-files/file/file-example.json") def setUp(self): self.maxDiff = None @@ -39,8 +39,10 @@ def test_asset_checksum(self): asset = item.assets["thumbnail"] # Get - self.assertEqual("90e40210f52acd32b09769d3b1871b420789456c", - FileExtension.ext(asset).checksum) + self.assertEqual( + "90e40210f52acd32b09769d3b1871b420789456c", + FileExtension.ext(asset).checksum, + ) # Set new_checksum = "90e40210163700a8a6501eccd00b6d3b44ddaed0" @@ -76,10 +78,12 @@ def test_asset_nodata(self): def test_migrates_old_checksum(self): example_path = TestCases.get_path( - 'data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json') + "data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json" + ) item = ps.Item.from_file(example_path) self.assertTrue(FileExtension.has_extension(item)) self.assertEqual( - FileExtension.ext(item.assets['noises']).checksum, - "90e40210a30d1711e81a4b11ef67b28744321659") + FileExtension.ext(item.assets["noises"]).checksum, + "90e40210a30d1711e81a4b11ef67b28744321659", + ) diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index 2559dd606..c085f785f 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -4,19 +4,29 @@ from tempfile import TemporaryDirectory import pystac as ps -from pystac import (Catalog, Item, CatalogType) -from pystac.extensions.label import (LabelExtension, LabelClasses, LabelCount, LabelOverview, - LabelStatistics, LabelType) +from pystac import Catalog, Item, CatalogType +from pystac.extensions.label import ( + LabelExtension, + LabelClasses, + LabelCount, + LabelOverview, + LabelStatistics, + LabelType, +) import pystac.validation from pystac.utils import get_opt -from tests.utils import (TestCases, test_to_from_dict) +from tests.utils import TestCases, test_to_from_dict class LabelTest(unittest.TestCase): def setUp(self): self.maxDiff = None - self.label_example_1_uri = TestCases.get_path('data-files/label/label-example-1.json') - self.label_example_2_uri = TestCases.get_path('data-files/label/label-example-2.json') + self.label_example_1_uri = TestCases.get_path( + "data-files/label/label-example-1.json" + ) + self.label_example_2_uri = TestCases.get_path( + "data-files/label/label-example-2.json" + ) def test_to_from_dict(self): with open(self.label_example_1_uri) as f: @@ -40,15 +50,15 @@ def test_from_file(self): def test_from_file_pre_081(self): d = ps.StacIO.default().read_json(self.label_example_1_uri) - d['stac_version'] = '0.8.0-rc1' - d['properties']['label:property'] = d['properties']['label:properties'] - d['properties'].pop('label:properties') - d['properties']['label:overview'] = d['properties']['label:overviews'] - d['properties'].pop('label:overviews') - d['properties']['label:method'] = d['properties']['label:methods'] - d['properties'].pop('label:methods') - d['properties']['label:task'] = d['properties']['label:tasks'] - d['properties'].pop('label:tasks') + d["stac_version"] = "0.8.0-rc1" + d["properties"]["label:property"] = d["properties"]["label:properties"] + d["properties"].pop("label:properties") + d["properties"]["label:overview"] = d["properties"]["label:overviews"] + d["properties"].pop("label:overviews") + d["properties"]["label:method"] = d["properties"]["label:methods"] + d["properties"].pop("label:methods") + d["properties"]["label:task"] = d["properties"]["label:tasks"] + d["properties"].pop("label:tasks") label_example_1 = ps.Item.from_dict(d, migrate=True) self.assertEqual(len(LabelExtension.ext(label_example_1).label_tasks or []), 2) @@ -71,17 +81,20 @@ def test_validate_label(self): pystac.validation.validate_dict(label_example_1_dict, ps.STACObjectType.ITEM) with TemporaryDirectory() as tmp_dir: - cat_dir = os.path.join(tmp_dir, 'catalog') + cat_dir = os.path.join(tmp_dir, "catalog") catalog = TestCases.test_case_1() catalog.normalize_and_save(cat_dir, catalog_type=CatalogType.SELF_CONTAINED) - cat_read = Catalog.from_file(os.path.join(cat_dir, 'catalog.json')) + cat_read = Catalog.from_file(os.path.join(cat_dir, "catalog.json")) label_item_read = cat_read.get_item("area-2-2-labels", recursive=True) label_item_read.validate() def test_read_label_item_owns_asset(self): - item = next(x for x in TestCases.test_case_2().get_all_items() - if LabelExtension.ext(x).has_extension) + item = next( + x + for x in TestCases.test_case_2().get_all_items() + if LabelExtension.ext(x).has_extension + ) assert len(item.assets) > 0 for asset_key in item.assets: self.assertEqual(item.assets[asset_key].owner, item) @@ -92,11 +105,13 @@ def test_label_description(self): # Get self.assertIn("label:description", label_item.properties) label_desc = LabelExtension.ext(label_item).label_description - self.assertEqual(label_desc, label_item.properties['label:description']) + self.assertEqual(label_desc, label_item.properties["label:description"]) # Set LabelExtension.ext(label_item).label_description = "A detailed description" - self.assertEqual("A detailed description", label_item.properties['label:description']) + self.assertEqual( + "A detailed description", label_item.properties["label:description"] + ) label_item.validate() def test_label_type(self): @@ -105,11 +120,11 @@ def test_label_type(self): # Get self.assertIn("label:type", label_item.properties) label_type = LabelExtension.ext(label_item).label_type - self.assertEqual(label_type, label_item.properties['label:type']) + self.assertEqual(label_type, label_item.properties["label:type"]) # Set LabelExtension.ext(label_item).label_type = LabelType.RASTER - self.assertEqual(LabelType.RASTER, label_item.properties['label:type']) + self.assertEqual(LabelType.RASTER, label_item.properties["label:type"]) label_item.validate() def test_label_properties(self): @@ -119,13 +134,13 @@ def test_label_properties(self): # Get self.assertIn("label:properties", label_item.properties) label_prop = LabelExtension.ext(label_item).label_properties - self.assertEqual(label_prop, label_item.properties['label:properties']) + self.assertEqual(label_prop, label_item.properties["label:properties"]) raster_label_prop = LabelExtension.ext(label_item2).label_properties self.assertEqual(raster_label_prop, None) # Set LabelExtension.ext(label_item).label_properties = ["prop1", "prop2"] - self.assertEqual(["prop1", "prop2"], label_item.properties['label:properties']) + self.assertEqual(["prop1", "prop2"], label_item.properties["label:properties"]) label_item.validate() def test_label_classes(self): @@ -139,14 +154,18 @@ def test_label_classes(self): # Set new_classes = [ LabelClasses.create(name="label2", classes=["five", "six"]), - LabelClasses.create(name="label", classes=["seven", "eight"]) + LabelClasses.create(name="label", classes=["seven", "eight"]), ] LabelExtension.ext(label_item).label_classes = new_classes - self.assertEqual([ - class_name for lc in label_item.properties["label:classes"] - for class_name in lc["classes"] - ], ["five", "six", "seven", "eight"]) + self.assertEqual( + [ + class_name + for lc in label_item.properties["label:classes"] + for class_name in lc["classes"] + ], + ["five", "six", "seven", "eight"], + ) label_item.validate() @@ -160,7 +179,7 @@ def test_label_tasks(self): # Set LabelExtension.ext(label_item).label_tasks = ["classification"] - self.assertEqual(["classification"], label_item.properties['label:tasks']) + self.assertEqual(["classification"], label_item.properties["label:tasks"]) label_item.validate() def test_label_methods(self): @@ -173,7 +192,9 @@ def test_label_methods(self): # Set LabelExtension.ext(label_item).label_methods = ["manual", "automated"] - self.assertEqual(["manual", "automated"], label_item.properties['label:methods']) + self.assertEqual( + ["manual", "automated"], label_item.properties["label:methods"] + ) label_item.validate() def test_label_overviews(self): @@ -193,35 +214,50 @@ def test_label_overviews(self): label_counts = get_opt(label_overviews[0].counts) self.assertEqual(label_counts[1].count, 17) get_opt(label_ext.label_overviews)[0].counts[1].count = 18 - self.assertEqual(label_item.properties['label:overviews'][0]['counts'][1]['count'], 18) + self.assertEqual( + label_item.properties["label:overviews"][0]["counts"][1]["count"], 18 + ) label_statistics = get_opt(label_overviews[1].statistics) self.assertEqual(label_statistics[0].name, "mean") get_opt(label_ext.label_overviews)[1].statistics[0].name = "avg" - self.assertEqual(label_item.properties['label:overviews'][1]['statistics'][0]['name'], - "avg") + self.assertEqual( + label_item.properties["label:overviews"][1]["statistics"][0]["name"], "avg" + ) # Set new_overviews = [ - LabelOverview.create(property_key="label2", - counts=[ - LabelCount.create(name="one", count=1), - LabelCount.create(name="two", count=1), - ]), - LabelOverview.create(property_key="label-reg", - statistics=[ - LabelStatistics.create(name="min", value=0.1), - LabelStatistics.create(name="max", value=1.0), - ]) + LabelOverview.create( + property_key="label2", + counts=[ + LabelCount.create(name="one", count=1), + LabelCount.create(name="two", count=1), + ], + ), + LabelOverview.create( + property_key="label-reg", + statistics=[ + LabelStatistics.create(name="min", value=0.1), + LabelStatistics.create(name="max", value=1.0), + ], + ), ] label_ext.label_overviews = new_overviews - self.assertEqual([(count['name'], count['count']) - for count in label_item.properties["label:overviews"][0]['counts']], - [("one", 1), ("two", 1)]) - - self.assertEqual([(count['name'], count['value']) - for count in label_item.properties["label:overviews"][1]['statistics']], - [("min", 0.1), ("max", 1.0)]) + self.assertEqual( + [ + (count["name"], count["count"]) + for count in label_item.properties["label:overviews"][0]["counts"] + ], + [("one", 1), ("two", 1)], + ) + + self.assertEqual( + [ + (count["name"], count["value"]) + for count in label_item.properties["label:overviews"][1]["statistics"] + ], + [("min", 0.1), ("max", 1.0)], + ) label_item.validate() diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 84463dffb..944901020 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -1,19 +1,25 @@ import json from typing import Any, Dict import unittest + # from copy import deepcopy import pystac as ps -from pystac.extensions.pointcloud import PointcloudExtension, PointcloudSchema, PointcloudStatistic -from tests.utils import (TestCases, test_to_from_dict) +from pystac.extensions.pointcloud import ( + PointcloudExtension, + PointcloudSchema, + PointcloudStatistic, +) +from tests.utils import TestCases, test_to_from_dict class PointcloudTest(unittest.TestCase): def setUp(self): self.maxDiff = None - self.example_uri = TestCases.get_path('data-files/pointcloud/example-laz.json') + self.example_uri = TestCases.get_path("data-files/pointcloud/example-laz.json") self.example_uri_no_statistics = TestCases.get_path( - 'data-files/pointcloud/example-laz-no-statistics.json') + "data-files/pointcloud/example-laz-no-statistics.json" + ) def test_to_from_dict(self): with open(self.example_uri) as f: @@ -27,12 +33,11 @@ def test_apply(self): PointcloudExtension.add_to(item) PointcloudExtension.ext(item).apply( - 1000, 'lidar', 'laszip', - [PointcloudSchema({ - 'name': 'X', - 'size': 8, - 'type': 'floating' - })]) + 1000, + "lidar", + "laszip", + [PointcloudSchema({"name": "X", "size": 8, "type": "floating"})], + ) self.assertTrue(PointcloudExtension.has_extension(item)) def test_validate_pointcloud(self): @@ -45,11 +50,11 @@ def test_count(self): # Get self.assertIn("pc:count", pc_item.properties) pc_count = PointcloudExtension.ext(pc_item).count - self.assertEqual(pc_count, pc_item.properties['pc:count']) + self.assertEqual(pc_count, pc_item.properties["pc:count"]) # Set PointcloudExtension.ext(pc_item).count = pc_count + 100 - self.assertEqual(pc_count + 100, pc_item.properties['pc:count']) + self.assertEqual(pc_count + 100, pc_item.properties["pc:count"]) # Validate pc_item.validate @@ -58,7 +63,7 @@ def test_count(self): # Ensure setting bad count fails validation with self.assertRaises(ps.STACValidationError): - PointcloudExtension.ext(pc_item).count = 'not_an_int' # type:ignore + PointcloudExtension.ext(pc_item).count = "not_an_int" # type:ignore pc_item.validate() def test_type(self): @@ -67,11 +72,11 @@ def test_type(self): # Get self.assertIn("pc:type", pc_item.properties) pc_type = PointcloudExtension.ext(pc_item).type - self.assertEqual(pc_type, pc_item.properties['pc:type']) + self.assertEqual(pc_type, pc_item.properties["pc:type"]) # Set - PointcloudExtension.ext(pc_item).type = 'sonar' - self.assertEqual('sonar', pc_item.properties['pc:type']) + PointcloudExtension.ext(pc_item).type = "sonar" + self.assertEqual("sonar", pc_item.properties["pc:type"]) # Validate pc_item.validate @@ -82,11 +87,11 @@ def test_encoding(self): # Get self.assertIn("pc:encoding", pc_item.properties) pc_encoding = PointcloudExtension.ext(pc_item).encoding - self.assertEqual(pc_encoding, pc_item.properties['pc:encoding']) + self.assertEqual(pc_encoding, pc_item.properties["pc:encoding"]) # Set - PointcloudExtension.ext(pc_item).encoding = 'binary' - self.assertEqual('binary', pc_item.properties['pc:encoding']) + PointcloudExtension.ext(pc_item).encoding = "binary" + self.assertEqual("binary", pc_item.properties["pc:encoding"]) # Validate pc_item.validate @@ -97,12 +102,14 @@ def test_schemas(self): # Get self.assertIn("pc:schemas", pc_item.properties) pc_schemas = [s.to_dict() for s in PointcloudExtension.ext(pc_item).schemas] - self.assertEqual(pc_schemas, pc_item.properties['pc:schemas']) + self.assertEqual(pc_schemas, pc_item.properties["pc:schemas"]) # Set - schema = [PointcloudSchema({'name': 'X', 'size': 8, 'type': 'floating'})] + schema = [PointcloudSchema({"name": "X", "size": 8, "type": "floating"})] PointcloudExtension.ext(pc_item).schemas = schema - self.assertEqual([s.to_dict() for s in schema], pc_item.properties['pc:schemas']) + self.assertEqual( + [s.to_dict() for s in schema], pc_item.properties["pc:schemas"] + ) # Validate pc_item.validate @@ -112,24 +119,30 @@ def test_statistics(self): # Get self.assertIn("pc:statistics", pc_item.properties) - pc_statistics = [s.to_dict() for s in PointcloudExtension.ext(pc_item).statistics] - self.assertEqual(pc_statistics, pc_item.properties['pc:statistics']) + pc_statistics = [ + s.to_dict() for s in PointcloudExtension.ext(pc_item).statistics + ] + self.assertEqual(pc_statistics, pc_item.properties["pc:statistics"]) # Set stats = [ - PointcloudStatistic({ - "average": 1, - "count": 1, - "maximum": 1, - "minimum": 1, - "name": "Test", - "position": 1, - "stddev": 1, - "variance": 1 - }) + PointcloudStatistic( + { + "average": 1, + "count": 1, + "maximum": 1, + "minimum": 1, + "name": "Test", + "position": 1, + "stddev": 1, + "variance": 1, + } + ) ] PointcloudExtension.ext(pc_item).statistics = stats - self.assertEqual([s.to_dict() for s in stats], pc_item.properties['pc:statistics']) + self.assertEqual( + [s.to_dict() for s in stats], pc_item.properties["pc:statistics"] + ) # Validate pc_item.validate @@ -139,11 +152,11 @@ def test_density(self): # Get self.assertIn("pc:density", pc_item.properties) pc_density = PointcloudExtension.ext(pc_item).density - self.assertEqual(pc_density, pc_item.properties['pc:density']) + self.assertEqual(pc_density, pc_item.properties["pc:density"]) # Set density = 100 PointcloudExtension.ext(pc_item).density = density - self.assertEqual(density, pc_item.properties['pc:density']) + self.assertEqual(density, pc_item.properties["pc:density"]) # Validate pc_item.validate @@ -174,7 +187,7 @@ def test_pointcloud_statistics(self): "name": "Test", "position": 1, "stddev": 1, - "variance": 1 + "variance": 1, } stat = PointcloudStatistic(props) self.assertEqual(props, stat.properties) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 896108700..66cbab86a 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -7,7 +7,7 @@ from pystac.validation import STACValidationError from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt -from tests.utils import (TestCases, test_to_from_dict) +from tests.utils import TestCases, test_to_from_dict WKT2 = """ GEOGCS["WGS 84", @@ -23,7 +23,8 @@ AXIS["Longitude",EAST], AUTHORITY["EPSG","4326"]] """ -PROJJSON = json.loads(""" +PROJJSON = json.loads( + """ { "$schema": "https://proj.org/schemas/v0.1/projjson.schema.json", "type": "GeographicCRS", @@ -66,13 +67,16 @@ "code": 4326 } } -""") +""" +) class ProjectionTest(unittest.TestCase): def setUp(self): self.maxDiff = None - self.example_uri = TestCases.get_path('data-files/projection/example-landsat8.json') + self.example_uri = TestCases.get_path( + "data-files/projection/example-landsat8.json" + ) def test_to_from_dict(self): with open(self.example_uri) as f: @@ -90,12 +94,10 @@ def test_apply(self): projjson=PROJJSON, geometry=item.geometry, bbox=item.bbox, - centroid={ - 'lat': 0.0, - 'lon': 1.0 - }, + centroid={"lat": 0.0, "lon": 1.0}, shape=[100, 100], - transform=[30.0, 0.0, 224985.0, 0.0, -30.0, 6790215.0, 0.0, 0.0, 1.0]) + transform=[30.0, 0.0, 224985.0, 0.0, -30.0, 6790215.0, 0.0, 0.0, 1.0], + ) def test_partial_apply(self): proj_item = ps.Item.from_file(self.example_uri) @@ -115,25 +117,27 @@ def test_epsg(self): # Get self.assertIn("proj:epsg", proj_item.properties) proj_epsg = ProjectionExtension.ext(proj_item).epsg - self.assertEqual(proj_epsg, proj_item.properties['proj:epsg']) + self.assertEqual(proj_epsg, proj_item.properties["proj:epsg"]) # Set ProjectionExtension.ext(proj_item).epsg = proj_epsg + 100 - self.assertEqual(proj_epsg + 100, proj_item.properties['proj:epsg']) + self.assertEqual(proj_epsg + 100, proj_item.properties["proj:epsg"]) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).epsg, - ProjectionExtension.ext(proj_item).epsg) + ProjectionExtension.ext(proj_item).epsg, + ) self.assertEqual(ProjectionExtension.ext(asset_prop).epsg, 9999) # Set to Asset ProjectionExtension.ext(asset_no_prop).epsg = 8888 self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).epsg, - ProjectionExtension.ext(proj_item).epsg) + ProjectionExtension.ext(proj_item).epsg, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, 8888) # Validate @@ -145,26 +149,30 @@ def test_wkt2(self): # Get self.assertIn("proj:wkt2", proj_item.properties) proj_wkt2 = ProjectionExtension.ext(proj_item).wkt2 - self.assertEqual(proj_wkt2, proj_item.properties['proj:wkt2']) + self.assertEqual(proj_wkt2, proj_item.properties["proj:wkt2"]) # Set ProjectionExtension.ext(proj_item).wkt2 = WKT2 - self.assertEqual(WKT2, proj_item.properties['proj:wkt2']) + self.assertEqual(WKT2, proj_item.properties["proj:wkt2"]) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).wkt2, - ProjectionExtension.ext(proj_item).wkt2) - self.assertTrue('TEST_TEXT' in get_opt(ProjectionExtension.ext(asset_prop).wkt2)) + ProjectionExtension.ext(proj_item).wkt2, + ) + self.assertTrue( + "TEST_TEXT" in get_opt(ProjectionExtension.ext(asset_prop).wkt2) + ) # Set to Asset asset_value = "TEST TEXT 2" ProjectionExtension.ext(asset_no_prop).wkt2 = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).wkt2, - ProjectionExtension.ext(proj_item).wkt2) + ProjectionExtension.ext(proj_item).wkt2, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, asset_value) # Validate @@ -176,28 +184,34 @@ def test_projjson(self): # Get self.assertIn("proj:projjson", proj_item.properties) proj_projjson = ProjectionExtension.ext(proj_item).projjson - self.assertEqual(proj_projjson, proj_item.properties['proj:projjson']) + self.assertEqual(proj_projjson, proj_item.properties["proj:projjson"]) # Set ProjectionExtension.ext(proj_item).projjson = PROJJSON - self.assertEqual(PROJJSON, proj_item.properties['proj:projjson']) + self.assertEqual(PROJJSON, proj_item.properties["proj:projjson"]) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).projjson, - ProjectionExtension.ext(proj_item).projjson) - self.assertEqual(ProjectionExtension.ext(asset_prop).projjson['id']['code'], 9999) + ProjectionExtension.ext(proj_item).projjson, + ) + self.assertEqual( + ProjectionExtension.ext(asset_prop).projjson["id"]["code"], 9999 + ) # Set to Asset asset_value = deepcopy(PROJJSON) - asset_value['id']['code'] = 7777 + asset_value["id"]["code"] = 7777 ProjectionExtension.ext(asset_no_prop).projjson = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).projjson, - ProjectionExtension.ext(proj_item).projjson) - self.assertEqual(ProjectionExtension.ext(asset_no_prop).projjson['id']['code'], 7777) + ProjectionExtension.ext(proj_item).projjson, + ) + self.assertEqual( + ProjectionExtension.ext(asset_no_prop).projjson["id"]["code"], 7777 + ) # Validate proj_item.validate() @@ -213,27 +227,31 @@ def test_geometry(self): # Get self.assertIn("proj:geometry", proj_item.properties) proj_geometry = ProjectionExtension.ext(proj_item).geometry - self.assertEqual(proj_geometry, proj_item.properties['proj:geometry']) + self.assertEqual(proj_geometry, proj_item.properties["proj:geometry"]) # Set ProjectionExtension.ext(proj_item).geometry = proj_item.geometry - self.assertEqual(proj_item.geometry, proj_item.properties['proj:geometry']) + self.assertEqual(proj_item.geometry, proj_item.properties["proj:geometry"]) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).geometry, - ProjectionExtension.ext(proj_item).geometry) + ProjectionExtension.ext(proj_item).geometry, + ) self.assertEqual( - ProjectionExtension.ext(asset_prop).geometry['coordinates'][0][0], [0.0, 0.0]) + ProjectionExtension.ext(asset_prop).geometry["coordinates"][0][0], + [0.0, 0.0], + ) # Set to Asset - asset_value: Dict[str, Any] = {'type': 'Point', 'coordinates': [1.0, 2.0]} + asset_value: Dict[str, Any] = {"type": "Point", "coordinates": [1.0, 2.0]} ProjectionExtension.ext(asset_no_prop).geometry = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).geometry, - ProjectionExtension.ext(proj_item).geometry) + ProjectionExtension.ext(proj_item).geometry, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, asset_value) # Validate @@ -250,18 +268,19 @@ def test_bbox(self): # Get self.assertIn("proj:bbox", proj_item.properties) proj_bbox = ProjectionExtension.ext(proj_item).bbox - self.assertEqual(proj_bbox, proj_item.properties['proj:bbox']) + self.assertEqual(proj_bbox, proj_item.properties["proj:bbox"]) # Set ProjectionExtension.ext(proj_item).bbox = [1.0, 2.0, 3.0, 4.0] - self.assertEqual(proj_item.properties['proj:bbox'], [1.0, 2.0, 3.0, 4.0]) + self.assertEqual(proj_item.properties["proj:bbox"], [1.0, 2.0, 3.0, 4.0]) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).bbox, - ProjectionExtension.ext(proj_item).bbox) + ProjectionExtension.ext(proj_item).bbox, + ) self.assertEqual(ProjectionExtension.ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) # Set to Asset @@ -269,7 +288,8 @@ def test_bbox(self): ProjectionExtension.ext(asset_no_prop).bbox = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).bbox, - ProjectionExtension.ext(proj_item).bbox) + ProjectionExtension.ext(proj_item).bbox, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, asset_value) # Validate @@ -281,27 +301,31 @@ def test_centroid(self): # Get self.assertIn("proj:centroid", proj_item.properties) proj_centroid = ProjectionExtension.ext(proj_item).centroid - self.assertEqual(proj_centroid, proj_item.properties['proj:centroid']) + self.assertEqual(proj_centroid, proj_item.properties["proj:centroid"]) # Set - new_val = {'lat': 2.0, 'lon': 3.0} + new_val = {"lat": 2.0, "lon": 3.0} ProjectionExtension.ext(proj_item).centroid = new_val - self.assertEqual(proj_item.properties['proj:centroid'], new_val) + self.assertEqual(proj_item.properties["proj:centroid"], new_val) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).centroid, - ProjectionExtension.ext(proj_item).centroid) - self.assertEqual(ProjectionExtension.ext(asset_prop).centroid, {"lat": 0.5, "lon": 0.3}) + ProjectionExtension.ext(proj_item).centroid, + ) + self.assertEqual( + ProjectionExtension.ext(asset_prop).centroid, {"lat": 0.5, "lon": 0.3} + ) # Set to Asset asset_value = {"lat": 1.5, "lon": 1.3} ProjectionExtension.ext(asset_no_prop).centroid = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).centroid, - ProjectionExtension.ext(proj_item).centroid) + ProjectionExtension.ext(proj_item).centroid, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, asset_value) # Validate @@ -309,7 +333,7 @@ def test_centroid(self): # Ensure setting bad centroid fails validation with self.assertRaises(STACValidationError): - ProjectionExtension.ext(proj_item).centroid = {'lat': 2.0, 'lng': 3.0} + ProjectionExtension.ext(proj_item).centroid = {"lat": 2.0, "lng": 3.0} proj_item.validate() def test_shape(self): @@ -318,19 +342,20 @@ def test_shape(self): # Get self.assertIn("proj:shape", proj_item.properties) proj_shape = ProjectionExtension.ext(proj_item).shape - self.assertEqual(proj_shape, proj_item.properties['proj:shape']) + self.assertEqual(proj_shape, proj_item.properties["proj:shape"]) # Set new_val = [100, 200] ProjectionExtension.ext(proj_item).shape = new_val - self.assertEqual(proj_item.properties['proj:shape'], new_val) + self.assertEqual(proj_item.properties["proj:shape"], new_val) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).shape, - ProjectionExtension.ext(proj_item).shape) + ProjectionExtension.ext(proj_item).shape, + ) self.assertEqual(ProjectionExtension.ext(asset_prop).shape, [16781, 16621]) # Set to Asset @@ -338,7 +363,8 @@ def test_shape(self): ProjectionExtension.ext(asset_no_prop).shape = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).shape, - ProjectionExtension.ext(proj_item).shape) + ProjectionExtension.ext(proj_item).shape, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, asset_value) # Validate @@ -350,29 +376,32 @@ def test_transform(self): # Get self.assertIn("proj:transform", proj_item.properties) proj_transform = ProjectionExtension.ext(proj_item).transform - self.assertEqual(proj_transform, proj_item.properties['proj:transform']) + self.assertEqual(proj_transform, proj_item.properties["proj:transform"]) # Set new_val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] ProjectionExtension.ext(proj_item).transform = new_val - self.assertEqual(proj_item.properties['proj:transform'], new_val) + self.assertEqual(proj_item.properties["proj:transform"], new_val) # Get from Asset - asset_no_prop = proj_item.assets['B1'] - asset_prop = proj_item.assets['B8'] + asset_no_prop = proj_item.assets["B1"] + asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).transform, - ProjectionExtension.ext(proj_item).transform) + ProjectionExtension.ext(proj_item).transform, + ) self.assertEqual( ProjectionExtension.ext(asset_prop).transform, - [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0]) + [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0], + ) # Set to Asset asset_value = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0] ProjectionExtension.ext(asset_no_prop).transform = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).transform, - ProjectionExtension.ext(proj_item).transform) + ProjectionExtension.ext(proj_item).transform, + ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, asset_value) # Validate diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index 407a26fae..c1e14e60d 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -10,7 +10,7 @@ def make_item() -> ps.Item: - asset_id = 'my/items/2011' + asset_id = "my/items/2011" start = datetime.datetime(2020, 11, 7) item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) @@ -27,12 +27,17 @@ def test_stac_extensions(self): self.assertTrue(SarExtension.has_extension(self.item)) def test_required(self): - mode: str = 'Nonesense mode' + mode: str = "Nonesense mode" frequency_band: sar.FrequencyBand = sar.FrequencyBand.P - polarizations: List[sar.Polarization] = [sar.Polarization.HV, sar.Polarization.VH] - product_type: str = 'Some product' - - SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type) + polarizations: List[sar.Polarization] = [ + sar.Polarization.HV, + sar.Polarization.VH, + ] + product_type: str = "Some product" + + SarExtension.ext(self.item).apply( + mode, frequency_band, polarizations, product_type + ) self.assertEqual(mode, SarExtension.ext(self.item).instrument_mode) self.assertIn(sar.INSTRUMENT_MODE, self.item.properties) @@ -48,10 +53,13 @@ def test_required(self): self.item.validate() def test_all(self): - mode: str = 'WV' + mode: str = "WV" frequency_band: sar.FrequencyBand = sar.FrequencyBand.KA - polarizations: List[sar.Polarization] = [sar.Polarization.VV, sar.Polarization.HH] - product_type: str = 'Some product' + polarizations: List[sar.Polarization] = [ + sar.Polarization.VV, + sar.Polarization.HH, + ] + product_type: str = "Some product" center_frequency: float = 1.2 resolution_range: float = 3.1 resolution_azimuth: float = 4.1 @@ -62,11 +70,21 @@ def test_all(self): looks_equivalent_number: float = 9.1 observation_direction: sar.ObservationDirection = sar.ObservationDirection.LEFT - SarExtension.ext(self.item).apply(mode, frequency_band, polarizations, product_type, - center_frequency, resolution_range, resolution_azimuth, - pixel_spacing_range, pixel_spacing_azimuth, looks_range, - looks_azimuth, looks_equivalent_number, - observation_direction) + SarExtension.ext(self.item).apply( + mode, + frequency_band, + polarizations, + product_type, + center_frequency, + resolution_range, + resolution_azimuth, + pixel_spacing_range, + pixel_spacing_azimuth, + looks_range, + looks_azimuth, + looks_equivalent_number, + observation_direction, + ) self.assertEqual(center_frequency, SarExtension.ext(self.item).center_frequency) self.assertIn(sar.CENTER_FREQUENCY, self.item.properties) @@ -74,13 +92,19 @@ def test_all(self): self.assertEqual(resolution_range, SarExtension.ext(self.item).resolution_range) self.assertIn(sar.RESOLUTION_RANGE, self.item.properties) - self.assertEqual(resolution_azimuth, SarExtension.ext(self.item).resolution_azimuth) + self.assertEqual( + resolution_azimuth, SarExtension.ext(self.item).resolution_azimuth + ) self.assertIn(sar.RESOLUTION_AZIMUTH, self.item.properties) - self.assertEqual(pixel_spacing_range, SarExtension.ext(self.item).pixel_spacing_range) + self.assertEqual( + pixel_spacing_range, SarExtension.ext(self.item).pixel_spacing_range + ) self.assertIn(sar.PIXEL_SPACING_RANGE, self.item.properties) - self.assertEqual(pixel_spacing_azimuth, SarExtension.ext(self.item).pixel_spacing_azimuth) + self.assertEqual( + pixel_spacing_azimuth, SarExtension.ext(self.item).pixel_spacing_azimuth + ) self.assertIn(sar.PIXEL_SPACING_AZIMUTH, self.item.properties) self.assertEqual(looks_range, SarExtension.ext(self.item).looks_range) @@ -89,28 +113,32 @@ def test_all(self): self.assertEqual(looks_azimuth, SarExtension.ext(self.item).looks_azimuth) self.assertIn(sar.LOOKS_AZIMUTH, self.item.properties) - self.assertEqual(looks_equivalent_number, - SarExtension.ext(self.item).looks_equivalent_number) + self.assertEqual( + looks_equivalent_number, SarExtension.ext(self.item).looks_equivalent_number + ) self.assertIn(sar.LOOKS_EQUIVALENT_NUMBER, self.item.properties) - self.assertEqual(observation_direction, SarExtension.ext(self.item).observation_direction) + self.assertEqual( + observation_direction, SarExtension.ext(self.item).observation_direction + ) self.assertIn(sar.OBSERVATION_DIRECTION, self.item.properties) self.item.validate() def test_polarization_must_be_list(self): - mode: str = 'Nonesense mode' + mode: str = "Nonesense mode" frequency_band: sar.FrequencyBand = sar.FrequencyBand.P # Skip type hint as we are passing in an incorrect polarization. polarizations = sar.Polarization.HV - product_type: str = 'Some product' + product_type: str = "Some product" with self.assertRaises(ps.STACError): SarExtension.ext(self.item).apply( mode, frequency_band, polarizations, # type:ignore - product_type) + product_type, + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index acfb4daef..ef3850535 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -12,7 +12,7 @@ def make_item() -> ps.Item: """Create basic test items that are only slightly different.""" - asset_id = 'an/asset' + asset_id = "an/asset" start = datetime.datetime(2018, 1, 2) item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) @@ -78,18 +78,18 @@ def test_from_dict(self): orbit_state = sat.OrbitState.GEOSTATIONARY relative_orbit = 1001 d: Dict[str, Any] = { - 'type': 'Feature', - 'stac_version': '1.0.0-beta.2', - 'id': 'an/asset', - 'properties': { - 'sat:orbit_state': orbit_state.value, - 'sat:relative_orbit': relative_orbit, - 'datetime': '2018-01-02T00:00:00Z' + "type": "Feature", + "stac_version": "1.0.0-beta.2", + "id": "an/asset", + "properties": { + "sat:orbit_state": orbit_state.value, + "sat:relative_orbit": relative_orbit, + "datetime": "2018-01-02T00:00:00Z", }, - 'geometry': None, - 'links': [], - 'assets': {}, - 'stac_extensions': ['sat'] + "geometry": None, + "links": [], + "assets": {}, + "stac_extensions": ["sat"], } item = ps.Item.from_dict(d) self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) @@ -100,8 +100,8 @@ def test_to_from_dict(self): relative_orbit = 1002 SatExtension.ext(self.item).apply(orbit_state, relative_orbit) d = self.item.to_dict() - self.assertEqual(orbit_state.value, d['properties'][sat.ORBIT_STATE]) - self.assertEqual(relative_orbit, d['properties'][sat.RELATIVE_ORBIT]) + self.assertEqual(orbit_state.value, d["properties"][sat.ORBIT_STATE]) + self.assertEqual(relative_orbit, d["properties"][sat.RELATIVE_ORBIT]) item = ps.Item.from_dict(d) self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 0c5d43be9..da0ef22c4 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -7,28 +7,28 @@ from pystac.extensions import scientific from pystac.extensions.scientific import ScientificExtension -URL_TEMPLATE = 'http://example.com/catalog/%s.json' +URL_TEMPLATE = "http://example.com/catalog/%s.json" -DOI_BASE_URL = 'https://doi.org/' +DOI_BASE_URL = "https://doi.org/" -DOI = '10.5061/dryad.s2v81.2' +DOI = "10.5061/dryad.s2v81.2" DOI_URL = DOI_BASE_URL + DOI -CITATION = 'Some citation string' +CITATION = "Some citation string" -PUB1_DOI = '10.1234/first' +PUB1_DOI = "10.1234/first" PUB1_DOI_URL = DOI_BASE_URL + PUB1_DOI -PUB2_DOI = '10.2345/second' +PUB2_DOI = "10.2345/second" PUB2_DOI_URL = DOI_BASE_URL + PUB2_DOI PUBLICATIONS = [ - scientific.Publication(PUB1_DOI, 'First citation.'), - scientific.Publication(PUB2_DOI, 'Second citation.') + scientific.Publication(PUB1_DOI, "First citation."), + scientific.Publication(PUB2_DOI, "Second citation."), ] def make_item() -> ps.Item: - asset_id = 'USGS/GAP/CONUS/2011' + asset_id = "USGS/GAP/CONUS/2011" start = datetime.datetime(2011, 1, 2) item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) item.set_self_href(URL_TEMPLATE % 2011) @@ -77,7 +77,7 @@ def test_citation(self): def test_publications_one(self): publications = PUBLICATIONS[:1] ScientificExtension.ext(self.item).apply(publications=publications) - self.assertEqual([1], [int('1')]) + self.assertEqual([1], [int("1")]) self.assertEqual(publications, ScientificExtension.ext(self.item).publications) self.assertIn(scientific.PUBLICATIONS, self.item.properties) @@ -122,7 +122,9 @@ def test_remove_publication_forward(self): ScientificExtension.ext(self.item).apply(DOI, publications=PUBLICATIONS) ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], ScientificExtension.ext(self.item).publications) + self.assertEqual( + [PUBLICATIONS[1]], ScientificExtension.ext(self.item).publications + ) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -140,7 +142,9 @@ def test_remove_publication_reverse(self): ScientificExtension.ext(self.item).apply(DOI, publications=PUBLICATIONS) ScientificExtension.ext(self.item).remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], ScientificExtension.ext(self.item).publications) + self.assertEqual( + [PUBLICATIONS[0]], ScientificExtension.ext(self.item).publications + ) links = self.item.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) @@ -172,14 +176,14 @@ def test_remove_all_publications_with_none(self): def make_collection() -> ps.Collection: - asset_id = 'my/thing' + asset_id = "my/thing" start = datetime.datetime(2018, 8, 24) end = start + datetime.timedelta(5, 4, 3, 2, 1) bboxes = [[-180.0, -90.0, 180.0, 90.0]] spatial_extent = ps.SpatialExtent(bboxes) temporal_extent = ps.TemporalExtent([[start, end]]) extent = ps.Extent(spatial_extent, temporal_extent) - collection = ps.Collection(asset_id, 'desc', extent) + collection = ps.Collection(asset_id, "desc", extent) collection.set_self_href(URL_TEMPLATE % 2019) ScientificExtension.add_to(collection) @@ -226,7 +230,9 @@ def test_citation(self): def test_publications_one(self): publications = PUBLICATIONS[:1] ScientificExtension.ext(self.collection).apply(publications=publications) - self.assertEqual(publications, ScientificExtension.ext(self.collection).publications) + self.assertEqual( + publications, ScientificExtension.ext(self.collection).publications + ) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -238,7 +244,9 @@ def test_publications_one(self): def test_publications(self): ScientificExtension.ext(self.collection).apply(publications=PUBLICATIONS) - self.assertEqual(PUBLICATIONS, ScientificExtension.ext(self.collection).publications) + self.assertEqual( + PUBLICATIONS, ScientificExtension.ext(self.collection).publications + ) self.assertIn(scientific.PUBLICATIONS, self.collection.extra_fields) links = self.collection.get_links(scientific.CITE_AS) @@ -272,7 +280,9 @@ def test_remove_publication_forward(self): ScientificExtension.ext(self.collection).apply(DOI, publications=PUBLICATIONS) ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[0]) - self.assertEqual([PUBLICATIONS[1]], ScientificExtension.ext(self.collection).publications) + self.assertEqual( + [PUBLICATIONS[1]], ScientificExtension.ext(self.collection).publications + ) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(DOI_URL, links[0].target) @@ -290,7 +300,9 @@ def test_remove_publication_reverse(self): ScientificExtension.ext(self.collection).apply(DOI, publications=PUBLICATIONS) ScientificExtension.ext(self.collection).remove_publication(PUBLICATIONS[1]) - self.assertEqual([PUBLICATIONS[0]], ScientificExtension.ext(self.collection).publications) + self.assertEqual( + [PUBLICATIONS[0]], ScientificExtension.ext(self.collection).publications + ) links = self.collection.get_links(scientific.CITE_AS) self.assertEqual(2, len(links)) self.assertEqual(PUB1_DOI_URL, links[1].target) @@ -321,5 +333,5 @@ def test_remove_all_publications_with_none(self): self.collection.validate() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index e1327b55b..587a6b725 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -4,14 +4,16 @@ import pystac as ps from pystac.extensions.timestamps import TimestampsExtension -from pystac.utils import (get_opt, str_to_datetime, datetime_to_str) -from tests.utils import (TestCases, test_to_from_dict) +from pystac.utils import get_opt, str_to_datetime, datetime_to_str +from tests.utils import TestCases, test_to_from_dict class TimestampsTest(unittest.TestCase): def setUp(self): self.maxDiff = None - self.example_uri = TestCases.get_path('data-files/timestamps/example-landsat8.json') + self.example_uri = TestCases.get_path( + "data-files/timestamps/example-landsat8.json" + ) with open(self.example_uri) as f: self.item_dict = json.load(f) self.sample_datetime_str = "2020-01-01T00:00:00Z" @@ -26,29 +28,34 @@ def test_apply(self): TimestampsExtension.add_to(item) self.assertTrue(TimestampsExtension.has_extension(item)) - TimestampsExtension.ext(item).apply(published=str_to_datetime("2020-01-03T06:45:55Z"), - expires=str_to_datetime("2020-02-03T06:45:55Z"), - unpublished=str_to_datetime("2020-03-03T06:45:55Z")) + TimestampsExtension.ext(item).apply( + published=str_to_datetime("2020-01-03T06:45:55Z"), + expires=str_to_datetime("2020-02-03T06:45:55Z"), + unpublished=str_to_datetime("2020-03-03T06:45:55Z"), + ) for d in [ - TimestampsExtension.ext(item).published, - TimestampsExtension.ext(item).expires, - TimestampsExtension.ext(item).unpublished + TimestampsExtension.ext(item).published, + TimestampsExtension.ext(item).expires, + TimestampsExtension.ext(item).unpublished, ]: self.assertIsInstance(d, datetime) - for p in ('published', 'expires', 'unpublished'): + for p in ("published", "expires", "unpublished"): self.assertIsInstance(item.properties[p], str) published_str = "2020-04-03T06:45:55Z" TimestampsExtension.ext(item).apply(published=str_to_datetime(published_str)) self.assertIsInstance(TimestampsExtension.ext(item).published, datetime) - self.assertEqual(item.properties['published'], published_str) + self.assertEqual(item.properties["published"], published_str) - for d in [TimestampsExtension.ext(item).expires, TimestampsExtension.ext(item).unpublished]: + for d in [ + TimestampsExtension.ext(item).expires, + TimestampsExtension.ext(item).unpublished, + ]: self.assertIsNone(d) - for p in ('expires', 'unpublished'): + for p in ("expires", "unpublished"): self.assertNotIn(p, item.properties) def test_validate_timestamps(self): @@ -62,28 +69,36 @@ def test_expires(self): self.assertIn("expires", timestamps_item.properties) timestamps_expires = TimestampsExtension.ext(timestamps_item).expires self.assertIsInstance(timestamps_expires, datetime) - self.assertEqual(datetime_to_str(get_opt(timestamps_expires)), - timestamps_item.properties['expires']) + self.assertEqual( + datetime_to_str(get_opt(timestamps_expires)), + timestamps_item.properties["expires"], + ) # Set TimestampsExtension.ext(timestamps_item).expires = self.sample_datetime - self.assertEqual(self.sample_datetime_str, timestamps_item.properties['expires']) + self.assertEqual( + self.sample_datetime_str, timestamps_item.properties["expires"] + ) # Get from Asset - asset_no_prop = timestamps_item.assets['red'] - asset_prop = timestamps_item.assets['blue'] + asset_no_prop = timestamps_item.assets["red"] + asset_prop = timestamps_item.assets["blue"] self.assertEqual( TimestampsExtension.ext(asset_no_prop).expires, - TimestampsExtension.ext(timestamps_item).expires) + TimestampsExtension.ext(timestamps_item).expires, + ) self.assertEqual( - TimestampsExtension.ext(asset_prop).expires, str_to_datetime("2018-12-02T00:00:00Z")) + TimestampsExtension.ext(asset_prop).expires, + str_to_datetime("2018-12-02T00:00:00Z"), + ) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") TimestampsExtension.ext(asset_no_prop).expires = asset_value self.assertNotEqual( TimestampsExtension.ext(asset_no_prop).expires, - TimestampsExtension.ext(timestamps_item).expires) + TimestampsExtension.ext(timestamps_item).expires, + ) self.assertEqual(TimestampsExtension.ext(asset_no_prop).expires, asset_value) # Validate @@ -96,28 +111,36 @@ def test_published(self): self.assertIn("published", timestamps_item.properties) timestamps_published = TimestampsExtension.ext(timestamps_item).published self.assertIsInstance(timestamps_published, datetime) - self.assertEqual(datetime_to_str(get_opt(timestamps_published)), - timestamps_item.properties['published']) + self.assertEqual( + datetime_to_str(get_opt(timestamps_published)), + timestamps_item.properties["published"], + ) # Set TimestampsExtension.ext(timestamps_item).published = self.sample_datetime - self.assertEqual(self.sample_datetime_str, timestamps_item.properties['published']) + self.assertEqual( + self.sample_datetime_str, timestamps_item.properties["published"] + ) # Get from Asset - asset_no_prop = timestamps_item.assets['red'] - asset_prop = timestamps_item.assets['blue'] + asset_no_prop = timestamps_item.assets["red"] + asset_prop = timestamps_item.assets["blue"] self.assertEqual( TimestampsExtension.ext(asset_no_prop).published, - TimestampsExtension.ext(timestamps_item).published) + TimestampsExtension.ext(timestamps_item).published, + ) self.assertEqual( - TimestampsExtension.ext(asset_prop).published, str_to_datetime("2018-11-02T00:00:00Z")) + TimestampsExtension.ext(asset_prop).published, + str_to_datetime("2018-11-02T00:00:00Z"), + ) # # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") TimestampsExtension.ext(asset_no_prop).published = asset_value self.assertNotEqual( TimestampsExtension.ext(asset_no_prop).published, - TimestampsExtension.ext(timestamps_item).published) + TimestampsExtension.ext(timestamps_item).published, + ) self.assertEqual(TimestampsExtension.ext(asset_no_prop).published, asset_value) # Validate @@ -133,25 +156,32 @@ def test_unpublished(self): # Set TimestampsExtension.ext(timestamps_item).unpublished = self.sample_datetime - self.assertEqual(self.sample_datetime_str, timestamps_item.properties['unpublished']) + self.assertEqual( + self.sample_datetime_str, timestamps_item.properties["unpublished"] + ) # Get from Asset - asset_no_prop = timestamps_item.assets['red'] - asset_prop = timestamps_item.assets['blue'] + asset_no_prop = timestamps_item.assets["red"] + asset_prop = timestamps_item.assets["blue"] self.assertEqual( TimestampsExtension.ext(asset_no_prop).unpublished, - TimestampsExtension.ext(timestamps_item).unpublished) + TimestampsExtension.ext(timestamps_item).unpublished, + ) self.assertEqual( TimestampsExtension.ext(asset_prop).unpublished, - str_to_datetime("2019-01-02T00:00:00Z")) + str_to_datetime("2019-01-02T00:00:00Z"), + ) # Set to Asset asset_value = str_to_datetime("2019-02-02T00:00:00Z") TimestampsExtension.ext(asset_no_prop).unpublished = asset_value self.assertNotEqual( TimestampsExtension.ext(asset_no_prop).unpublished, - TimestampsExtension.ext(timestamps_item).unpublished) - self.assertEqual(TimestampsExtension.ext(asset_no_prop).unpublished, asset_value) + TimestampsExtension.ext(timestamps_item).unpublished, + ) + self.assertEqual( + TimestampsExtension.ext(asset_no_prop).unpublished, asset_value + ) # Validate timestamps_item.validate() diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index e8e799229..30d865a0e 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -9,12 +9,12 @@ from pystac.extensions.version import VersionExtension from tests.utils import TestCases -URL_TEMPLATE: str = 'http://example.com/catalog/%s.json' +URL_TEMPLATE: str = "http://example.com/catalog/%s.json" def make_item(year: int) -> ps.Item: """Create basic test items that are only slightly different.""" - asset_id = f'USGS/GAP/CONUS/{year}' + asset_id = f"USGS/GAP/CONUS/{year}" start = datetime.datetime(year, 1, 2) item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) @@ -26,7 +26,7 @@ def make_item(year: int) -> ps.Item: class ItemVersionExtensionTest(unittest.TestCase): - version: str = '1.2.3' + version: str = "1.2.3" def setUp(self): super().setUp() @@ -105,16 +105,17 @@ def test_all_links(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, - successor) + VersionExtension.ext(self.item).apply( + self.version, deprecated, latest, predecessor, successor + ) self.item.validate() def test_full_copy(self): cat = TestCases.test_case_1() # Fetch two items from the catalog - item1 = cat.get_item('area-1-1-imagery', recursive=True) - item2 = cat.get_item('area-2-2-imagery', recursive=True) + item1 = cat.get_item("area-1-1-imagery", recursive=True) + item2 = cat.get_item("area-2-2-imagery", recursive=True) assert item1 is not None assert item2 is not None @@ -124,15 +125,15 @@ def test_full_copy(self): VersionExtension.add_to(item1) VersionExtension.add_to(item2) - VersionExtension.ext(item1).apply(version='2.0', predecessor=item2) - VersionExtension.ext(item2).apply(version='1.0', successor=item1, latest=item1) + VersionExtension.ext(item1).apply(version="2.0", predecessor=item2) + VersionExtension.ext(item2).apply(version="1.0", successor=item1, latest=item1) # Make a full copy of the catalog cat_copy = cat.full_copy() # Retrieve the copied version of the items - item1_copy = cat_copy.get_item('area-1-1-imagery', recursive=True) - item2_copy = cat_copy.get_item('area-2-2-imagery', recursive=True) + item1_copy = cat_copy.get_item("area-1-1-imagery", recursive=True) + item2_copy = cat_copy.get_item("area-2-2-imagery", recursive=True) # Check to see if the version links point to the instances of the # item objects as they should. @@ -149,8 +150,9 @@ def test_setting_none_clears_link(self): latest = make_item(2013) predecessor = make_item(2010) successor = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest, predecessor, - successor) + VersionExtension.ext(self.item).apply( + self.version, deprecated, latest, predecessor, successor + ) VersionExtension.ext(self.item).latest = None links = self.item.get_links(version.LATEST) @@ -172,8 +174,9 @@ def test_multiple_link_setting(self): latest1 = make_item(2013) predecessor1 = make_item(2010) successor1 = make_item(2012) - VersionExtension.ext(self.item).apply(self.version, deprecated, latest1, predecessor1, - successor1) + VersionExtension.ext(self.item).apply( + self.version, deprecated, latest1, predecessor1, successor1 + ) year = 2015 latest2 = make_item(year) @@ -201,7 +204,7 @@ def test_multiple_link_setting(self): def make_collection(year: int) -> ps.Collection: - asset_id = f'my/collection/of/things/{year}' + asset_id = f"my/collection/of/things/{year}" start = datetime.datetime(2014, 8, 10) end = datetime.datetime(year, 1, 3, 4, 5) bboxes = [[-180.0, -90.0, 180.0, 90.0]] @@ -209,7 +212,7 @@ def make_collection(year: int) -> ps.Collection: temporal_extent = ps.TemporalExtent([[start, end]]) extent = ps.Extent(spatial_extent, temporal_extent) - collection = ps.Collection(asset_id, 'desc', extent) + collection = ps.Collection(asset_id, "desc", extent) collection.set_self_href(URL_TEMPLATE % year) VersionExtension.add_to(collection) @@ -218,7 +221,7 @@ def make_collection(year: int) -> ps.Collection: class CollectionVersionExtensionTest(unittest.TestCase): - version: str = '1.2.3' + version: str = "1.2.3" def setUp(self): super().setUp() @@ -267,7 +270,9 @@ def test_latest(self): def test_predecessor(self): year = 2010 predecessor = make_collection(year) - VersionExtension.ext(self.collection).apply(self.version, predecessor=predecessor) + VersionExtension.ext(self.collection).apply( + self.version, predecessor=predecessor + ) predecessor_result = VersionExtension.ext(self.collection).predecessor self.assertIs(predecessor, predecessor_result) @@ -297,17 +302,18 @@ def test_validate_all(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, - successor) + VersionExtension.ext(self.collection).apply( + self.version, deprecated, latest, predecessor, successor + ) self.collection.validate() def test_full_copy(self): cat = TestCases.test_case_1() # Fetch two collections from the catalog - col1 = cat.get_child('area-1-1', recursive=True) + col1 = cat.get_child("area-1-1", recursive=True) assert isinstance(col1, ps.Collection) - col2 = cat.get_child('area-2-2', recursive=True) + col2 = cat.get_child("area-2-2", recursive=True) assert isinstance(col2, ps.Collection) # Enable the version extension on each, and link them @@ -315,15 +321,15 @@ def test_full_copy(self): VersionExtension.add_to(col1) VersionExtension.add_to(col2) - VersionExtension.ext(col1).apply(version='2.0', predecessor=col2) - VersionExtension.ext(col2).apply(version='1.0', successor=col1, latest=col1) + VersionExtension.ext(col1).apply(version="2.0", predecessor=col2) + VersionExtension.ext(col2).apply(version="1.0", successor=col1, latest=col1) # Make a full copy of the catalog cat_copy = cat.full_copy() # Retrieve the copied version of the items - col1_copy = cat_copy.get_child('area-1-1', recursive=True) - col2_copy = cat_copy.get_child('area-2-2', recursive=True) + col1_copy = cat_copy.get_child("area-1-1", recursive=True) + col2_copy = cat_copy.get_child("area-2-2", recursive=True) # Check to see if the version links point to the instances of the # col objects as they should. @@ -340,8 +346,9 @@ def test_setting_none_clears_link(self): latest = make_collection(2013) predecessor = make_collection(2010) successor = make_collection(2012) - VersionExtension.ext(self.collection).apply(self.version, deprecated, latest, predecessor, - successor) + VersionExtension.ext(self.collection).apply( + self.version, deprecated, latest, predecessor, successor + ) VersionExtension.ext(self.collection).latest = None links = self.collection.get_links(version.LATEST) @@ -363,8 +370,9 @@ def test_multiple_link_setting(self): latest1 = make_collection(2013) predecessor1 = make_collection(2010) successor1 = make_collection(2012) - VersionExtension.ext(self.collection).apply(self.version, deprecated, latest1, predecessor1, - successor1) + VersionExtension.ext(self.collection).apply( + self.version, deprecated, latest1, predecessor1, successor1 + ) year = 2015 latest2 = make_collection(year) @@ -391,5 +399,5 @@ def test_multiple_link_setting(self): self.assertEqual(expected_href, links[0].get_href()) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 0b721615b..1580dfbaf 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -3,13 +3,13 @@ import pystac as ps from pystac.extensions.view import ViewExtension -from tests.utils import (TestCases, test_to_from_dict) +from tests.utils import TestCases, test_to_from_dict class ViewTest(unittest.TestCase): def setUp(self): self.maxDiff = None - self.example_uri = TestCases.get_path('data-files/view/example-landsat8.json') + self.example_uri = TestCases.get_path("data-files/view/example-landsat8.json") def test_to_from_dict(self): with open(self.example_uri) as f: @@ -21,11 +21,13 @@ def test_apply(self): self.assertFalse(ViewExtension.has_extension(item)) ViewExtension.add_to(item) - ViewExtension.ext(item).apply(off_nadir=1.0, - incidence_angle=2.0, - azimuth=3.0, - sun_azimuth=4.0, - sun_elevation=5.0) + ViewExtension.ext(item).apply( + off_nadir=1.0, + incidence_angle=2.0, + azimuth=3.0, + sun_azimuth=4.0, + sun_elevation=5.0, + ) self.assertEqual(ViewExtension.ext(item).off_nadir, 1.0) self.assertEqual(ViewExtension.ext(item).incidence_angle, 2.0) @@ -44,18 +46,19 @@ def test_off_nadir(self): # Get self.assertIn("view:off_nadir", view_item.properties) view_off_nadir = ViewExtension.ext(view_item).off_nadir - self.assertEqual(view_off_nadir, view_item.properties['view:off_nadir']) + self.assertEqual(view_off_nadir, view_item.properties["view:off_nadir"]) # Set ViewExtension.ext(view_item).off_nadir = view_off_nadir + 10 - self.assertEqual(view_off_nadir + 10, view_item.properties['view:off_nadir']) + self.assertEqual(view_off_nadir + 10, view_item.properties["view:off_nadir"]) # Get from Asset - asset_no_prop = view_item.assets['blue'] - asset_prop = view_item.assets['red'] + asset_no_prop = view_item.assets["blue"] + asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).off_nadir, - ViewExtension.ext(view_item).off_nadir) + ViewExtension.ext(view_item).off_nadir, + ) self.assertEqual(ViewExtension.ext(asset_prop).off_nadir, 3.0) # Set to Asset @@ -63,7 +66,8 @@ def test_off_nadir(self): ViewExtension.ext(asset_no_prop).off_nadir = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).off_nadir, - ViewExtension.ext(view_item).off_nadir) + ViewExtension.ext(view_item).off_nadir, + ) self.assertEqual(ViewExtension.ext(asset_no_prop).off_nadir, asset_value) # Validate @@ -75,18 +79,23 @@ def test_incidence_angle(self): # Get self.assertIn("view:incidence_angle", view_item.properties) view_incidence_angle = ViewExtension.ext(view_item).incidence_angle - self.assertEqual(view_incidence_angle, view_item.properties['view:incidence_angle']) + self.assertEqual( + view_incidence_angle, view_item.properties["view:incidence_angle"] + ) # Set ViewExtension.ext(view_item).incidence_angle = view_incidence_angle + 10 - self.assertEqual(view_incidence_angle + 10, view_item.properties['view:incidence_angle']) + self.assertEqual( + view_incidence_angle + 10, view_item.properties["view:incidence_angle"] + ) # Get from Asset - asset_no_prop = view_item.assets['blue'] - asset_prop = view_item.assets['red'] + asset_no_prop = view_item.assets["blue"] + asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).incidence_angle, - ViewExtension.ext(view_item).incidence_angle) + ViewExtension.ext(view_item).incidence_angle, + ) self.assertEqual(ViewExtension.ext(asset_prop).incidence_angle, 4.0) # Set to Asset @@ -94,7 +103,8 @@ def test_incidence_angle(self): ViewExtension.ext(asset_no_prop).incidence_angle = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).incidence_angle, - ViewExtension.ext(view_item).incidence_angle) + ViewExtension.ext(view_item).incidence_angle, + ) self.assertEqual(ViewExtension.ext(asset_no_prop).incidence_angle, asset_value) # Validate @@ -106,18 +116,19 @@ def test_azimuth(self): # Get self.assertIn("view:azimuth", view_item.properties) view_azimuth = ViewExtension.ext(view_item).azimuth - self.assertEqual(view_azimuth, view_item.properties['view:azimuth']) + self.assertEqual(view_azimuth, view_item.properties["view:azimuth"]) # Set ViewExtension.ext(view_item).azimuth = view_azimuth + 100 - self.assertEqual(view_azimuth + 100, view_item.properties['view:azimuth']) + self.assertEqual(view_azimuth + 100, view_item.properties["view:azimuth"]) # Get from Asset - asset_no_prop = view_item.assets['blue'] - asset_prop = view_item.assets['red'] + asset_no_prop = view_item.assets["blue"] + asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).azimuth, - ViewExtension.ext(view_item).azimuth) + ViewExtension.ext(view_item).azimuth, + ) self.assertEqual(ViewExtension.ext(asset_prop).azimuth, 5.0) # Set to Asset @@ -125,7 +136,8 @@ def test_azimuth(self): ViewExtension.ext(asset_no_prop).azimuth = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).azimuth, - ViewExtension.ext(view_item).azimuth) + ViewExtension.ext(view_item).azimuth, + ) self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, asset_value) # Validate @@ -137,18 +149,21 @@ def test_sun_azimuth(self): # Get self.assertIn("view:sun_azimuth", view_item.properties) view_sun_azimuth = ViewExtension.ext(view_item).sun_azimuth - self.assertEqual(view_sun_azimuth, view_item.properties['view:sun_azimuth']) + self.assertEqual(view_sun_azimuth, view_item.properties["view:sun_azimuth"]) # Set ViewExtension.ext(view_item).sun_azimuth = view_sun_azimuth + 100 - self.assertEqual(view_sun_azimuth + 100, view_item.properties['view:sun_azimuth']) + self.assertEqual( + view_sun_azimuth + 100, view_item.properties["view:sun_azimuth"] + ) # Get from Asset - asset_no_prop = view_item.assets['blue'] - asset_prop = view_item.assets['red'] + asset_no_prop = view_item.assets["blue"] + asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).sun_azimuth, - ViewExtension.ext(view_item).sun_azimuth) + ViewExtension.ext(view_item).sun_azimuth, + ) self.assertEqual(ViewExtension.ext(asset_prop).sun_azimuth, 1.0) # Set to Asset @@ -156,7 +171,8 @@ def test_sun_azimuth(self): ViewExtension.ext(asset_no_prop).sun_azimuth = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).sun_azimuth, - ViewExtension.ext(view_item).sun_azimuth) + ViewExtension.ext(view_item).sun_azimuth, + ) self.assertEqual(ViewExtension.ext(asset_no_prop).sun_azimuth, asset_value) # Validate @@ -168,18 +184,21 @@ def test_sun_elevation(self): # Get self.assertIn("view:sun_elevation", view_item.properties) view_sun_elevation = ViewExtension.ext(view_item).sun_elevation - self.assertEqual(view_sun_elevation, view_item.properties['view:sun_elevation']) + self.assertEqual(view_sun_elevation, view_item.properties["view:sun_elevation"]) # Set ViewExtension.ext(view_item).sun_elevation = view_sun_elevation + 10 - self.assertEqual(view_sun_elevation + 10, view_item.properties['view:sun_elevation']) + self.assertEqual( + view_sun_elevation + 10, view_item.properties["view:sun_elevation"] + ) # Get from Asset - asset_no_prop = view_item.assets['blue'] - asset_prop = view_item.assets['red'] + asset_no_prop = view_item.assets["blue"] + asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).sun_elevation, - ViewExtension.ext(view_item).sun_elevation) + ViewExtension.ext(view_item).sun_elevation, + ) self.assertEqual(ViewExtension.ext(asset_prop).sun_elevation, 2.0) # Set to Asset @@ -187,7 +206,8 @@ def test_sun_elevation(self): ViewExtension.ext(asset_no_prop).sun_elevation = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).sun_elevation, - ViewExtension.ext(view_item).sun_elevation) + ViewExtension.ext(view_item).sun_elevation, + ) self.assertEqual(ViewExtension.ext(asset_no_prop).sun_elevation, asset_value) # Validate diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index 8012ae549..0adff5ba4 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -3,9 +3,12 @@ import pystac as ps from pystac.cache import CollectionCache -from pystac.serialization import (identify_stac_object, identify_stac_object_type, - merge_common_properties) -from pystac.serialization.identify import (STACVersionRange, STACVersionID) +from pystac.serialization import ( + identify_stac_object, + identify_stac_object_type, + merge_common_properties, +) +from pystac.serialization.identify import STACVersionRange, STACVersionID from tests.utils import TestCases @@ -22,9 +25,9 @@ def test_identify(self): d = ps.StacIO.default().read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: try: - merge_common_properties(d, - json_href=path, - collection_cache=collection_cache) + merge_common_properties( + d, json_href=path, collection_cache=collection_cache + ) except HTTPError: pass @@ -33,37 +36,43 @@ def test_identify(self): str_info = str(actual) self.assertIsInstance(str_info, str) - msg = 'Failed {}:'.format(path) + msg = "Failed {}:".format(path) self.assertEqual(actual.object_type, example.object_type, msg=msg) - version_contained_in_range = actual.version_range.contains(example.stac_version) + version_contained_in_range = actual.version_range.contains( + example.stac_version + ) self.assertTrue(version_contained_in_range, msg=msg) - self.assertEqual(set(actual.extensions), set(example.extensions), msg=msg) + self.assertEqual( + set(actual.extensions), set(example.extensions), msg=msg + ) class VersionTest(unittest.TestCase): def test_version_ordering(self): - self.assertEqual(STACVersionID('0.9.0'), STACVersionID('0.9.0')) - self.assertFalse(STACVersionID('0.9.0') < STACVersionID('0.9.0')) - self.assertFalse(STACVersionID('0.9.0') != STACVersionID('0.9.0')) - self.assertFalse(STACVersionID('0.9.0') > STACVersionID('0.9.0')) - self.assertTrue(STACVersionID('1.0.0-beta.2') < '1.0.0') - self.assertTrue(STACVersionID('0.9.1') > '0.9.0') # type:ignore - self.assertFalse(STACVersionID('0.9.0') > '0.9.0') # type:ignore - self.assertTrue(STACVersionID('0.9.0') <= '0.9.0') # type:ignore + self.assertEqual(STACVersionID("0.9.0"), STACVersionID("0.9.0")) + self.assertFalse(STACVersionID("0.9.0") < STACVersionID("0.9.0")) + self.assertFalse(STACVersionID("0.9.0") != STACVersionID("0.9.0")) + self.assertFalse(STACVersionID("0.9.0") > STACVersionID("0.9.0")) + self.assertTrue(STACVersionID("1.0.0-beta.2") < "1.0.0") + self.assertTrue(STACVersionID("0.9.1") > "0.9.0") # type:ignore + self.assertFalse(STACVersionID("0.9.0") > "0.9.0") # type:ignore + self.assertTrue(STACVersionID("0.9.0") <= "0.9.0") # type:ignore self.assertTrue( - STACVersionID('1.0.0-beta.1') <= STACVersionID('1.0.0-beta.2')) # type:ignore - self.assertFalse(STACVersionID('1.0.0') < STACVersionID('1.0.0-beta.2')) + STACVersionID("1.0.0-beta.1") + <= STACVersionID("1.0.0-beta.2") # type:ignore + ) + self.assertFalse(STACVersionID("1.0.0") < STACVersionID("1.0.0-beta.2")) def test_version_range_ordering(self): - version_range = STACVersionRange('0.9.0', '1.0.0-beta.2') + version_range = STACVersionRange("0.9.0", "1.0.0-beta.2") self.assertIsInstance(str(version_range), str) - self.assertTrue(version_range.contains('1.0.0-beta.1')) - self.assertFalse(version_range.contains('1.0.0')) - self.assertTrue(version_range.is_later_than('0.8.9')) + self.assertTrue(version_range.contains("1.0.0-beta.1")) + self.assertFalse(version_range.contains("1.0.0")) + self.assertTrue(version_range.is_later_than("0.8.9")) - version_range = STACVersionRange('0.9.0', '1.0.0-beta.1') - self.assertFalse(version_range.contains('1.0.0-beta.2')) + version_range = STACVersionRange("0.9.0", "1.0.0-beta.1") + self.assertFalse(version_range.contains("1.0.0-beta.2")) - version_range = STACVersionRange(min_version='0.6.0-rc1', max_version='0.9.0') - self.assertTrue(version_range.contains('0.9.0')) + version_range = STACVersionRange(min_version="0.6.0-rc1", max_version="0.9.0") + self.assertTrue(version_range.contains("0.9.0")) diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index 9c827579c..777168161 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -4,8 +4,12 @@ import pystac as ps from pystac.cache import CollectionCache -from pystac.serialization import (identify_stac_object, identify_stac_object_type, - merge_common_properties, migrate_to_latest) +from pystac.serialization import ( + identify_stac_object, + identify_stac_object_type, + merge_common_properties, + migrate_to_latest, +) from pystac.utils import str_to_datetime from tests.utils import TestCases @@ -23,7 +27,9 @@ def test_migrate(self): d = ps.StacIO.default().read_json(path) if identify_stac_object_type(d) == ps.STACObjectType.ITEM: - merge_common_properties(d, json_href=path, collection_cache=collection_cache) + merge_common_properties( + d, json_href=path, collection_cache=collection_cache + ) info = identify_stac_object(d) @@ -32,29 +38,41 @@ def test_migrate(self): migrated_info = identify_stac_object(migrated_d) self.assertEqual(migrated_info.object_type, info.object_type) - self.assertEqual(migrated_info.version_range.latest_valid_version(), - ps.get_stac_version()) + self.assertEqual( + migrated_info.version_range.latest_valid_version(), + ps.get_stac_version(), + ) # Ensure all stac_extensions are schema URIs - for e_id in migrated_d['stac_extensions']: - self.assertTrue(e_id.endswith('.json'), f"{e_id} is not a JSON schema URI") + for e_id in migrated_d["stac_extensions"]: + self.assertTrue( + e_id.endswith(".json"), f"{e_id} is not a JSON schema URI" + ) # Test that PySTAC can read it without errors. if info.object_type != ps.STACObjectType.ITEMCOLLECTION: - self.assertIsInstance(ps.read_dict(migrated_d, href=path), ps.STACObject) + self.assertIsInstance( + ps.read_dict(migrated_d, href=path), ps.STACObject + ) def test_migrates_removed_extension(self): item = ps.Item.from_file( - TestCases.get_path('data-files/examples/0.7.0/extensions/sar/' - 'examples/sentinel1.json')) - self.assertFalse('dtr' in item.stac_extensions) - self.assertEqual(item.common_metadata.start_datetime, - str_to_datetime("2018-11-03T23:58:55.121559Z")) + TestCases.get_path( + "data-files/examples/0.7.0/extensions/sar/" "examples/sentinel1.json" + ) + ) + self.assertFalse("dtr" in item.stac_extensions) + self.assertEqual( + item.common_metadata.start_datetime, + str_to_datetime("2018-11-03T23:58:55.121559Z"), + ) def test_migrates_added_extension(self): item = ps.Item.from_file( - TestCases.get_path('data-files/examples/0.8.1/item-spec/' - 'examples/planet-sample.json')) + TestCases.get_path( + "data-files/examples/0.8.1/item-spec/" "examples/planet-sample.json" + ) + ) self.assertTrue(ViewExtension.has_extension(item)) view_ext = ViewExtension.ext(item) self.assertEqual(view_ext.sun_azimuth, 101.8) @@ -63,8 +81,11 @@ def test_migrates_added_extension(self): def test_migrates_renamed_extension(self): collection = ps.Collection.from_file( - TestCases.get_path('data-files/examples/0.9.0/extensions/asset/' - 'examples/example-landsat8.json')) + TestCases.get_path( + "data-files/examples/0.9.0/extensions/asset/" + "examples/example-landsat8.json" + ) + ) self.assertTrue(ItemAssetsExtension.has_extension(collection)) - self.assertIn('item_assets', collection.extra_fields) + self.assertIn("item_assets", collection.extra_fields) diff --git a/tests/test_cache.py b/tests/test_cache.py index d4c7abb31..ac2430c9d 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -3,15 +3,20 @@ import unittest import pystac as ps -from pystac.cache import (ResolvedObjectCache, ResolvedObjectCollectionCache) +from pystac.cache import ResolvedObjectCache, ResolvedObjectCollectionCache from tests.utils import TestCases def create_catalog(suffix: Any, include_href: bool = True) -> ps.Catalog: return ps.Catalog( - id='test {}'.format(suffix), - description='test desc {}'.format(suffix), - href=('http://example.com/catalog_{}.json'.format(suffix) if include_href else None)) + id="test {}".format(suffix), + description="test desc {}".format(suffix), + href=( + "http://example.com/catalog_{}.json".format(suffix) + if include_href + else None + ), + ) class ResolvedObjectCacheTest(unittest.TestCase): @@ -51,21 +56,27 @@ def test_merge(self): cached_ids_2: Dict[str, Any] = {cat3.id: cat3, cat1.id: identical_cat1} cached_hrefs_2: Dict[str, Any] = { get_opt(cat4.get_self_href()): cat4, - get_opt(cat2.get_self_href()): identical_cat2 + get_opt(cat2.get_self_href()): identical_cat2, } - cache1 = ResolvedObjectCollectionCache(ResolvedObjectCache(), - cached_ids=cached_ids_1, - cached_hrefs=cached_hrefs_1) - cache2 = ResolvedObjectCollectionCache(ResolvedObjectCache(), - cached_ids=cached_ids_2, - cached_hrefs=cached_hrefs_2) + cache1 = ResolvedObjectCollectionCache( + ResolvedObjectCache(), cached_ids=cached_ids_1, cached_hrefs=cached_hrefs_1 + ) + cache2 = ResolvedObjectCollectionCache( + ResolvedObjectCache(), cached_ids=cached_ids_2, cached_hrefs=cached_hrefs_2 + ) - merged = ResolvedObjectCollectionCache.merge(ResolvedObjectCache(), cache1, cache2) + merged = ResolvedObjectCollectionCache.merge( + ResolvedObjectCache(), cache1, cache2 + ) - self.assertEqual(set(merged.cached_ids.keys()), set([cat.id for cat in [cat1, cat3]])) + self.assertEqual( + set(merged.cached_ids.keys()), set([cat.id for cat in [cat1, cat3]]) + ) self.assertIs(merged.get_by_id(cat1.id), cat1) - self.assertEqual(set(merged.cached_hrefs.keys()), - set([cat.get_self_href() for cat in [cat2, cat4]])) + self.assertEqual( + set(merged.cached_hrefs.keys()), + set([cat.get_self_href() for cat in [cat2, cat4]]), + ) self.assertIs(merged.get_by_href(get_opt(cat2.get_self_href())), cat2) def test_cache(self): @@ -75,4 +86,4 @@ def test_cache(self): cache.cache(collection_json, collection.get_self_href()) cached = cache.get_by_id(collection.id) assert isinstance(cached, dict) - self.assertEqual(cached['id'], collection.id) + self.assertEqual(cached["id"], collection.id) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 56230bb75..34ba375ec 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,11 +7,19 @@ from collections import defaultdict import pystac as ps -from pystac import (Catalog, Collection, CatalogType, Item, Asset, MediaType, HIERARCHICAL_LINKS) +from pystac import ( + Catalog, + Collection, + CatalogType, + Item, + Asset, + MediaType, + HIERARCHICAL_LINKS, +) from pystac.extensions.label import LabelClasses, LabelExtension, LabelType from pystac.validation import STACValidationError from pystac.utils import is_absolute_href -from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, MockStacIO) +from tests.utils import TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, MockStacIO class CatalogTypeTest(unittest.TestCase): @@ -19,7 +27,9 @@ def test_determine_type_for_absolute_published(self): cat = TestCases.test_case_1() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat_json = ps.StacIO.default().read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.StacIO.default().read_json( + os.path.join(tmp_dir, "catalog.json") + ) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.ABSOLUTE_PUBLISHED) @@ -28,22 +38,25 @@ def test_determine_type_for_relative_published(self): cat = TestCases.test_case_2() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) - cat_json = ps.StacIO.default().read_json(os.path.join(tmp_dir, 'catalog.json')) + cat_json = ps.StacIO.default().read_json( + os.path.join(tmp_dir, "catalog.json") + ) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.RELATIVE_PUBLISHED) def test_determine_type_for_self_contained(self): cat_json = ps.StacIO.default().read_json( - TestCases.get_path('data-files/catalogs/test-case-1/catalog.json')) + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) catalog_type = CatalogType.determine_type(cat_json) self.assertEqual(catalog_type, CatalogType.SELF_CONTAINED) def test_determine_type_for_unknown(self): - catalog = Catalog(id='test', description='test desc') - subcat = Catalog(id='subcat', description='subcat desc') + catalog = Catalog(id="test", description="test desc") + subcat = Catalog(id="subcat", description="subcat desc") catalog.add_child(subcat) - catalog.normalize_hrefs('http://example.com') + catalog.normalize_hrefs("http://example.com") d = catalog.to_dict(include_self_link=False) self.assertIsNone(CatalogType.determine_type(d)) @@ -52,12 +65,14 @@ def test_determine_type_for_unknown(self): class CatalogTest(unittest.TestCase): def test_create_and_read(self): with TemporaryDirectory() as tmp_dir: - cat_dir = os.path.join(tmp_dir, 'catalog') + cat_dir = os.path.join(tmp_dir, "catalog") catalog = TestCases.test_case_1() - catalog.normalize_and_save(cat_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + catalog.normalize_and_save( + cat_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED + ) - read_catalog = Catalog.from_file('{}/catalog.json'.format(cat_dir)) + read_catalog = Catalog.from_file("{}/catalog.json".format(cat_dir)) collections = catalog.get_children() self.assertEqual(len(list(collections)), 2) @@ -69,82 +84,89 @@ def test_create_and_read(self): def test_read_remote(self): # TODO: Move this URL to the main stac-spec repo once the example JSON is fixed. catalog_url = ( - 'https://raw.githubusercontent.com/lossyrob/stac-spec/0.9.0/pystac-upgrade-fixes' - '/extensions/label/examples/multidataset/catalog.json') + "https://raw.githubusercontent.com/lossyrob/stac-spec/0.9.0/pystac-upgrade-fixes" + "/extensions/label/examples/multidataset/catalog.json" + ) cat = Catalog.from_file(catalog_url) - zanzibar = cat.get_child('zanzibar-collection') + zanzibar = cat.get_child("zanzibar-collection") self.assertEqual(len(list(zanzibar.get_items())), 2) def test_clear_items_removes_from_cache(self): - catalog = Catalog(id='test', description='test') - subcat = Catalog(id='subcat', description='test') + catalog = Catalog(id="test", description="test") + subcat = Catalog(id="subcat", description="test") catalog.add_child(subcat) - item = Item(id='test-item', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={'key': 'one'}) + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={"key": "one"}, + ) subcat.add_item(item) items = list(catalog.get_all_items()) self.assertEqual(len(items), 1) - self.assertEqual(items[0].properties['key'], 'one') + self.assertEqual(items[0].properties["key"], "one") subcat.clear_items() - item = Item(id='test-item', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={'key': 'two'}) + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={"key": "two"}, + ) subcat.add_item(item) items = list(catalog.get_all_items()) self.assertEqual(len(items), 1) - self.assertEqual(items[0].properties['key'], 'two') - - subcat.remove_item('test-item') - item = Item(id='test-item', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={'key': 'three'}) + self.assertEqual(items[0].properties["key"], "two") + + subcat.remove_item("test-item") + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={"key": "three"}, + ) subcat.add_item(item) items = list(catalog.get_all_items()) self.assertEqual(len(items), 1) - self.assertEqual(items[0].properties['key'], 'three') + self.assertEqual(items[0].properties["key"], "three") def test_clear_children_removes_from_cache(self): - catalog = Catalog(id='test', description='test') - subcat = Catalog(id='subcat', description='test') + catalog = Catalog(id="test", description="test") + subcat = Catalog(id="subcat", description="test") catalog.add_child(subcat) children = list(catalog.get_children()) self.assertEqual(len(children), 1) - self.assertEqual(children[0].description, 'test') + self.assertEqual(children[0].description, "test") catalog.clear_children() - subcat = Catalog(id='subcat', description='test2') + subcat = Catalog(id="subcat", description="test2") catalog.add_child(subcat) children = list(catalog.get_children()) self.assertEqual(len(children), 1) - self.assertEqual(children[0].description, 'test2') + self.assertEqual(children[0].description, "test2") - catalog.remove_child('subcat') - subcat = Catalog(id='subcat', description='test3') + catalog.remove_child("subcat") + subcat = Catalog(id="subcat", description="test3") catalog.add_child(subcat) children = list(catalog.get_children()) self.assertEqual(len(children), 1) - self.assertEqual(children[0].description, 'test3') + self.assertEqual(children[0].description, "test3") def test_clear_children_sets_parent_and_root_to_None(self): - catalog = Catalog(id='test', description='test') - subcat1 = Catalog(id='subcat', description='test') - subcat2 = Catalog(id='subcat2', description='test2') + catalog = Catalog(id="test", description="test") + subcat1 = Catalog(id="subcat", description="test") + subcat2 = Catalog(id="subcat2", description="test2") catalog.add_children([subcat1, subcat2]) self.assertIsNotNone(subcat1.get_parent()) @@ -176,12 +198,12 @@ def test_add_item_throws_if_child(self): def test_get_child_returns_none_if_not_found(self): cat = TestCases.test_case_1() - child = cat.get_child('thisshouldnotbeachildid', recursive=True) + child = cat.get_child("thisshouldnotbeachildid", recursive=True) self.assertIsNone(child) def test_get_item_returns_none_if_not_found(self): cat = TestCases.test_case_1() - item = cat.get_item('thisshouldnotbeanitemid', recursive=True) + item = cat.get_item("thisshouldnotbeanitemid", recursive=True) self.assertIsNone(item) def test_sets_catalog_type(self): @@ -193,15 +215,21 @@ def test_walk_iterates_correctly(self): def test_catalog(cat: Catalog): expected_catalog_iterations = 1 actual_catalog_iterations = 0 - with self.subTest(title='Testing catalog {}'.format(cat.id)): + with self.subTest(title="Testing catalog {}".format(cat.id)): for root, children, items in cat.walk(): actual_catalog_iterations += 1 expected_catalog_iterations += len(list(root.get_children())) - self.assertEqual(set([c.id for c in root.get_children()]), - set([c.id for c in children]), 'Children unequal') - self.assertEqual(set([c.id for c in root.get_items()]), - set([c.id for c in items]), 'Items unequal') + self.assertEqual( + set([c.id for c in root.get_children()]), + set([c.id for c in children]), + "Children unequal", + ) + self.assertEqual( + set([c.id for c in root.get_items()]), + set([c.id for c in items]), + "Items unequal", + ) self.assertEqual(actual_catalog_iterations, expected_catalog_iterations) @@ -233,8 +261,10 @@ def test_clone_generates_correct_links(self): for link in item.get_links(): actual_link_types_to_counts[item.id][link.rel] += 1 - self.assertEqual(set(expected_link_types_to_counts.keys()), - set(actual_link_types_to_counts.keys())) + self.assertEqual( + set(expected_link_types_to_counts.keys()), + set(actual_link_types_to_counts.keys()), + ) for obj_id in actual_link_types_to_counts: expected_counts = expected_link_types_to_counts[obj_id] @@ -242,9 +272,12 @@ def test_clone_generates_correct_links(self): self.assertEqual(set(expected_counts.keys()), set(actual_counts.keys())) for rel in expected_counts: self.assertEqual( - actual_counts[rel], expected_counts[rel], - 'Clone of {} has {} {} links, original has {}'.format( - obj_id, actual_counts[rel], rel, expected_counts[rel])) + actual_counts[rel], + expected_counts[rel], + "Clone of {} has {} {} links, original has {}".format( + obj_id, actual_counts[rel], rel, expected_counts[rel] + ), + ) def test_save_uses_previous_catalog_type(self): catalog = TestCases.test_case_1() @@ -265,51 +298,58 @@ def test_clone_uses_previous_catalog_type(self): def test_normalize_hrefs_sets_all_hrefs(self): catalog = TestCases.test_case_1() - catalog.normalize_hrefs('http://example.com') + catalog.normalize_hrefs("http://example.com") for root, _, items in catalog.walk(): - self.assertTrue(root.get_self_href().startswith('http://example.com')) + self.assertTrue(root.get_self_href().startswith("http://example.com")) for link in root.links: if link.is_resolved(): target_href = cast(ps.STACObject, link.target).self_href else: target_href = link.absolute_href self.assertTrue( - 'http://example.com' in target_href, - '[{}] {} does not contain "{}"'.format(link.rel, target_href, - 'http://example.com')) + "http://example.com" in target_href, + '[{}] {} does not contain "{}"'.format( + link.rel, target_href, "http://example.com" + ), + ) for item in items: - self.assertIn('http://example.com', item.self_href) + self.assertIn("http://example.com", item.self_href) def test_normalize_hrefs_makes_absolute_href(self): catalog = TestCases.test_case_1() - catalog.normalize_hrefs('./relativepath') - abspath = os.path.abspath('./relativepath') + catalog.normalize_hrefs("./relativepath") + abspath = os.path.abspath("./relativepath") self.assertTrue(catalog.get_self_href().startswith(abspath)) def test_normalize_href_works_with_label_source_links(self): catalog = TestCases.test_case_1() - catalog.normalize_hrefs('http://example.com') - item = catalog.get_item('area-1-1-labels', recursive=True) + catalog.normalize_hrefs("http://example.com") + item = catalog.get_item("area-1-1-labels", recursive=True) assert item is not None source = next(iter(LabelExtension.ext(item).get_sources())) self.assertEqual( source.get_self_href(), - "http://example.com/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json") + "http://example.com/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json", + ) def test_generate_subcatalogs_works_with_custom_properties(self): catalog = TestCases.test_case_8() - defaults = {'pl:item_type': 'PlanetScope'} - catalog.generate_subcatalogs('${year}/${month}/${pl:item_type}', defaults=defaults) + defaults = {"pl:item_type": "PlanetScope"} + catalog.generate_subcatalogs( + "${year}/${month}/${pl:item_type}", defaults=defaults + ) - month_cat = catalog.get_child('8', recursive=True) + month_cat = catalog.get_child("8", recursive=True) type_cats = set([cat.id for cat in month_cat.get_children()]) - self.assertEqual(type_cats, set(['PSScene4Band', 'SkySatScene', 'PlanetScope'])) + self.assertEqual(type_cats, set(["PSScene4Band", "SkySatScene", "PlanetScope"])) def test_generate_subcatalogs_does_not_change_item_count(self): catalog = TestCases.test_case_7() - item_counts = {cat.id: len(list(cat.get_all_items())) for cat in catalog.get_children()} + item_counts = { + cat.id: len(list(cat.get_all_items())) for cat in catalog.get_children() + } catalog.generate_subcatalogs("${year}/${day}") @@ -317,74 +357,89 @@ def test_generate_subcatalogs_does_not_change_item_count(self): catalog.normalize_hrefs(tmp_dir) catalog.save(ps.CatalogType.SELF_CONTAINED) - cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, 'catalog.json')) + cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) for child in cat2.get_children(): actual = len(list(child.get_all_items())) expected = item_counts[child.id] - self.assertEqual(actual, expected, msg=" for child '{}'".format(child.id)) + self.assertEqual( + actual, expected, msg=" for child '{}'".format(child.id) + ) def test_generate_subcatalogs_can_be_applied_multiple_times(self): catalog = TestCases.test_case_8() - _ = catalog.generate_subcatalogs('${year}/${month}') - catalog.normalize_hrefs('/tmp') - expected_hrefs = {item.id: item.get_self_href() for item in catalog.get_all_items()} + _ = catalog.generate_subcatalogs("${year}/${month}") + catalog.normalize_hrefs("/tmp") + expected_hrefs = { + item.id: item.get_self_href() for item in catalog.get_all_items() + } - result = catalog.generate_subcatalogs('${year}/${month}') + result = catalog.generate_subcatalogs("${year}/${month}") self.assertEqual(len(result), 0) - catalog.normalize_hrefs('/tmp') + catalog.normalize_hrefs("/tmp") for item in catalog.get_all_items(): - self.assertEqual(item.get_self_href(), - expected_hrefs[item.id], - msg=" for item '{}'".format(item.id)) + self.assertEqual( + item.get_self_href(), + expected_hrefs[item.id], + msg=" for item '{}'".format(item.id), + ) def test_generate_subcatalogs_works_after_adding_more_items(self): - catalog = Catalog(id='test', description='Test') - properties = dict(property1='A', property2=1) + catalog = Catalog(id="test", description="Test") + properties = dict(property1="A", property2=1) catalog.add_item( - Item(id='item1', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties=properties)) - catalog.generate_subcatalogs('${property1}/${property2}') + Item( + id="item1", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties=properties, + ) + ) + catalog.generate_subcatalogs("${property1}/${property2}") catalog.add_item( - Item(id='item2', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties=properties)) - catalog.generate_subcatalogs('${property1}/${property2}') - - catalog.normalize_hrefs('/tmp') - item1_parent = catalog.get_item('item1', recursive=True).get_parent() - item2_parent = catalog.get_item('item2', recursive=True).get_parent() + Item( + id="item2", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties=properties, + ) + ) + catalog.generate_subcatalogs("${property1}/${property2}") + + catalog.normalize_hrefs("/tmp") + item1_parent = catalog.get_item("item1", recursive=True).get_parent() + item2_parent = catalog.get_item("item2", recursive=True).get_parent() self.assertEqual(item1_parent.get_self_href(), item2_parent.get_self_href()) def test_generate_subcatalogs_works_for_branched_subcatalogs(self): - catalog = Catalog(id='test', description='Test') + catalog = Catalog(id="test", description="Test") item_properties = [ - dict(property1='A', property2=1, property3='i'), # add 3 subcats - dict(property1='A', property2=1, property3='j'), # add 1 more - dict(property1='A', property2=2, property3='i'), # add 2 more - dict(property1='B', property2=1, property3='i'), # add 3 more + dict(property1="A", property2=1, property3="i"), # add 3 subcats + dict(property1="A", property2=1, property3="j"), # add 1 more + dict(property1="A", property2=2, property3="i"), # add 2 more + dict(property1="B", property2=1, property3="i"), # add 3 more ] for ni, properties in enumerate(item_properties): catalog.add_item( - Item(id='item{}'.format(ni), - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties=properties)) - result = catalog.generate_subcatalogs('${property1}/${property2}/${property3}') + Item( + id="item{}".format(ni), + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties=properties, + ) + ) + result = catalog.generate_subcatalogs("${property1}/${property2}/${property3}") self.assertEqual(len(result), 9) actual_subcats = set([cat.id for cat in result]) - expected_subcats = {'A', 'B', '1', '2', 'i', 'j'} + expected_subcats = {"A", "B", "1", "2", "i", "j"} self.assertSetEqual(actual_subcats, expected_subcats) def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self): - catalog = Catalog(id='test', description='Test') + catalog = Catalog(id="test", description="Test") item_properties = [ dict(property1=1, property2=1), # add 2 subcats dict(property1=1, property2=2), # add 1 more @@ -393,24 +448,27 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self): ] for ni, properties in enumerate(item_properties): catalog.add_item( - Item(id='item{}'.format(ni), - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties=properties)) - result = catalog.generate_subcatalogs('${property1}/${property2}') + Item( + id="item{}".format(ni), + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties=properties, + ) + ) + result = catalog.generate_subcatalogs("${property1}/${property2}") self.assertEqual(len(result), 6) - catalog.normalize_hrefs('/') + catalog.normalize_hrefs("/") for item in catalog.get_all_items(): parent_href = item.get_parent().self_href path_to_parent, _ = os.path.split(parent_href) - subcats = [el for el in path_to_parent.split('/') if el] + subcats = [el for el in path_to_parent.split("/") if el] self.assertEqual(len(subcats), 2, msg=" for item '{}'".format(item.id)) def test_map_items(self): def item_mapper(item: ps.Item) -> ps.Item: - item.properties['ITEM_MAPPER'] = 'YEP' + item.properties["ITEM_MAPPER"] = "YEP" return item with TemporaryDirectory() as tmp_dir: @@ -418,23 +476,23 @@ def item_mapper(item: ps.Item) -> ps.Item: new_cat = catalog.map_items(item_mapper) - new_cat.normalize_hrefs(os.path.join(tmp_dir, 'cat')) + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - result_cat = Catalog.from_file(os.path.join(tmp_dir, 'cat', 'catalog.json')) + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) for item in result_cat.get_all_items(): - self.assertTrue('ITEM_MAPPER' in item.properties) + self.assertTrue("ITEM_MAPPER" in item.properties) for item in catalog.get_all_items(): - self.assertFalse('ITEM_MAPPER' in item.properties) + self.assertFalse("ITEM_MAPPER" in item.properties) def test_map_items_multiple(self): def item_mapper(item: ps.Item) -> List[ps.Item]: item2 = item.clone() - item2.id = item2.id + '_2' - item.properties['ITEM_MAPPER_1'] = 'YEP' - item2.properties['ITEM_MAPPER_2'] = 'YEP' + item2.id = item2.id + "_2" + item.properties["ITEM_MAPPER_1"] = "YEP" + item2.properties["ITEM_MAPPER_2"] = "YEP" return [item, item2] with TemporaryDirectory() as tmp_dir: @@ -443,72 +501,85 @@ def item_mapper(item: ps.Item) -> List[ps.Item]: new_cat = catalog.map_items(item_mapper) - new_cat.normalize_hrefs(os.path.join(tmp_dir, 'cat')) + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - result_cat = Catalog.from_file(os.path.join(tmp_dir, 'cat', 'catalog.json')) + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) result_items = result_cat.get_all_items() self.assertEqual(len(list(catalog_items)) * 2, len(list(result_items))) ones, twos = 0, 0 for item in result_items: - self.assertTrue(('ITEM_MAPPER_1' in item.properties) - or ('ITEM_MAPPER_2' in item.properties)) - if 'ITEM_MAPPER_1' in item.properties: + self.assertTrue( + ("ITEM_MAPPER_1" in item.properties) + or ("ITEM_MAPPER_2" in item.properties) + ) + if "ITEM_MAPPER_1" in item.properties: ones += 1 - if 'ITEM_MAPPER_2' in item.properties: + if "ITEM_MAPPER_2" in item.properties: twos += 1 self.assertEqual(ones, twos) for item in catalog.get_all_items(): - self.assertFalse(('ITEM_MAPPER_1' in item.properties) - or ('ITEM_MAPPER_2' in item.properties)) + self.assertFalse( + ("ITEM_MAPPER_1" in item.properties) + or ("ITEM_MAPPER_2" in item.properties) + ) def test_map_items_multiple_2(self): - catalog = Catalog(id='test-1', description='Test1') - item1 = Item(id='item1', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) - item1.add_asset('ortho', Asset(href='/some/ortho.tif')) + catalog = Catalog(id="test-1", description="Test1") + item1 = Item( + id="item1", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) + item1.add_asset("ortho", Asset(href="/some/ortho.tif")) catalog.add_item(item1) - kitten = Catalog(id='test-kitten', description='A cuter version of catalog') + kitten = Catalog(id="test-kitten", description="A cuter version of catalog") catalog.add_child(kitten) - item2 = Item(id='item2', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) - item2.add_asset('ortho', Asset(href='/some/other/ortho.tif')) + item2 = Item( + id="item2", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) + item2.add_asset("ortho", Asset(href="/some/other/ortho.tif")) kitten.add_item(item2) def modify_item_title(item: ps.Item) -> ps.Item: - item.properties['title'] = 'Some title' + item.properties["title"] = "Some title" return item def create_label_item(item: ps.Item) -> List[ps.Item]: # Assumes the GEOJSON labels are in the # same location as the image - img_href = item.assets['ortho'].href - label_href = '{}.geojson'.format(os.path.splitext(img_href)[0]) - label_item = Item(id='Labels', - geometry=item.geometry, - bbox=item.bbox, - datetime=datetime.utcnow(), - properties={}) + img_href = item.assets["ortho"].href + label_href = "{}.geojson".format(os.path.splitext(img_href)[0]) + label_item = Item( + id="Labels", + geometry=item.geometry, + bbox=item.bbox, + datetime=datetime.utcnow(), + properties={}, + ) LabelExtension(label_item).add_to(label_item) label_ext = LabelExtension.ext(label_item) label_ext.apply( - label_description='labels', + label_description="labels", label_type=LabelType.VECTOR, - label_properties=['label'], - label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], - label_tasks=['classification']) - label_ext.add_source(item, assets=['ortho']) + label_properties=["label"], + label_classes=[ + LabelClasses.create(classes=["one", "two"], name="label") + ], + label_tasks=["classification"], + ) + label_ext.add_source(item, assets=["ortho"]) label_ext.add_geojson_labels(label_href) return [item, label_item] @@ -521,11 +592,11 @@ def create_label_item(item: ps.Item) -> List[ps.Item]: self.assertTrue(len(list(items)) == 4) def test_map_assets_single(self): - changed_asset = 'd43bead8-e3f8-4c51-95d6-e24e750a402b' + changed_asset = "d43bead8-e3f8-4c51-95d6-e24e750a402b" def asset_mapper(key: str, asset: ps.Asset) -> ps.Asset: if key == changed_asset: - asset.title = 'NEW TITLE' + asset.title = "NEW TITLE" return asset @@ -534,29 +605,31 @@ def asset_mapper(key: str, asset: ps.Asset) -> ps.Asset: new_cat = catalog.map_assets(asset_mapper) - new_cat.normalize_hrefs(os.path.join(tmp_dir, 'cat')) + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - result_cat = Catalog.from_file(os.path.join(tmp_dir, 'cat', 'catalog.json')) + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) found = False for item in result_cat.get_all_items(): for key, asset in item.assets.items(): if key == changed_asset: found = True - self.assertEqual(asset.title, 'NEW TITLE') + self.assertEqual(asset.title, "NEW TITLE") else: - self.assertNotEqual(asset.title, 'NEW TITLE') + self.assertNotEqual(asset.title, "NEW TITLE") self.assertTrue(found) def test_map_assets_tup(self): changed_assets: List[str] = [] - def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Tuple[str, ps.Asset]]: - if asset.media_type and 'geotiff' in asset.media_type: - asset.title = 'NEW TITLE' + def asset_mapper( + key: str, asset: ps.Asset + ) -> Union[ps.Asset, Tuple[str, ps.Asset]]: + if asset.media_type and "geotiff" in asset.media_type: + asset.title = "NEW TITLE" changed_assets.append(key) - return ('{}-modified'.format(key), asset) + return ("{}-modified".format(key), asset) else: return asset @@ -565,21 +638,21 @@ def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Tuple[str, ps.Ass new_cat = catalog.map_assets(asset_mapper) - new_cat.normalize_hrefs(os.path.join(tmp_dir, 'cat')) + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - result_cat = Catalog.from_file(os.path.join(tmp_dir, 'cat', 'catalog.json')) + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) found = False not_found = False for item in result_cat.get_all_items(): for key, asset in item.assets.items(): - if key.replace('-modified', '') in changed_assets: + if key.replace("-modified", "") in changed_assets: found = True - self.assertEqual(asset.title, 'NEW TITLE') + self.assertEqual(asset.title, "NEW TITLE") else: not_found = True - self.assertNotEqual(asset.title, 'NEW TITLE') + self.assertNotEqual(asset.title, "NEW TITLE") self.assertTrue(found) self.assertTrue(not_found) @@ -587,14 +660,16 @@ def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Tuple[str, ps.Ass def test_map_assets_multi(self): changed_assets = [] - def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Dict[str, ps.Asset]]: - if asset.media_type and 'geotiff' in asset.media_type: + def asset_mapper( + key: str, asset: ps.Asset + ) -> Union[ps.Asset, Dict[str, ps.Asset]]: + if asset.media_type and "geotiff" in asset.media_type: changed_assets.append(key) mod1 = asset.clone() - mod1.title = 'NEW TITLE 1' + mod1.title = "NEW TITLE 1" mod2 = asset.clone() - mod2.title = 'NEW TITLE 2' - return {'{}-mod-1'.format(key): mod1, '{}-mod-2'.format(key): mod2} + mod2.title = "NEW TITLE 2" + return {"{}-mod-1".format(key): mod1, "{}-mod-2".format(key): mod2} else: return asset @@ -603,25 +678,25 @@ def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Dict[str, ps.Asse new_cat = catalog.map_assets(asset_mapper) - new_cat.normalize_hrefs(os.path.join(tmp_dir, 'cat')) + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - result_cat = Catalog.from_file(os.path.join(tmp_dir, 'cat', 'catalog.json')) + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) found1 = False found2 = False not_found = False for item in result_cat.get_all_items(): for key, asset in item.assets.items(): - if key.replace('-mod-1', '') in changed_assets: + if key.replace("-mod-1", "") in changed_assets: found1 = True - self.assertEqual(asset.title, 'NEW TITLE 1') - elif key.replace('-mod-2', '') in changed_assets: + self.assertEqual(asset.title, "NEW TITLE 1") + elif key.replace("-mod-2", "") in changed_assets: found2 = True - self.assertEqual(asset.title, 'NEW TITLE 2') + self.assertEqual(asset.title, "NEW TITLE 2") else: not_found = True - self.assertNotEqual(asset.title, 'NEW TITLE') + self.assertNotEqual(asset.title, "NEW TITLE") self.assertTrue(found1) self.assertTrue(found2) @@ -630,15 +705,15 @@ def asset_mapper(key: str, asset: ps.Asset) -> Union[ps.Asset, Dict[str, ps.Asse def test_make_all_asset_hrefs_absolute(self): cat = TestCases.test_case_2() cat.make_all_asset_hrefs_absolute() - item = cat.get_item('cf73ec1a-d790-4b59-b077-e101738571ed', recursive=True) + item = cat.get_item("cf73ec1a-d790-4b59-b077-e101738571ed", recursive=True) - href = item.assets['cf73ec1a-d790-4b59-b077-e101738571ed'].href + href = item.assets["cf73ec1a-d790-4b59-b077-e101738571ed"].href self.assertTrue(is_absolute_href(href)) def test_make_all_asset_hrefs_relative(self): cat = TestCases.test_case_2() - item = cat.get_item('cf73ec1a-d790-4b59-b077-e101738571ed', recursive=True) - asset = item.assets['cf73ec1a-d790-4b59-b077-e101738571ed'] + item = cat.get_item("cf73ec1a-d790-4b59-b077-e101738571ed", recursive=True) + asset = item.assets["cf73ec1a-d790-4b59-b077-e101738571ed"] original_href = asset.href cat.make_all_asset_hrefs_absolute() @@ -682,50 +757,50 @@ def check_all_absolute(cat: Catalog): def test_full_copy_and_normalize_works_with_created_stac(self): cat = TestCases.test_case_3() cat_copy = cat.full_copy() - cat_copy.normalize_hrefs('http://example.com') + cat_copy.normalize_hrefs("http://example.com") for root, catalogs, items in cat_copy.walk(): for link in root.links: - if link.rel != 'self': + if link.rel != "self": self.assertIsNot(link.target, None) for item in items: for link in item.links: - if link.rel != 'self': + if link.rel != "self": self.assertIsNot(link.get_href(), None) def test_extra_fields(self): catalog = TestCases.test_case_1() - catalog.extra_fields['type'] = 'FeatureCollection' + catalog.extra_fields["type"] = "FeatureCollection" with TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, 'catalog.json') + p = os.path.join(tmp_dir, "catalog.json") catalog.save_object(include_self_link=False, dest_href=p) with open(p) as f: cat_json = json.load(f) - self.assertTrue('type' in cat_json) - self.assertEqual(cat_json['type'], 'FeatureCollection') + self.assertTrue("type" in cat_json) + self.assertEqual(cat_json["type"], "FeatureCollection") read_cat = ps.Catalog.from_file(p) - self.assertTrue('type' in read_cat.extra_fields) - self.assertEqual(read_cat.extra_fields['type'], 'FeatureCollection') + self.assertTrue("type" in read_cat.extra_fields) + self.assertEqual(read_cat.extra_fields["type"], "FeatureCollection") def test_validate_all(self): for cat in TestCases.all_test_catalogs(): with self.subTest(cat.id): # If hrefs are not set, it will fail validation. if cat.get_self_href() is None: - cat.normalize_hrefs('/tmp') + cat.normalize_hrefs("/tmp") cat.validate_all() # Make one invalid, write it off, read it in, ensure it throws cat = TestCases.test_case_1() - item = cat.get_item('area-1-1-labels', recursive=True) - item.geometry = {'type': 'INVALID', 'coordinates': 'NONE'} + item = cat.get_item("area-1-1-labels", recursive=True) + item.geometry = {"type": "INVALID", "coordinates": "NONE"} with TemporaryDirectory() as tmp_dir: cat.normalize_hrefs(tmp_dir) cat.save(catalog_type=ps.CatalogType.SELF_CONTAINED) - cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, 'catalog.json')) + cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) with self.assertRaises(STACValidationError): cat2.validate_all() @@ -756,24 +831,31 @@ def test_set_hrefs_manually(self): # Set each item's HREF based on it's datetime for item in items: - item_href = '{}/{}-{}/{}.json'.format(root_dir, item.datetime.year, - item.datetime.month, item.id) + item_href = "{}/{}-{}/{}.json".format( + root_dir, item.datetime.year, item.datetime.month, item.id + ) item.set_self_href(item_href) catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - read_catalog = Catalog.from_file(os.path.join(tmp_dir, 'catalog.json')) + read_catalog = Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) for root, _, items in read_catalog.walk(): parent = root.get_parent() if parent is None: - self.assertEqual(root.get_self_href(), os.path.join(tmp_dir, 'catalog.json')) + self.assertEqual( + root.get_self_href(), os.path.join(tmp_dir, "catalog.json") + ) else: d = os.path.dirname(parent.self_href) - self.assertEqual(root.get_self_href(), - os.path.join(d, root.id, root.DEFAULT_FILE_NAME)) + self.assertEqual( + root.get_self_href(), + os.path.join(d, root.id, root.DEFAULT_FILE_NAME), + ) for item in items: - end = '{}-{}/{}.json'.format(item.datetime.year, item.datetime.month, item.id) + end = "{}-{}/{}.json".format( + item.datetime.year, item.datetime.month, item.id + ) self.assertTrue(item.get_self_href().endswith(end)) def test_collections_cache_correctly(self): @@ -790,18 +872,24 @@ def test_collections_cache_correctly(self): self.assertNotEqual(list(items), None) call_uris: List[Any] = [ - call[0][0] for call in mock_io.mock.read_text.call_args_list + call[0][0] + for call in mock_io.mock.read_text.call_args_list if call[0][0] in expected_collection_reads ] for collection_uri in expected_collection_reads: calls = len([x for x in call_uris if x == collection_uri]) self.assertEqual( - calls, 1, '{} was read {} times instead of once!'.format(collection_uri, calls)) + calls, + 1, + "{} was read {} times instead of once!".format( + collection_uri, calls + ), + ) def test_reading_iterating_and_writing_works_as_expected(self): - """ Test case to cover issue #88 """ - stac_uri = 'tests/data-files/catalogs/test-case-6/catalog.json' + """Test case to cover issue #88""" + stac_uri = "tests/data-files/catalogs/test-case-6/catalog.json" cat = Catalog.from_file(stac_uri) # Iterate over the items. This was causing failure in @@ -810,12 +898,12 @@ def test_reading_iterating_and_writing_works_as_expected(self): pass with TemporaryDirectory() as tmp_dir: - new_stac_uri = os.path.join(tmp_dir, 'test-case-6') + new_stac_uri = os.path.join(tmp_dir, "test-case-6") cat.normalize_hrefs(new_stac_uri) cat.save(catalog_type=CatalogType.SELF_CONTAINED) # Open the local copy and iterate over it. - cat2 = Catalog.from_file(os.path.join(new_stac_uri, 'catalog.json')) + cat2 = Catalog.from_file(os.path.join(new_stac_uri, "catalog.json")) for item in cat2.get_all_items(): # Iterate again over the items. This would fail in #88 @@ -835,7 +923,9 @@ def test_resolve_planet(self): def test_handles_children_with_same_id(self): # This catalog has the root and child collection share an ID. - cat = ps.Catalog.from_file(TestCases.get_path('data-files/invalid/shared-id/catalog.json')) + cat = ps.Catalog.from_file( + TestCases.get_path("data-files/invalid/shared-id/catalog.json") + ) items = list(cat.get_all_items()) self.assertEqual(len(items), 1) @@ -855,15 +945,17 @@ def check_link(self, link: ps.Link, tag: str): target_href: str = cast(ps.STACObject, link.target).self_href else: target_href = str(link.target) - self.assertTrue(tag in target_href, - '[{}] {} does not contain "{}"'.format(link.rel, target_href, tag)) + self.assertTrue( + tag in target_href, + '[{}] {} does not contain "{}"'.format(link.rel, target_href, tag), + ) def check_item(self, item: Item, tag: str): for link in item.links: self.check_link(link, tag) def check_catalog(self, c: Catalog, tag: str): - self.assertEqual(len(c.get_links('root')), 1) + self.assertEqual(len(c.get_links("root")), 1) for link in c.links: self.check_link(link, tag) @@ -876,88 +968,105 @@ def check_catalog(self, c: Catalog, tag: str): def test_full_copy_1(self): with TemporaryDirectory() as tmp_dir: - cat = Catalog(id='test', description='test catalog') + cat = Catalog(id="test", description="test catalog") - item = Item(id='test_item', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) + item = Item( + id="test_item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) cat.add_item(item) - cat.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-1-source')) + cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-source")) cat2 = cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-1-dest')) + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-dest")) - self.check_catalog(cat, 'source') - self.check_catalog(cat2, 'dest') + self.check_catalog(cat, "source") + self.check_catalog(cat2, "dest") def test_full_copy_2(self): with TemporaryDirectory() as tmp_dir: - cat = Catalog(id='test', description='test catalog') - image_item = Item(id='Imagery', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) - for key in ['ortho', 'dsm']: + cat = Catalog(id="test", description="test catalog") + image_item = Item( + id="Imagery", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) + for key in ["ortho", "dsm"]: image_item.add_asset( - key, Asset(href='some/{}.tif'.format(key), media_type=MediaType.GEOTIFF)) - - label_item = Item(id='Labels', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) + key, + Asset(href="some/{}.tif".format(key), media_type=MediaType.GEOTIFF), + ) + + label_item = Item( + id="Labels", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) LabelExtension.add_to(label_item) label_ext = LabelExtension.ext(label_item) label_ext.apply( - label_description='labels', + label_description="labels", label_type=LabelType.VECTOR, - label_properties=['label'], - label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], - label_tasks=['classification']) - label_ext.add_source(image_item, assets=['ortho']) + label_properties=["label"], + label_classes=[ + LabelClasses.create(classes=["one", "two"], name="label") + ], + label_tasks=["classification"], + ) + label_ext.add_source(image_item, assets=["ortho"]) cat.add_items([image_item, label_item]) - cat.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-2-source')) + cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-source")) cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) cat2 = cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-2-dest')) + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-dest")) cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - self.check_catalog(cat, 'source') - self.check_catalog(cat2, 'dest') + self.check_catalog(cat, "source") + self.check_catalog(cat2, "dest") def test_full_copy_3(self): with TemporaryDirectory() as tmp_dir: root_cat = TestCases.test_case_1() - root_cat.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-3-source')) + root_cat.normalize_hrefs( + os.path.join(tmp_dir, "catalog-full-copy-3-source") + ) root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) cat2 = root_cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-3-dest')) + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-3-dest")) cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - self.check_catalog(root_cat, 'source') - self.check_catalog(cat2, 'dest') + self.check_catalog(root_cat, "source") + self.check_catalog(cat2, "dest") def test_full_copy_4(self): with TemporaryDirectory() as tmp_dir: root_cat = TestCases.test_case_2() - root_cat.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-4-source')) + root_cat.normalize_hrefs( + os.path.join(tmp_dir, "catalog-full-copy-4-source") + ) root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) cat2 = root_cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, 'catalog-full-copy-4-dest')) + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-4-dest")) cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - self.check_catalog(root_cat, 'source') - self.check_catalog(cat2, 'dest') + self.check_catalog(root_cat, "source") + self.check_catalog(cat2, "dest") # Check that the relative asset link was saved correctly in the copy. - item = cat2.get_item('cf73ec1a-d790-4b59-b077-e101738571ed', recursive=True) + item = cat2.get_item("cf73ec1a-d790-4b59-b077-e101738571ed", recursive=True) - href = item.assets['cf73ec1a-d790-4b59-b077-e101738571ed'].get_absolute_href() + href = item.assets[ + "cf73ec1a-d790-4b59-b077-e101738571ed" + ].get_absolute_href() assert href is not None self.assertTrue(os.path.exists(href)) diff --git a/tests/test_collection.py b/tests/test_collection.py index 92f8e801e..574e24375 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -8,16 +8,16 @@ import pystac as ps from pystac.extensions.eo import EOExtension from pystac.validation import validate_dict -from pystac import (Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType) +from pystac import Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType from pystac.utils import datetime_to_str -from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX) +from tests.utils import TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX TEST_DATETIME = datetime(2020, 3, 14, 16, 32) class CollectionTest(unittest.TestCase): def test_spatial_extent_from_coordinates(self): - extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM['coordinates']) + extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]) self.assertEqual(len(extent.bboxes), 1) bbox = extent.bboxes[0] @@ -51,12 +51,12 @@ def test_clone_uses_previous_catalog_type(self): def test_multiple_extents(self): cat1 = TestCases.test_case_1() - col1 = cat1.get_child('country-1').get_child('area-1-1') + col1 = cat1.get_child("country-1").get_child("area-1-1") col1.validate() self.assertIsInstance(col1, Collection) validate_dict(col1.to_dict(), ps.STACObjectType.COLLECTION) - multi_ext_uri = TestCases.get_path('data-files/collections/multi-extent.json') + multi_ext_uri = TestCases.get_path("data-files/collections/multi-extent.json") with open(multi_ext_uri) as f: multi_ext_dict = json.load(f) validate_dict(multi_ext_dict, ps.STACObjectType.COLLECTION) @@ -65,67 +65,74 @@ def test_multiple_extents(self): multi_ext_col = Collection.from_file(multi_ext_uri) multi_ext_col.validate() ext = multi_ext_col.extent - extent_dict = multi_ext_dict['extent'] + extent_dict = multi_ext_dict["extent"] self.assertIsInstance(ext, Extent) self.assertIsInstance(ext.spatial.bboxes[0], list) self.assertEqual(len(ext.spatial.bboxes), 2) self.assertDictEqual(ext.to_dict(), extent_dict) cloned_ext = ext.clone() - self.assertDictEqual(cloned_ext.to_dict(), multi_ext_dict['extent']) + self.assertDictEqual(cloned_ext.to_dict(), multi_ext_dict["extent"]) def test_extra_fields(self): catalog = TestCases.test_case_2() - collection = catalog.get_child('1a8c1632-fa91-4a62-b33e-3a87c2ebdf16') + collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") - collection.extra_fields['test'] = 'extra' + collection.extra_fields["test"] = "extra" with TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, 'collection.json') + p = os.path.join(tmp_dir, "collection.json") collection.save_object(include_self_link=False, dest_href=p) with open(p) as f: col_json = json.load(f) - self.assertTrue('test' in col_json) - self.assertEqual(col_json['test'], 'extra') + self.assertTrue("test" in col_json) + self.assertEqual(col_json["test"], "extra") read_col = ps.Collection.from_file(p) - self.assertTrue('test' in read_col.extra_fields) - self.assertEqual(read_col.extra_fields['test'], 'extra') + self.assertTrue("test" in read_col.extra_fields) + self.assertEqual(read_col.extra_fields["test"], "extra") def test_update_extents(self): catalog = TestCases.test_case_2() - base_collection = catalog.get_child('1a8c1632-fa91-4a62-b33e-3a87c2ebdf16') + base_collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") assert isinstance(base_collection, Collection) base_extent = base_collection.extent collection = base_collection.clone() - item1 = Item(id='test-item-1', - geometry=ARBITRARY_GEOM, - bbox=[-180, -90, 180, 90], - datetime=TEST_DATETIME, - properties={'key': 'one'}, - stac_extensions=['eo', 'commons']) - - item2 = Item(id='test-item-1', - geometry=ARBITRARY_GEOM, - bbox=[-180, -90, 180, 90], - datetime=None, - properties={ - 'start_datetime': datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)), - 'end_datetime': datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0)) - }, - stac_extensions=['eo', 'commons']) + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-180, -90, 180, 90], + datetime=TEST_DATETIME, + properties={"key": "one"}, + stac_extensions=["eo", "commons"], + ) + + item2 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-180, -90, 180, 90], + datetime=None, + properties={ + "start_datetime": datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)), + "end_datetime": datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0)), + }, + stac_extensions=["eo", "commons"], + ) collection.add_item(item1) collection.update_extent_from_items() self.assertEqual([[-180, -90, 180, 90]], collection.extent.spatial.bboxes) - self.assertEqual(len(base_extent.spatial.bboxes[0]), - len(collection.extent.spatial.bboxes[0])) + self.assertEqual( + len(base_extent.spatial.bboxes[0]), len(collection.extent.spatial.bboxes[0]) + ) - self.assertNotEqual(base_extent.temporal.intervals, collection.extent.temporal.intervals) - collection.remove_item('test-item-1') + self.assertNotEqual( + base_extent.temporal.intervals, collection.extent.temporal.intervals + ) + collection.remove_item("test-item-1") collection.update_extent_from_items() self.assertNotEqual([[-180, -90, 180, 90]], collection.extent.spatial.bboxes) collection.add_item(item2) @@ -133,8 +140,14 @@ def test_update_extents(self): collection.update_extent_from_items() self.assertEqual( - [[item2.common_metadata.start_datetime, base_extent.temporal.intervals[0][1]]], - collection.extent.temporal.intervals) + [ + [ + item2.common_metadata.start_datetime, + base_extent.temporal.intervals[0][1], + ] + ], + collection.extent.temporal.intervals, + ) def test_supplying_href_in_init_does_not_fail(self): test_href = "http://example.com/collection.json" @@ -142,16 +155,16 @@ def test_supplying_href_in_init_does_not_fail(self): 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=test_href) + collection = Collection( + id="test", description="test desc", extent=collection_extent, href=test_href + ) self.assertEqual(collection.get_self_href(), test_href) def test_collection_with_href_caches_by_href(self): collection = ps.Collection.from_file( - TestCases.get_path('data-files/examples/hand-0.8.1/collection.json')) + TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") + ) cache = collection._resolved_objects # Since all of our STAC objects have HREFs, everything should be @@ -168,41 +181,53 @@ def test_spatial_allows_single_bbox(self): collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) - collection = Collection(id='test', description='test desc', extent=collection_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.set_self_href("https://example.com/collection.json") collection.validate() def test_from_items(self): - item1 = Item(id='test-item-1', - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - properties={}) - - item2 = Item(id='test-item-2', - geometry=ARBITRARY_GEOM, - bbox=[0, -9, 10, 1], - datetime=None, - properties={ - 'start_datetime': - datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC)), - 'end_datetime': - datetime_to_str(datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC)) - }) - - item3 = Item(id='test-item-2', - geometry=ARBITRARY_GEOM, - bbox=[-5, -20, 5, 0], - datetime=None, - properties={ - 'start_datetime': - datetime_to_str(datetime(2000, 12, 1, 12, 0, 0, 0, tzinfo=tz.UTC)), - 'end_datetime': - datetime_to_str(datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC), ) - }) + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + properties={}, + ) + + item2 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[0, -9, 10, 1], + datetime=None, + properties={ + "start_datetime": datetime_to_str( + datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + "end_datetime": datetime_to_str( + datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + }, + ) + + item3 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[-5, -20, 5, 0], + datetime=None, + properties={ + "start_datetime": datetime_to_str( + datetime(2000, 12, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + "end_datetime": datetime_to_str( + datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + ), + }, + ) extent = Extent.from_items([item1, item2, item3]) diff --git a/tests/test_item.py b/tests/test_item.py index 9488679b7..be2955d82 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -10,13 +10,13 @@ from pystac.validation import validate_dict import pystac.serialization.common_properties from pystac.item import CommonMetadata -from pystac.utils import (datetime_to_str, get_opt, str_to_datetime, is_absolute_href) -from tests.utils import (TestCases, test_to_from_dict) +from pystac.utils import datetime_to_str, get_opt, str_to_datetime, is_absolute_href +from tests.utils import TestCases, test_to_from_dict class ItemTest(unittest.TestCase): def get_example_item_dict(self): - m = TestCases.get_path('data-files/item/sample-item.json') + m = TestCases.get_path("data-files/item/sample-item.json") with open(m) as f: item_dict = json.load(f) return item_dict @@ -30,20 +30,23 @@ def test_to_from_dict(self): item = Item.from_dict(item_dict) self.assertEqual( item.get_self_href(), - 'http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.json') + "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.json", + ) # test asset creation additional field(s) - self.assertEqual(item.assets['analytic'].properties['product'], - 'http://cool-sat.com/catalog/products/analytic.json') - self.assertEqual(len(item.assets['thumbnail'].properties), 0) + self.assertEqual( + item.assets["analytic"].properties["product"], + "http://cool-sat.com/catalog/products/analytic.json", + ) + self.assertEqual(len(item.assets["thumbnail"].properties), 0) def test_set_self_href_does_not_break_asset_hrefs(self): cat = TestCases.test_case_2() for item in cat.get_all_items(): for asset in item.assets.values(): if is_absolute_href(asset.href): - asset.href = (f'./{os.path.basename(asset.href)}') - item.set_self_href('http://example.com/item.json') + asset.href = f"./{os.path.basename(asset.href)}" + item.set_self_href("http://example.com/item.json") for asset in item.assets.values(): self.assertTrue(is_absolute_href(asset.href)) @@ -52,7 +55,7 @@ def test_set_self_href_none_ignores_relative_asset_hrefs(self): for item in cat.get_all_items(): for asset in item.assets.values(): if is_absolute_href(asset.href): - asset.href = (f'./{os.path.basename(asset.href)}') + asset.href = f"./{os.path.basename(asset.href)}" item.set_self_href(None) for asset in item.assets.values(): self.assertFalse(is_absolute_href(asset.href)) @@ -60,31 +63,33 @@ def test_set_self_href_none_ignores_relative_asset_hrefs(self): def test_asset_absolute_href(self): item_dict = self.get_example_item_dict() item = Item.from_dict(item_dict) - rel_asset = Asset('./data.geojson') + rel_asset = Asset("./data.geojson") rel_asset.set_owner(item) - expected_href = 'http://cool-sat.com/catalog/CS3-20160503_132130_04/data.geojson' + expected_href = ( + "http://cool-sat.com/catalog/CS3-20160503_132130_04/data.geojson" + ) actual_href = rel_asset.get_absolute_href() self.assertEqual(expected_href, actual_href) def test_extra_fields(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - item.extra_fields['test'] = 'extra' + item.extra_fields["test"] = "extra" with TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, 'item.json') + p = os.path.join(tmp_dir, "item.json") item.save_object(include_self_link=False, dest_href=p) with open(p) as f: item_json = json.load(f) - self.assertTrue('test' in item_json) - self.assertEqual(item_json['test'], 'extra') + self.assertTrue("test" in item_json) + self.assertEqual(item_json["test"], "extra") read_item = ps.Item.from_file(p) - self.assertTrue('test' in read_item.extra_fields) - self.assertEqual(read_item.extra_fields['test'], 'extra') + self.assertTrue("test" in read_item.extra_fields) + self.assertEqual(read_item.extra_fields["test"], "extra") def test_clearing_collection(self): - collection = TestCases.test_case_4().get_child('acc') + collection = TestCases.test_case_4().get_child("acc") assert isinstance(collection, ps.Collection) item = next(iter(collection.get_all_items())) self.assertEqual(item.collection_id, collection.id) @@ -100,61 +105,81 @@ def test_datetime_ISO8601_format(self): item = Item.from_dict(item_dict) - formatted_time = item.to_dict()['properties']['datetime'] + formatted_time = item.to_dict()["properties"]["datetime"] - self.assertEqual('2016-05-03T13:22:30.040000Z', formatted_time) + self.assertEqual("2016-05-03T13:22:30.040000Z", formatted_time) def test_null_datetime(self): - item = ps.Item.from_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) with self.assertRaises(ps.STACError): - Item('test', geometry=item.geometry, bbox=item.bbox, datetime=None, properties={}) - - null_dt_item = Item('test', - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - properties={ - 'start_datetime': datetime_to_str(get_opt(item.datetime)), - 'end_datetime': datetime_to_str(get_opt(item.datetime)) - }) + Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + properties={}, + ) + + null_dt_item = Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + properties={ + "start_datetime": datetime_to_str(get_opt(item.datetime)), + "end_datetime": datetime_to_str(get_opt(item.datetime)), + }, + ) null_dt_item.validate() def test_get_set_asset_datetime(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) item_datetime = item.datetime # No property on asset - self.assertEqual(item.get_datetime(item.assets['thumbnail']), item.datetime) + self.assertEqual(item.get_datetime(item.assets["thumbnail"]), item.datetime) # Property on asset - self.assertNotEqual(item.get_datetime(item.assets['analytic']), item.datetime) - self.assertEqual(item.get_datetime(item.assets['analytic']), - str_to_datetime("2017-05-03T13:22:30.040Z")) + self.assertNotEqual(item.get_datetime(item.assets["analytic"]), item.datetime) + self.assertEqual( + item.get_datetime(item.assets["analytic"]), + str_to_datetime("2017-05-03T13:22:30.040Z"), + ) - item.set_datetime(str_to_datetime("2018-05-03T13:22:30.040Z"), item.assets['thumbnail']) + item.set_datetime( + str_to_datetime("2018-05-03T13:22:30.040Z"), item.assets["thumbnail"] + ) self.assertEqual(item.get_datetime(), item_datetime) - self.assertEqual(item.get_datetime(item.assets['thumbnail']), - str_to_datetime("2018-05-03T13:22:30.040Z")) + self.assertEqual( + item.get_datetime(item.assets["thumbnail"]), + str_to_datetime("2018-05-03T13:22:30.040Z"), + ) def test_read_eo_item_owns_asset(self): - item = next(x for x in TestCases.test_case_1().get_all_items() if isinstance(x, Item)) + item = next( + x for x in TestCases.test_case_1().get_all_items() if isinstance(x, Item) + ) assert len(item.assets) > 0 for asset_key in item.assets: self.assertEqual(item.assets[asset_key].owner, item) def test_self_contained_item(self): item_dict = self.get_example_item_dict() - item_dict['links'] = [link for link in item_dict['links'] if link['rel'] == 'self'] + item_dict["links"] = [ + link for link in item_dict["links"] if link["rel"] == "self" + ] item = Item.from_dict(item_dict) self.assertIsInstance(item, Item) self.assertEqual(len(item.links), 1) def test_null_geometry(self): m = TestCases.get_path( - 'data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json') + "data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json" + ) with open(m) as f: item_dict = json.load(f) @@ -165,17 +190,20 @@ def test_null_geometry(self): item.validate() item_dict = item.to_dict() - self.assertIsNone(item_dict['geometry']) + self.assertIsNone(item_dict["geometry"]) with self.assertRaises(KeyError): - item_dict['bbox'] + item_dict["bbox"] def test_0_9_item_with_no_extensions_does_not_read_collection_data(self): item_json = ps.StacIO.default().read_json( - TestCases.get_path('data-files/examples/hand-0.9.0/010100/010100.json')) - assert item_json.get('stac_extensions') is None - assert item_json.get('stac_version') == '0.9.0' - - did_merge = pystac.serialization.common_properties.merge_common_properties(item_json) + TestCases.get_path("data-files/examples/hand-0.9.0/010100/010100.json") + ) + assert item_json.get("stac_extensions") is None + assert item_json.get("stac_version") == "0.9.0" + + did_merge = pystac.serialization.common_properties.merge_common_properties( + item_json + ) self.assertFalse(did_merge) def test_clone_sets_asset_owner(self): @@ -202,30 +230,32 @@ def test_make_asset_href_relative_is_noop_on_relative_hrefs(self): class CommonMetadataTest(unittest.TestCase): def setUp(self): self.URI_1 = TestCases.get_path( - 'data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json') + "data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json" + ) self.ITEM_1 = Item.from_file(self.URI_1) self.URI_2 = TestCases.get_path( - 'data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json') + "data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json" + ) self.ITEM_2 = Item.from_file(self.URI_2) self.EXAMPLE_CM_DICT: Dict[str, Any] = { - 'start_datetime': - '2020-05-21T16:42:24.896Z', - 'platform': - 'example platform', - 'providers': [{ - 'name': 'example provider', - 'roles': ['example roll'], - 'url': 'https://example-provider.com/' - }] + "start_datetime": "2020-05-21T16:42:24.896Z", + "platform": "example platform", + "providers": [ + { + "name": "example provider", + "roles": ["example roll"], + "url": "https://example-provider.com/", + } + ], } def test_datetimes(self): # save dict of original item to check that `common_metadata` # method doesn't mutate self.item_1 before = self.ITEM_1.clone().to_dict() - start_datetime_str = self.ITEM_1.properties['start_datetime'] + start_datetime_str = self.ITEM_1.properties["start_datetime"] self.assertIsInstance(start_datetime_str, str) common_metadata = self.ITEM_1.common_metadata @@ -242,12 +272,12 @@ def test_common_metadata_start_datetime(self): example_datetime_dt = str_to_datetime(example_datetime_str) self.assertEqual(x.common_metadata.start_datetime, start_datetime_dt) - self.assertEqual(x.properties['start_datetime'], start_datetime_str) + self.assertEqual(x.properties["start_datetime"], start_datetime_str) x.common_metadata.start_datetime = example_datetime_dt self.assertEqual(x.common_metadata.start_datetime, example_datetime_dt) - self.assertEqual(x.properties['start_datetime'], example_datetime_str) + self.assertEqual(x.properties["start_datetime"], example_datetime_str) def test_common_metadata_end_datetime(self): x = self.ITEM_1.clone() @@ -257,12 +287,12 @@ def test_common_metadata_end_datetime(self): example_datetime_dt = str_to_datetime(example_datetime_str) self.assertEqual(x.common_metadata.end_datetime, end_datetime_dt) - self.assertEqual(x.properties['end_datetime'], end_datetime_str) + self.assertEqual(x.properties["end_datetime"], end_datetime_str) x.common_metadata.end_datetime = example_datetime_dt self.assertEqual(x.common_metadata.end_datetime, example_datetime_dt) - self.assertEqual(x.properties['end_datetime'], example_datetime_str) + self.assertEqual(x.properties["end_datetime"], example_datetime_str) def test_common_metadata_created(self): x = self.ITEM_2.clone() @@ -272,12 +302,12 @@ def test_common_metadata_created(self): example_datetime_dt = str_to_datetime(example_datetime_str) self.assertEqual(x.common_metadata.created, created_dt) - self.assertEqual(x.properties['created'], created_str) + self.assertEqual(x.properties["created"], created_str) x.common_metadata.created = example_datetime_dt self.assertEqual(x.common_metadata.created, example_datetime_dt) - self.assertEqual(x.properties['created'], example_datetime_str) + self.assertEqual(x.properties["created"], example_datetime_str) def test_common_metadata_updated(self): x = self.ITEM_2.clone() @@ -287,37 +317,40 @@ def test_common_metadata_updated(self): example_datetime_dt = str_to_datetime(example_datetime_str) self.assertEqual(x.common_metadata.updated, updated_dt) - self.assertEqual(x.properties['updated'], updated_str) + self.assertEqual(x.properties["updated"], updated_str) x.common_metadata.updated = example_datetime_dt self.assertEqual(x.common_metadata.updated, example_datetime_dt) - self.assertEqual(x.properties['updated'], example_datetime_str) + self.assertEqual(x.properties["updated"], example_datetime_str) def test_common_metadata_providers(self): x = self.ITEM_2.clone() - providers_dict_list: List[Dict[str, Any]] = [{ - "name": "CoolSat", - "roles": ["producer", "licensor"], - "url": "https://cool-sat.com/" - }] + providers_dict_list: List[Dict[str, Any]] = [ + { + "name": "CoolSat", + "roles": ["producer", "licensor"], + "url": "https://cool-sat.com/", + } + ] providers_object_list = [Provider.from_dict(d) for d in providers_dict_list] - example_providers_dict_list: List[Dict[str, Any]] = [{ - "name": - "ExampleProvider_1", - "roles": ["example_role_1", "example_role_2"], - "url": - "https://exampleprovider1.com/" - }, { - "name": - "ExampleProvider_2", - "roles": ["example_role_1", "example_role_2"], - "url": - "https://exampleprovider2.com/" - }] - example_providers_object_list = [Provider.from_dict(d) for d in example_providers_dict_list] + example_providers_dict_list: List[Dict[str, Any]] = [ + { + "name": "ExampleProvider_1", + "roles": ["example_role_1", "example_role_2"], + "url": "https://exampleprovider1.com/", + }, + { + "name": "ExampleProvider_2", + "roles": ["example_role_1", "example_role_2"], + "url": "https://exampleprovider2.com/", + }, + ] + example_providers_object_list = [ + Provider.from_dict(d) for d in example_providers_dict_list + ] for i in range(len(get_opt(x.common_metadata.providers))): p1 = get_opt(x.common_metadata.providers)[i] @@ -326,7 +359,7 @@ def test_common_metadata_providers(self): self.assertIsInstance(p2, Provider) self.assertDictEqual(p1.to_dict(), p2.to_dict()) - pd1 = x.properties['providers'][i] + pd1 = x.properties["providers"][i] pd2 = providers_dict_list[i] self.assertIsInstance(pd1, dict) self.assertIsInstance(pd2, dict) @@ -341,7 +374,7 @@ def test_common_metadata_providers(self): self.assertIsInstance(p2, Provider) self.assertDictEqual(p1.to_dict(), p2.to_dict()) - pd1 = x.properties['providers'][i] + pd1 = x.properties["providers"][i] pd2 = example_providers_dict_list[i] self.assertIsInstance(pd1, dict) self.assertIsInstance(pd2, dict) @@ -356,14 +389,14 @@ def test_common_metadata_basics(self): self.assertEqual(x.common_metadata.title, title) x.common_metadata.title = example_title self.assertEqual(x.common_metadata.title, example_title) - self.assertEqual(x.properties['title'], example_title) + self.assertEqual(x.properties["title"], example_title) # Description example_description = "example description" self.assertIsNone(x.common_metadata.description) x.common_metadata.description = example_description self.assertEqual(x.common_metadata.description, example_description) - self.assertEqual(x.properties['description'], example_description) + self.assertEqual(x.properties["description"], example_description) # License license = "PDDL-1.0" @@ -371,7 +404,7 @@ def test_common_metadata_basics(self): self.assertEqual(x.common_metadata.license, license) x.common_metadata.license = example_license self.assertEqual(x.common_metadata.license, example_license) - self.assertEqual(x.properties['license'], example_license) + self.assertEqual(x.properties["license"], example_license) # Platform platform = "coolsat2" @@ -379,7 +412,7 @@ def test_common_metadata_basics(self): self.assertEqual(x.common_metadata.platform, platform) x.common_metadata.platform = example_platform self.assertEqual(x.common_metadata.platform, example_platform) - self.assertEqual(x.properties['platform'], example_platform) + self.assertEqual(x.properties["platform"], example_platform) # Instruments instruments = ["cool_sensor_v1"] @@ -387,21 +420,21 @@ def test_common_metadata_basics(self): self.assertListEqual(x.common_metadata.instruments or [], instruments) x.common_metadata.instruments = example_instruments self.assertListEqual(x.common_metadata.instruments, example_instruments) - self.assertListEqual(x.properties['instruments'], example_instruments) + self.assertListEqual(x.properties["instruments"], example_instruments) # Constellation example_constellation = "example constellation" self.assertIsNone(x.common_metadata.constellation) x.common_metadata.constellation = example_constellation self.assertEqual(x.common_metadata.constellation, example_constellation) - self.assertEqual(x.properties['constellation'], example_constellation) + self.assertEqual(x.properties["constellation"], example_constellation) # Mission example_mission = "example mission" self.assertIsNone(x.common_metadata.mission) x.common_metadata.mission = example_mission self.assertEqual(x.common_metadata.mission, example_mission) - self.assertEqual(x.properties['mission'], example_mission) + self.assertEqual(x.properties["mission"], example_mission) # GSD gsd = 0.512 @@ -409,250 +442,269 @@ def test_common_metadata_basics(self): self.assertEqual(x.common_metadata.gsd, gsd) x.common_metadata.gsd = example_gsd self.assertEqual(x.common_metadata.gsd, example_gsd) - self.assertEqual(x.properties['gsd'], example_gsd) + self.assertEqual(x.properties["gsd"], example_gsd) def test_asset_start_datetime(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.start_datetime a2_known_value = str_to_datetime("2017-05-01T13:22:30.040Z") # Get - a1_value = cm.get_start_datetime(item.assets['analytic']) - a2_value = cm.get_start_datetime(item.assets['thumbnail']) + a1_value = cm.get_start_datetime(item.assets["analytic"]) + a2_value = cm.get_start_datetime(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = str_to_datetime("2014-05-01T13:22:30.040Z") - cm.set_start_datetime(set_value, item.assets['analytic']) - new_a1_value = cm.get_start_datetime(item.assets['analytic']) + cm.set_start_datetime(set_value, item.assets["analytic"]) + new_a1_value = cm.get_start_datetime(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.start_datetime, item_value) def test_asset_end_datetime(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.end_datetime a2_known_value = str_to_datetime("2017-05-02T13:22:30.040Z") # Get - a1_value = cm.get_end_datetime(item.assets['analytic']) - a2_value = cm.get_end_datetime(item.assets['thumbnail']) + a1_value = cm.get_end_datetime(item.assets["analytic"]) + a2_value = cm.get_end_datetime(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = str_to_datetime("2014-05-01T13:22:30.040Z") - cm.set_end_datetime(set_value, item.assets['analytic']) - new_a1_value = cm.get_end_datetime(item.assets['analytic']) + cm.set_end_datetime(set_value, item.assets["analytic"]) + new_a1_value = cm.get_end_datetime(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.end_datetime, item_value) def test_asset_license(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.license - a2_known_value = 'CC-BY-4.0' + a2_known_value = "CC-BY-4.0" # Get - a1_value = cm.get_license(item.assets['analytic']) - a2_value = cm.get_license(item.assets['thumbnail']) + a1_value = cm.get_license(item.assets["analytic"]) + a2_value = cm.get_license(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set - set_value = 'various' - cm.set_license(set_value, item.assets['analytic']) - new_a1_value = cm.get_license(item.assets['analytic']) + set_value = "various" + cm.set_license(set_value, item.assets["analytic"]) + new_a1_value = cm.get_license(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.license, item_value) def test_asset_providers(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = get_opt(cm.providers) a2_known_value = [ - ps.Provider(name="USGS", - url="https://landsat.usgs.gov/", - roles=["producer", "licensor"]) + ps.Provider( + name="USGS", + url="https://landsat.usgs.gov/", + roles=["producer", "licensor"], + ) ] # Get - a1_value: List[Provider] = get_opt(cm.get_providers(item.assets['analytic'])) - a2_value: List[Provider] = get_opt(cm.get_providers(item.assets['thumbnail'])) + a1_value: List[Provider] = get_opt(cm.get_providers(item.assets["analytic"])) + a2_value: List[Provider] = get_opt(cm.get_providers(item.assets["thumbnail"])) self.assertEqual(a1_value[0].to_dict(), item_value[0].to_dict()) self.assertNotEqual(a2_value[0].to_dict(), item_value[0].to_dict()) self.assertEqual(a2_value[0].to_dict(), a2_known_value[0].to_dict()) # Set - set_value = [ps.Provider(name="John Snow", url="https://cholera.com/", roles=["producer"])] - cm.set_providers(set_value, item.assets['analytic']) - new_a1_value: List[Provider] = get_opt(cm.get_providers(item.assets['analytic'])) + set_value = [ + ps.Provider( + name="John Snow", url="https://cholera.com/", roles=["producer"] + ) + ] + cm.set_providers(set_value, item.assets["analytic"]) + new_a1_value: List[Provider] = get_opt( + cm.get_providers(item.assets["analytic"]) + ) self.assertEqual(new_a1_value[0].to_dict(), set_value[0].to_dict()) self.assertEqual(get_opt(cm.providers)[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.platform - a2_known_value = 'shoes' + a2_known_value = "shoes" # Get - a1_value = cm.get_platform(item.assets['analytic']) - a2_value = cm.get_platform(item.assets['thumbnail']) + a1_value = cm.get_platform(item.assets["analytic"]) + a2_value = cm.get_platform(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set - set_value = 'brick' - cm.set_platform(set_value, item.assets['analytic']) - new_a1_value = cm.get_platform(item.assets['analytic']) + set_value = "brick" + cm.set_platform(set_value, item.assets["analytic"]) + new_a1_value = cm.get_platform(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.platform, item_value) def test_asset_instruments(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.instruments a2_known_value = ["caliper"] # Get - a1_value = cm.get_instruments(item.assets['analytic']) - a2_value = cm.get_instruments(item.assets['thumbnail']) + a1_value = cm.get_instruments(item.assets["analytic"]) + a2_value = cm.get_instruments(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = ["horns"] - cm.set_instruments(set_value, item.assets['analytic']) - new_a1_value = cm.get_instruments(item.assets['analytic']) + cm.set_instruments(set_value, item.assets["analytic"]) + new_a1_value = cm.get_instruments(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.instruments, item_value) def test_asset_constellation(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.constellation - a2_known_value = 'little dipper' + a2_known_value = "little dipper" # Get - a1_value = cm.get_constellation(item.assets['analytic']) - a2_value = cm.get_constellation(item.assets['thumbnail']) + a1_value = cm.get_constellation(item.assets["analytic"]) + a2_value = cm.get_constellation(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set - set_value = 'orion' - cm.set_constellation(set_value, item.assets['analytic']) - new_a1_value = cm.get_constellation(item.assets['analytic']) + set_value = "orion" + cm.set_constellation(set_value, item.assets["analytic"]) + new_a1_value = cm.get_constellation(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.constellation, item_value) def test_asset_mission(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.mission - a2_known_value = 'possible' + a2_known_value = "possible" # Get - a1_value = cm.get_mission(item.assets['analytic']) - a2_value = cm.get_mission(item.assets['thumbnail']) + a1_value = cm.get_mission(item.assets["analytic"]) + a2_value = cm.get_mission(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set - set_value = 'critical' - cm.set_mission(set_value, item.assets['analytic']) - new_a1_value = cm.get_mission(item.assets['analytic']) + set_value = "critical" + cm.set_mission(set_value, item.assets["analytic"]) + new_a1_value = cm.get_mission(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.mission, item_value) def test_asset_gsd(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.gsd a2_known_value = 40 # Get - a1_value = cm.get_gsd(item.assets['analytic']) - a2_value = cm.get_gsd(item.assets['thumbnail']) + a1_value = cm.get_gsd(item.assets["analytic"]) + a2_value = cm.get_gsd(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = 100 - cm.set_gsd(set_value, item.assets['analytic']) - new_a1_value = cm.get_gsd(item.assets['analytic']) + cm.set_gsd(set_value, item.assets["analytic"]) + new_a1_value = cm.get_gsd(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.gsd, item_value) def test_asset_created(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.created a2_known_value = str_to_datetime("2017-05-17T13:22:30.040Z") # Get - a1_value = cm.get_created(item.assets['analytic']) - a2_value = cm.get_created(item.assets['thumbnail']) + a1_value = cm.get_created(item.assets["analytic"]) + a2_value = cm.get_created(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = str_to_datetime("2014-05-17T13:22:30.040Z") - cm.set_created(set_value, item.assets['analytic']) - new_a1_value = cm.get_created(item.assets['analytic']) + cm.set_created(set_value, item.assets["analytic"]) + new_a1_value = cm.get_created(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.created, item_value) def test_asset_updated(self): item = ps.Item.from_file( - TestCases.get_path('data-files/item/sample-item-asset-properties.json')) + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) cm = item.common_metadata item_value = cm.updated a2_known_value = str_to_datetime("2017-05-18T13:22:30.040Z") # Get - a1_value = cm.get_updated(item.assets['analytic']) - a2_value = cm.get_updated(item.assets['thumbnail']) + a1_value = cm.get_updated(item.assets["analytic"]) + a2_value = cm.get_updated(item.assets["thumbnail"]) self.assertEqual(a1_value, item_value) self.assertNotEqual(a2_value, item_value) self.assertEqual(a2_value, a2_known_value) # Set set_value = str_to_datetime("2014-05-18T13:22:30.040Z") - cm.set_updated(set_value, item.assets['analytic']) - new_a1_value = cm.get_updated(item.assets['analytic']) + cm.set_updated(set_value, item.assets["analytic"]) + new_a1_value = cm.get_updated(item.assets["analytic"]) self.assertEqual(new_a1_value, set_value) self.assertEqual(cm.updated, item_value) diff --git a/tests/test_layout.py b/tests/test_layout.py index d518133a6..4333cdce6 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -1,13 +1,18 @@ -from datetime import (datetime, timedelta) +from datetime import datetime, timedelta import os from typing import Callable from pystac.collection import Collection import unittest import pystac as ps -from pystac.layout import (LayoutTemplate, CustomLayoutStrategy, TemplateLayoutStrategy, - BestPracticesLayoutStrategy, TemplateError) -from tests.utils import (TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX) +from pystac.layout import ( + LayoutTemplate, + CustomLayoutStrategy, + TemplateLayoutStrategy, + BestPracticesLayoutStrategy, + TemplateError, +) +from tests.utils import TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX class LayoutTemplateTest(unittest.TestCase): @@ -15,78 +20,82 @@ def test_templates_item_datetime(self): year = 2020 month = 11 day = 3 - date = '2020-11-03' + date = "2020-11-03" dt = datetime(year, month, day, 18, 30) - template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') + template = LayoutTemplate("${year}/${month}/${day}/${date}/item.json") - item = ps.Item('test', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=dt, - properties={}) + item = ps.Item( + "test", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=dt, + properties={}, + ) parts = template.get_template_values(item) - self.assertEqual(set(parts), set(['year', 'month', 'day', 'date'])) + self.assertEqual(set(parts), set(["year", "month", "day", "date"])) - self.assertEqual(parts['year'], year) - self.assertEqual(parts['month'], month) - self.assertEqual(parts['day'], day) - self.assertEqual(parts['date'], date) + self.assertEqual(parts["year"], year) + self.assertEqual(parts["month"], month) + self.assertEqual(parts["day"], day) + self.assertEqual(parts["date"], date) path = template.substitute(item) - self.assertEqual(path, '2020/11/3/2020-11-03/item.json') + self.assertEqual(path, "2020/11/3/2020-11-03/item.json") def test_templates_item_start_datetime(self): year = 2020 month = 11 day = 3 - date = '2020-11-03' + date = "2020-11-03" dt = datetime(year, month, day, 18, 30) - template = LayoutTemplate('${year}/${month}/${day}/${date}/item.json') + template = LayoutTemplate("${year}/${month}/${day}/${date}/item.json") - item = ps.Item('test', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=None, - properties={ - 'start_datetime': dt.isoformat(), - 'end_datetime': (dt + timedelta(days=1)).isoformat() - }) + item = ps.Item( + "test", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=None, + properties={ + "start_datetime": dt.isoformat(), + "end_datetime": (dt + timedelta(days=1)).isoformat(), + }, + ) parts = template.get_template_values(item) - self.assertEqual(set(parts), set(['year', 'month', 'day', 'date'])) + self.assertEqual(set(parts), set(["year", "month", "day", "date"])) - self.assertEqual(parts['year'], year) - self.assertEqual(parts['month'], month) - self.assertEqual(parts['day'], day) - self.assertEqual(parts['date'], date) + self.assertEqual(parts["year"], year) + self.assertEqual(parts["month"], month) + self.assertEqual(parts["day"], day) + self.assertEqual(parts["date"], date) path = template.substitute(item) - self.assertEqual(path, '2020/11/3/2020-11-03/item.json') + self.assertEqual(path, "2020/11/3/2020-11-03/item.json") def test_templates_item_collection(self): - template = LayoutTemplate('${collection}/item.json') + template = LayoutTemplate("${collection}/item.json") - collection = TestCases.test_case_4().get_child('acc') + collection = TestCases.test_case_4().get_child("acc") item = next(iter(collection.get_all_items())) assert item.collection_id is not None parts = template.get_template_values(item) self.assertEqual(len(parts), 1) - self.assertIn('collection', parts) - self.assertEqual(parts['collection'], item.collection_id) + self.assertIn("collection", parts) + self.assertEqual(parts["collection"], item.collection_id) path = template.substitute(item) - self.assertEqual(path, '{}/item.json'.format(item.collection_id)) + self.assertEqual(path, "{}/item.json".format(item.collection_id)) def test_throws_for_no_collection(self): - template = LayoutTemplate('${collection}/item.json') + template = LayoutTemplate("${collection}/item.json") - collection = TestCases.test_case_4().get_child('acc') + collection = TestCases.test_case_4().get_child("acc") item = next(iter(collection.get_all_items())) item.set_collection(None) assert item.collection_id is None @@ -97,235 +106,264 @@ def test_throws_for_no_collection(self): def test_nested_properties(self): dt = datetime(2020, 11, 3, 18, 30) - template = LayoutTemplate('${test.prop}/${ext:extra.test.prop}/item.json') - - item = ps.Item('test', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=dt, - properties={'test': { - 'prop': 4326 - }}, - extra_fields={'ext:extra': { - 'test': { - 'prop': 3857 - } - }}) + template = LayoutTemplate("${test.prop}/${ext:extra.test.prop}/item.json") + + item = ps.Item( + "test", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=dt, + properties={"test": {"prop": 4326}}, + extra_fields={"ext:extra": {"test": {"prop": 3857}}}, + ) parts = template.get_template_values(item) - self.assertEqual(set(parts), set(['test.prop', 'ext:extra.test.prop'])) + self.assertEqual(set(parts), set(["test.prop", "ext:extra.test.prop"])) - self.assertEqual(parts['test.prop'], 4326) - self.assertEqual(parts['ext:extra.test.prop'], 3857) + self.assertEqual(parts["test.prop"], 4326) + self.assertEqual(parts["ext:extra.test.prop"], 3857) path = template.substitute(item) - self.assertEqual(path, '4326/3857/item.json') + self.assertEqual(path, "4326/3857/item.json") def test_substitute_with_colon_properties(self): dt = datetime(2020, 11, 3, 18, 30) - template = LayoutTemplate('${ext:prop}/item.json') + template = LayoutTemplate("${ext:prop}/item.json") - item = ps.Item('test', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=dt, - properties={'ext:prop': 1}) + item = ps.Item( + "test", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=dt, + properties={"ext:prop": 1}, + ) path = template.substitute(item) self.assertEqual(path, "1/item.json") def test_defaults(self): - template = LayoutTemplate('${doesnotexist}/collection.json', - defaults={'doesnotexist': 'yes'}) + template = LayoutTemplate( + "${doesnotexist}/collection.json", defaults={"doesnotexist": "yes"} + ) - catalog = TestCases.test_case_4().get_child('acc') + catalog = TestCases.test_case_4().get_child("acc") assert catalog is not None - catalog.extra_fields = {'one': 'two'} + catalog.extra_fields = {"one": "two"} path = template.substitute(catalog) - self.assertEqual(path, 'yes/collection.json') + self.assertEqual(path, "yes/collection.json") def test_docstring_examples(self): item = ps.Item.from_file( TestCases.get_path( - "data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json")) + "data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json" + ) + ) item.common_metadata.license = "CC-BY-3.0" # Uses the year, month and day of the item template1 = LayoutTemplate("${year}/${month}/${day}") path1 = template1.substitute(item) - self.assertEqual(path1, '2014/6/2') + self.assertEqual(path1, "2014/6/2") # Uses a custom extension properties found on in the item properties. template2 = LayoutTemplate("${landsat:path}/${landsat:row}") path2 = template2.substitute(item) - self.assertEqual(path2, '107/18') + self.assertEqual(path2, "107/18") # Uses the collection ID and a common metadata property for an item. template3 = LayoutTemplate("${collection}/${common_metadata.license}") path3 = template3.substitute(item) - self.assertEqual(path3, 'landsat-8-l1/CC-BY-3.0') + self.assertEqual(path3, "landsat-8-l1/CC-BY-3.0") class CustomLayoutStrategyTest(unittest.TestCase): def get_custom_catalog_func(self) -> Callable[[ps.Catalog, str, bool], str]: def fn(cat: ps.Catalog, parent_dir: str, is_root: bool): - return os.path.join(parent_dir, 'cat/{}/{}.json'.format(is_root, cat.id)) + return os.path.join(parent_dir, "cat/{}/{}.json".format(is_root, cat.id)) return fn def get_custom_collection_func(self) -> Callable[[ps.Collection, str, bool], str]: def fn(col: ps.Collection, parent_dir: str, is_root: bool): - return os.path.join(parent_dir, 'col/{}/{}.json'.format(is_root, col.id)) + return os.path.join(parent_dir, "col/{}/{}.json".format(is_root, col.id)) return fn def get_custom_item_func(self) -> Callable[[ps.Item, str], str]: def fn(item: ps.Item, parent_dir: str): - return os.path.join(parent_dir, 'item/{}.json'.format(item.id)) + return os.path.join(parent_dir, "item/{}.json".format(item.id)) return fn def test_produces_layout_for_catalog(self): strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func()) - cat = ps.Catalog(id='test', description='test desc') - href = strategy.get_href(cat, parent_dir='http://example.com', is_root=True) - self.assertEqual(href, 'http://example.com/cat/True/test.json') + cat = ps.Catalog(id="test", description="test desc") + href = strategy.get_href(cat, parent_dir="http://example.com", is_root=True) + self.assertEqual(href, "http://example.com/cat/True/test.json") def test_produces_fallback_layout_for_catalog(self): fallback = BestPracticesLayoutStrategy() - strategy = CustomLayoutStrategy(collection_func=self.get_custom_collection_func(), - item_func=self.get_custom_item_func(), - fallback_strategy=fallback) - cat = ps.Catalog(id='test', description='test desc') - href = strategy.get_href(cat, parent_dir='http://example.com') - expected = fallback.get_href(cat, parent_dir='http://example.com') + strategy = CustomLayoutStrategy( + collection_func=self.get_custom_collection_func(), + item_func=self.get_custom_item_func(), + fallback_strategy=fallback, + ) + cat = ps.Catalog(id="test", description="test desc") + href = strategy.get_href(cat, parent_dir="http://example.com") + expected = fallback.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, expected) def test_produces_layout_for_collection(self): - strategy = CustomLayoutStrategy(collection_func=self.get_custom_collection_func()) + strategy = CustomLayoutStrategy( + collection_func=self.get_custom_collection_func() + ) collection = TestCases.test_case_8() - href = strategy.get_href(collection, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/col/False/{}.json'.format(collection.id)) + href = strategy.get_href(collection, parent_dir="http://example.com") + self.assertEqual( + href, "http://example.com/col/False/{}.json".format(collection.id) + ) def test_produces_fallback_layout_for_collection(self): fallback = BestPracticesLayoutStrategy() - strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func(), - item_func=self.get_custom_item_func(), - fallback_strategy=fallback) + strategy = CustomLayoutStrategy( + catalog_func=self.get_custom_catalog_func(), + item_func=self.get_custom_item_func(), + fallback_strategy=fallback, + ) collection = TestCases.test_case_8() - href = strategy.get_href(collection, parent_dir='http://example.com') - expected = fallback.get_href(collection, parent_dir='http://example.com') + href = strategy.get_href(collection, parent_dir="http://example.com") + expected = fallback.get_href(collection, parent_dir="http://example.com") self.assertEqual(href, expected) def test_produces_layout_for_item(self): strategy = CustomLayoutStrategy(item_func=self.get_custom_item_func()) collection = TestCases.test_case_8() item = next(iter(collection.get_all_items())) - href = strategy.get_href(item, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/item/{}.json'.format(item.id)) + href = strategy.get_href(item, parent_dir="http://example.com") + self.assertEqual(href, "http://example.com/item/{}.json".format(item.id)) def test_produces_fallback_layout_for_item(self): fallback = BestPracticesLayoutStrategy() - strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func(), - collection_func=self.get_custom_collection_func(), - fallback_strategy=fallback) + strategy = CustomLayoutStrategy( + catalog_func=self.get_custom_catalog_func(), + collection_func=self.get_custom_collection_func(), + fallback_strategy=fallback, + ) collection = TestCases.test_case_8() item = next(iter(collection.get_all_items())) - href = strategy.get_href(item, parent_dir='http://example.com') - expected = fallback.get_href(item, parent_dir='http://example.com') + href = strategy.get_href(item, parent_dir="http://example.com") + expected = fallback.get_href(item, parent_dir="http://example.com") self.assertEqual(href, expected) class TemplateLayoutStrategyTest(unittest.TestCase): - TEST_CATALOG_TEMPLATE = 'cat/${id}/${description}' - TEST_COLLECTION_TEMPLATE = 'col/${id}/${license}' - TEST_ITEM_TEMPLATE = 'item/${collection}/${id}.json' + TEST_CATALOG_TEMPLATE = "cat/${id}/${description}" + TEST_COLLECTION_TEMPLATE = "col/${id}/${license}" + TEST_ITEM_TEMPLATE = "item/${collection}/${id}.json" def _get_collection(self) -> Collection: - result = TestCases.test_case_4().get_child('acc') + result = TestCases.test_case_4().get_child("acc") assert isinstance(result, Collection) return result def test_produces_layout_for_catalog(self): strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE) - cat = ps.Catalog(id='test', description='test-desc') - href = strategy.get_href(cat, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/cat/test/test-desc/catalog.json') + cat = ps.Catalog(id="test", description="test-desc") + href = strategy.get_href(cat, parent_dir="http://example.com") + self.assertEqual(href, "http://example.com/cat/test/test-desc/catalog.json") def test_produces_layout_for_catalog_with_filename(self): - template = 'cat/${id}/${description}/${id}.json' + template = "cat/${id}/${description}/${id}.json" strategy = TemplateLayoutStrategy(catalog_template=template) - cat = ps.Catalog(id='test', description='test-desc') - href = strategy.get_href(cat, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/cat/test/test-desc/test.json') + cat = ps.Catalog(id="test", description="test-desc") + href = strategy.get_href(cat, parent_dir="http://example.com") + self.assertEqual(href, "http://example.com/cat/test/test-desc/test.json") def test_produces_fallback_layout_for_catalog(self): fallback = BestPracticesLayoutStrategy() - strategy = TemplateLayoutStrategy(collection_template=self.TEST_COLLECTION_TEMPLATE, - item_template=self.TEST_ITEM_TEMPLATE, - fallback_strategy=fallback) - cat = ps.Catalog(id='test', description='test desc') - href = strategy.get_href(cat, parent_dir='http://example.com') - expected = fallback.get_href(cat, parent_dir='http://example.com') + strategy = TemplateLayoutStrategy( + collection_template=self.TEST_COLLECTION_TEMPLATE, + item_template=self.TEST_ITEM_TEMPLATE, + fallback_strategy=fallback, + ) + cat = ps.Catalog(id="test", description="test desc") + href = strategy.get_href(cat, parent_dir="http://example.com") + expected = fallback.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, expected) def test_produces_layout_for_collection(self): - strategy = TemplateLayoutStrategy(collection_template=self.TEST_COLLECTION_TEMPLATE) + strategy = TemplateLayoutStrategy( + collection_template=self.TEST_COLLECTION_TEMPLATE + ) collection = self._get_collection() - href = strategy.get_href(collection, parent_dir='http://example.com') + href = strategy.get_href(collection, parent_dir="http://example.com") self.assertEqual( href, - 'http://example.com/col/{}/{}/collection.json'.format(collection.id, - collection.license)) + "http://example.com/col/{}/{}/collection.json".format( + collection.id, collection.license + ), + ) def test_produces_layout_for_collection_with_filename(self): - template = 'col/${id}/${license}/col.json' + template = "col/${id}/${license}/col.json" strategy = TemplateLayoutStrategy(collection_template=template) collection = self._get_collection() - href = strategy.get_href(collection, parent_dir='http://example.com') + href = strategy.get_href(collection, parent_dir="http://example.com") self.assertEqual( - href, 'http://example.com/col/{}/{}/col.json'.format(collection.id, collection.license)) + href, + "http://example.com/col/{}/{}/col.json".format( + collection.id, collection.license + ), + ) def test_produces_fallback_layout_for_collection(self): fallback = BestPracticesLayoutStrategy() - strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE, - item_template=self.TEST_ITEM_TEMPLATE, - fallback_strategy=fallback) + strategy = TemplateLayoutStrategy( + catalog_template=self.TEST_CATALOG_TEMPLATE, + item_template=self.TEST_ITEM_TEMPLATE, + fallback_strategy=fallback, + ) collection = self._get_collection() - href = strategy.get_href(collection, parent_dir='http://example.com') - expected = fallback.get_href(collection, parent_dir='http://example.com') + href = strategy.get_href(collection, parent_dir="http://example.com") + expected = fallback.get_href(collection, parent_dir="http://example.com") self.assertEqual(href, expected) def test_produces_layout_for_item(self): strategy = TemplateLayoutStrategy(item_template=self.TEST_ITEM_TEMPLATE) collection = self._get_collection() item = next(iter(collection.get_all_items())) - href = strategy.get_href(item, parent_dir='http://example.com') - self.assertEqual(href, - 'http://example.com/item/{}/{}.json'.format(item.collection_id, item.id)) + href = strategy.get_href(item, parent_dir="http://example.com") + self.assertEqual( + href, + "http://example.com/item/{}/{}.json".format(item.collection_id, item.id), + ) def test_produces_layout_for_item_without_filename(self): - template = 'item/${collection}' + template = "item/${collection}" strategy = TemplateLayoutStrategy(item_template=template) collection = self._get_collection() item = next(iter(collection.get_all_items())) - href = strategy.get_href(item, parent_dir='http://example.com') - self.assertEqual(href, - 'http://example.com/item/{}/{}.json'.format(item.collection_id, item.id)) + href = strategy.get_href(item, parent_dir="http://example.com") + self.assertEqual( + href, + "http://example.com/item/{}/{}.json".format(item.collection_id, item.id), + ) def test_produces_fallback_layout_for_item(self): fallback = BestPracticesLayoutStrategy() - strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE, - collection_template=self.TEST_COLLECTION_TEMPLATE, - fallback_strategy=fallback) + strategy = TemplateLayoutStrategy( + catalog_template=self.TEST_CATALOG_TEMPLATE, + collection_template=self.TEST_COLLECTION_TEMPLATE, + fallback_strategy=fallback, + ) collection = self._get_collection() item = next(iter(collection.get_all_items())) - href = strategy.get_href(item, parent_dir='http://example.com') - expected = fallback.get_href(item, parent_dir='http://example.com') + href = strategy.get_href(item, parent_dir="http://example.com") + expected = fallback.get_href(item, parent_dir="http://example.com") self.assertEqual(href, expected) @@ -334,28 +372,34 @@ def setUp(self): self.strategy = BestPracticesLayoutStrategy() def test_produces_layout_for_root_catalog(self): - cat = ps.Catalog(id='test', description='test desc') - href = self.strategy.get_href(cat, parent_dir='http://example.com', is_root=True) - self.assertEqual(href, 'http://example.com/catalog.json') + cat = ps.Catalog(id="test", description="test desc") + href = self.strategy.get_href( + cat, parent_dir="http://example.com", is_root=True + ) + self.assertEqual(href, "http://example.com/catalog.json") def test_produces_layout_for_child_catalog(self): - cat = ps.Catalog(id='test', description='test desc') - href = self.strategy.get_href(cat, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/test/catalog.json') + cat = ps.Catalog(id="test", description="test desc") + href = self.strategy.get_href(cat, parent_dir="http://example.com") + self.assertEqual(href, "http://example.com/test/catalog.json") def test_produces_layout_for_root_collection(self): collection = TestCases.test_case_8() - href = self.strategy.get_href(collection, parent_dir='http://example.com', is_root=True) - self.assertEqual(href, 'http://example.com/collection.json') + href = self.strategy.get_href( + collection, parent_dir="http://example.com", is_root=True + ) + self.assertEqual(href, "http://example.com/collection.json") def test_produces_layout_for_child_collection(self): collection = TestCases.test_case_8() - href = self.strategy.get_href(collection, parent_dir='http://example.com') - self.assertEqual(href, 'http://example.com/{}/collection.json'.format(collection.id)) + href = self.strategy.get_href(collection, parent_dir="http://example.com") + self.assertEqual( + href, "http://example.com/{}/collection.json".format(collection.id) + ) def test_produces_layout_for_item(self): collection = TestCases.test_case_8() item = next(iter(collection.get_all_items())) - href = self.strategy.get_href(item, parent_dir='http://example.com') - expected = 'http://example.com/{}/{}.json'.format(item.id, item.id) + href = self.strategy.get_href(item, parent_dir="http://example.com") + expected = "http://example.com/{}/{}.json".format(item.id, item.id) self.assertEqual(href, expected) diff --git a/tests/test_link.py b/tests/test_link.py index 67734fc8b..8639eb101 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -11,25 +11,27 @@ class LinkTest(unittest.TestCase): item: ps.Item def setUp(self): - self.item = ps.Item(id='test-item', - geometry=None, - bbox=None, - datetime=TEST_DATETIME, - properties={}) + self.item = ps.Item( + id="test-item", + geometry=None, + bbox=None, + datetime=TEST_DATETIME, + properties={}, + ) def test_minimal(self): - rel = 'my rel' - target = 'https://example.com/a/b' + rel = "my rel" + target = "https://example.com/a/b" link = ps.Link(rel, target) self.assertEqual(target, link.get_href()) self.assertEqual(target, link.get_absolute_href()) - expected_repr = f'' + expected_repr = f"" self.assertEqual(expected_repr, link.__repr__()) self.assertFalse(link.is_resolved()) - expected_dict = {'rel': rel, 'href': target} + expected_dict = {"rel": rel, "href": target} self.assertEqual(expected_dict, link.to_dict()) # Run the same tests on the clone. @@ -52,79 +54,60 @@ def test_minimal(self): self.assertEqual(self.item, link.owner) def test_relative(self): - rel = 'my rel' - target = '../elsewhere' - mime_type = 'example/stac_thing' - link = ps.Link(rel, target, mime_type, 'a title', properties={'a': 'b'}) + rel = "my rel" + target = "../elsewhere" + mime_type = "example/stac_thing" + link = ps.Link(rel, target, mime_type, "a title", properties={"a": "b"}) expected_dict = { - 'rel': rel, - 'href': target, - 'type': 'example/stac_thing', - 'title': 'a title', - 'a': 'b' + "rel": rel, + "href": target, + "type": "example/stac_thing", + "title": "a title", + "a": "b", } self.assertEqual(expected_dict, link.to_dict()) 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 = ps.Catalog(id='test', description='test desc') + catalog = ps.Catalog(id="test", description="test desc") catalog.add_item(self.item) - catalog.set_self_href('/some/href') + catalog.set_self_href("/some/href") - link = catalog.get_single_link('item') + link = catalog.get_single_link("item") self.assertIsNone(link.get_href()) def test_resolve_stac_object_no_root_and_target_is_item(self): - link = ps.Link('my rel', target=self.item) + link = ps.Link("my rel", target=self.item) link.resolve_stac_object() class StaticLinkTest(unittest.TestCase): def test_from_dict_round_trip(self): test_cases = [ - { - 'rel': '', - 'href': '' - }, # Not valid, but works. - { - 'rel': 'r', - 'href': 't' - }, - { - 'rel': 'r', - 'href': '/t' - }, - { - 'rel': 'r', - 'href': 't', - 'type': 'a/b', - 'title': 't', - 'c': 'd', - 1: 2 - }, + {"rel": "", "href": ""}, # Not valid, but works. + {"rel": "r", "href": "t"}, + {"rel": "r", "href": "/t"}, + {"rel": "r", "href": "t", "type": "a/b", "title": "t", "c": "d", 1: 2}, # Special case. - { - 'rel': 'self', - 'href': 't' - }, + {"rel": "self", "href": "t"}, ] for d in test_cases: d2 = ps.Link.from_dict(d).to_dict() self.assertEqual(d, d2) def test_from_dict_failures(self): - for d in [{}, {'href': 't'}, {'rel': 'r'}]: + for d in [{}, {"href": "t"}, {"rel": "r"}]: with self.assertRaises(KeyError): ps.Link.from_dict(d) def test_collection(self): - c = ps.Collection('collection id', 'desc', extent=ARBITRARY_EXTENT) + c = ps.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) link = ps.Link.collection(c) - expected = {'rel': 'collection', 'href': None, 'type': 'application/json'} + expected = {"rel": "collection", "href": None, "type": "application/json"} self.assertEqual(expected, link.to_dict()) def test_child(self): - c = ps.Collection('collection id', 'desc', extent=ARBITRARY_EXTENT) + c = ps.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) link = ps.Link.child(c) - expected = {'rel': 'child', 'href': None, 'type': 'application/json'} + expected = {"rel": "child", "href": None, "type": "application/json"} self.assertEqual(expected, link.to_dict()) diff --git a/tests/test_utils.py b/tests/test_utils.py index 4a314995f..639df2a35 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,26 +6,41 @@ from pystac import utils -from pystac.utils import (make_relative_href, make_absolute_href, is_absolute_href) +from pystac.utils import make_relative_href, make_absolute_href, is_absolute_href class UtilsTest(unittest.TestCase): def test_make_relative_href(self): # Test cases of (source_href, start_href, expected) test_cases = [ - ('/a/b/c/d/catalog.json', '/a/b/c/catalog.json', './d/catalog.json'), - ('/a/b/catalog.json', '/a/b/c/catalog.json', '../catalog.json'), - ('/a/catalog.json', '/a/b/c/catalog.json', '../../catalog.json'), - ('http://stacspec.org/a/b/c/d/catalog.json', 'http://stacspec.org/a/b/c/catalog.json', - './d/catalog.json'), - ('http://stacspec.org/a/b/catalog.json', 'http://stacspec.org/a/b/c/catalog.json', - '../catalog.json'), - ('http://stacspec.org/a/catalog.json', 'http://stacspec.org/a/b/c/catalog.json', - '../../catalog.json'), - ('http://stacspec.org/a/catalog.json', 'http://cogeo.org/a/b/c/catalog.json', - 'http://stacspec.org/a/catalog.json'), - ('http://stacspec.org/a/catalog.json', 'https://stacspec.org/a/b/c/catalog.json', - 'http://stacspec.org/a/catalog.json') + ("/a/b/c/d/catalog.json", "/a/b/c/catalog.json", "./d/catalog.json"), + ("/a/b/catalog.json", "/a/b/c/catalog.json", "../catalog.json"), + ("/a/catalog.json", "/a/b/c/catalog.json", "../../catalog.json"), + ( + "http://stacspec.org/a/b/c/d/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "./d/catalog.json", + ), + ( + "http://stacspec.org/a/b/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "../catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "../../catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "http://cogeo.org/a/b/c/catalog.json", + "http://stacspec.org/a/catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "https://stacspec.org/a/b/c/catalog.json", + "http://stacspec.org/a/catalog.json", + ), ] for source_href, start_href, expected in test_cases: @@ -37,21 +52,48 @@ def test_make_relative_href_windows(self): try: # Test cases of (source_href, start_href, expected) test_cases = [ - ('C:\\a\\b\\c\\d\\catalog.json', 'C:\\a\\b\\c\\catalog.json', '.\\d\\catalog.json'), - ('C:\\a\\b\\catalog.json', 'C:\\a\\b\\c\\catalog.json', '..\\catalog.json'), - ('C:\\a\\catalog.json', 'C:\\a\\b\\c\\catalog.json', '..\\..\\catalog.json'), - ('a\\b\\c\\catalog.json', 'a\\b\\catalog.json', '.\\c\\catalog.json'), - ('a\\b\\catalog.json', 'a\\b\\c\\catalog.json', '..\\catalog.json'), - ('http://stacspec.org/a/b/c/d/catalog.json', - 'http://stacspec.org/a/b/c/catalog.json', './d/catalog.json'), - ('http://stacspec.org/a/b/catalog.json', 'http://stacspec.org/a/b/c/catalog.json', - '../catalog.json'), - ('http://stacspec.org/a/catalog.json', 'http://stacspec.org/a/b/c/catalog.json', - '../../catalog.json'), - ('http://stacspec.org/a/catalog.json', 'http://cogeo.org/a/b/c/catalog.json', - 'http://stacspec.org/a/catalog.json'), - ('http://stacspec.org/a/catalog.json', 'https://stacspec.org/a/b/c/catalog.json', - 'http://stacspec.org/a/catalog.json') + ( + "C:\\a\\b\\c\\d\\catalog.json", + "C:\\a\\b\\c\\catalog.json", + ".\\d\\catalog.json", + ), + ( + "C:\\a\\b\\catalog.json", + "C:\\a\\b\\c\\catalog.json", + "..\\catalog.json", + ), + ( + "C:\\a\\catalog.json", + "C:\\a\\b\\c\\catalog.json", + "..\\..\\catalog.json", + ), + ("a\\b\\c\\catalog.json", "a\\b\\catalog.json", ".\\c\\catalog.json"), + ("a\\b\\catalog.json", "a\\b\\c\\catalog.json", "..\\catalog.json"), + ( + "http://stacspec.org/a/b/c/d/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "./d/catalog.json", + ), + ( + "http://stacspec.org/a/b/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "../catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "http://stacspec.org/a/b/c/catalog.json", + "../../catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "http://cogeo.org/a/b/c/catalog.json", + "http://stacspec.org/a/catalog.json", + ), + ( + "http://stacspec.org/a/catalog.json", + "https://stacspec.org/a/b/c/catalog.json", + "http://stacspec.org/a/catalog.json", + ), ] for source_href, start_href, expected in test_cases: @@ -62,27 +104,41 @@ def test_make_relative_href_windows(self): def test_make_absolute_href(self): # Test cases of (source_href, start_href, expected) - test_cases = [('item.json', '/a/b/c/catalog.json', '/a/b/c/item.json'), - ('./item.json', '/a/b/c/catalog.json', '/a/b/c/item.json'), - ('./z/item.json', '/a/b/c/catalog.json', '/a/b/c/z/item.json'), - ('../item.json', '/a/b/c/catalog.json', '/a/b/item.json'), - ('item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/item.json'), - ('./item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/item.json'), - ('./z/item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/z/item.json'), - ('../item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/item.json')] + test_cases = [ + ("item.json", "/a/b/c/catalog.json", "/a/b/c/item.json"), + ("./item.json", "/a/b/c/catalog.json", "/a/b/c/item.json"), + ("./z/item.json", "/a/b/c/catalog.json", "/a/b/c/z/item.json"), + ("../item.json", "/a/b/c/catalog.json", "/a/b/item.json"), + ( + "item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/item.json", + ), + ( + "./item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/item.json", + ), + ( + "./z/item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/z/item.json", + ), + ( + "../item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/item.json", + ), + ] for source_href, start_href, expected in test_cases: actual = make_absolute_href(source_href, start_href) self.assertEqual(actual, expected) def test_make_absolute_href_on_vsitar(self): - rel_path = 'some/item.json' - cat_path = '/vsitar//tmp/catalog.tar/catalog.json' - expected = '/vsitar//tmp/catalog.tar/some/item.json' + rel_path = "some/item.json" + cat_path = "/vsitar//tmp/catalog.tar/catalog.json" + expected = "/vsitar//tmp/catalog.tar/some/item.json" self.assertEqual(expected, make_absolute_href(rel_path, cat_path)) @@ -90,19 +146,36 @@ def test_make_absolute_href_windows(self): utils._pathlib = ntpath try: # Test cases of (source_href, start_href, expected) - test_cases = [('item.json', 'C:\\a\\b\\c\\catalog.json', 'c:\\a\\b\\c\\item.json'), - ('.\\item.json', 'C:\\a\\b\\c\\catalog.json', 'c:\\a\\b\\c\\item.json'), - ('.\\z\\item.json', 'Z:\\a\\b\\c\\catalog.json', - 'z:\\a\\b\\c\\z\\item.json'), - ('..\\item.json', 'a:\\a\\b\\c\\catalog.json', 'a:\\a\\b\\item.json'), - ('item.json', 'HTTPS://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/item.json'), - ('./item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/item.json'), - ('./z/item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/c/z/item.json'), - ('../item.json', 'https://stacspec.org/a/b/c/catalog.json', - 'https://stacspec.org/a/b/item.json')] + test_cases = [ + ("item.json", "C:\\a\\b\\c\\catalog.json", "c:\\a\\b\\c\\item.json"), + (".\\item.json", "C:\\a\\b\\c\\catalog.json", "c:\\a\\b\\c\\item.json"), + ( + ".\\z\\item.json", + "Z:\\a\\b\\c\\catalog.json", + "z:\\a\\b\\c\\z\\item.json", + ), + ("..\\item.json", "a:\\a\\b\\c\\catalog.json", "a:\\a\\b\\item.json"), + ( + "item.json", + "HTTPS://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/item.json", + ), + ( + "./item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/item.json", + ), + ( + "./z/item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/c/z/item.json", + ), + ( + "../item.json", + "https://stacspec.org/a/b/c/catalog.json", + "https://stacspec.org/a/b/item.json", + ), + ] for source_href, start_href, expected in test_cases: actual = make_absolute_href(source_href, start_href) @@ -112,8 +185,13 @@ def test_make_absolute_href_windows(self): def test_is_absolute_href(self): # Test cases of (href, expected) - test_cases = [('item.json', False), ('./item.json', False), ('../item.json', False), - ('/item.json', True), ('http://stacspec.org/item.json', True)] + test_cases = [ + ("item.json", False), + ("./item.json", False), + ("../item.json", False), + ("/item.json", True), + ("http://stacspec.org/item.json", True), + ] for href, expected in test_cases: actual = is_absolute_href(href) @@ -124,8 +202,13 @@ def test_is_absolute_href_windows(self): try: # Test cases of (href, expected) - test_cases = [('item.json', False), ('.\\item.json', False), ('..\\item.json', False), - ('c:\\item.json', True), ('http://stacspec.org/item.json', True)] + test_cases = [ + ("item.json", False), + (".\\item.json", False), + ("..\\item.json", False), + ("c:\\item.json", True), + ("http://stacspec.org/item.json", True), + ] for href, expected in test_cases: actual = is_absolute_href(href) @@ -135,11 +218,21 @@ def test_is_absolute_href_windows(self): def test_datetime_to_str(self): cases = ( - ('timezone naive, assume utc', datetime(2000, 1, 1), '2000-01-01T00:00:00Z'), - ('timezone aware, utc', datetime(2000, 1, 1, - tzinfo=timezone.utc), '2000-01-01T00:00:00Z'), - ('timezone aware, utc -7', datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=-7))), - '2000-01-01T00:00:00-07:00'), + ( + "timezone naive, assume utc", + datetime(2000, 1, 1), + "2000-01-01T00:00:00Z", + ), + ( + "timezone aware, utc", + datetime(2000, 1, 1, tzinfo=timezone.utc), + "2000-01-01T00:00:00Z", + ), + ( + "timezone aware, utc -7", + datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=-7))), + "2000-01-01T00:00:00-07:00", + ), ) for title, dt, expected in cases: @@ -149,9 +242,9 @@ def test_datetime_to_str(self): def test_geojson_bbox(self): # Use sample Geojson from https://en.wikipedia.org/wiki/GeoJSON - with open('tests/data-files/geojson/sample.geojson') as sample_geojson: + with open("tests/data-files/geojson/sample.geojson") as sample_geojson: all_features = json.load(sample_geojson) - geom_dicts = [f['geometry'] for f in all_features['features']] + geom_dicts = [f["geometry"] for f in all_features["features"]] for geom in geom_dicts: got = utils.geometry_to_bbox(geom) self.assertNotEqual(got, None) diff --git a/tests/test_version.py b/tests/test_version.py index 04aa8d1a1..4598b025f 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -7,27 +7,27 @@ class VersionTest(unittest.TestCase): def setUp(self): - self._prev_env_version = os.environ.get('PYSTAC_STAC_VERSION_OVERRIDE') + self._prev_env_version = os.environ.get("PYSTAC_STAC_VERSION_OVERRIDE") self._prev_version = ps.get_stac_version() def tearDown(self): if self._prev_env_version is None: - os.environ.pop('PYSTAC_STAC_VERSION_OVERRIDE', None) + os.environ.pop("PYSTAC_STAC_VERSION_OVERRIDE", None) else: - os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = self._prev_env_version + os.environ["PYSTAC_STAC_VERSION_OVERRIDE"] = self._prev_env_version ps.set_stac_version(None) def test_override_stac_version_with_environ(self): - override_version = '1.0.0-gamma.2' - os.environ['PYSTAC_STAC_VERSION_OVERRIDE'] = override_version + override_version = "1.0.0-gamma.2" + os.environ["PYSTAC_STAC_VERSION_OVERRIDE"] = override_version cat = TestCases.test_case_1() d = cat.to_dict() - self.assertEqual(d['stac_version'], override_version) + self.assertEqual(d["stac_version"], override_version) def test_override_stac_version_with_call(self): - override_version = '1.0.0-delta.2' + override_version = "1.0.0-delta.2" ps.set_stac_version(override_version) cat = TestCases.test_case_1() d = cat.to_dict() - self.assertEqual(d['stac_version'], override_version) + self.assertEqual(d["stac_version"], override_version) diff --git a/tests/test_writing.py b/tests/test_writing.py index 9ce484e18..44af0f732 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -2,7 +2,7 @@ from tempfile import TemporaryDirectory import pystac as ps -from pystac import (Collection, CatalogType, HIERARCHICAL_LINKS) +from pystac import Collection, CatalogType, HIERARCHICAL_LINKS from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href from pystac.validation import validate_dict @@ -13,6 +13,7 @@ class STACWritingTest(unittest.TestCase): """Tests writing STACs, using JSON Schema validation, and ensure that links are correctly set to relative or absolute. """ + def validate_catalog(self, catalog: ps.Catalog): catalog.validate() validated_count = 1 @@ -43,45 +44,56 @@ def validate_asset_href_type(item: ps.Item, item_href: str): else: self.assertTrue(is_valid) - def validate_item_link_type(href: str, link_type: str, should_include_self: bool): + def validate_item_link_type( + href: str, link_type: str, should_include_self: bool + ): item_dict = ps.StacIO.default().read_json(href) item = ps.Item.from_file(href) - rel_links = HIERARCHICAL_LINKS + ps.EXTENSION_HOOKS.get_extended_object_links(item) + rel_links = ( + HIERARCHICAL_LINKS + ps.EXTENSION_HOOKS.get_extended_object_links(item) + ) for link in item.get_links(): - if not link.rel == 'self': - if link_type == 'RELATIVE' and link.rel in rel_links: + if not link.rel == "self": + if link_type == "RELATIVE" and link.rel in rel_links: self.assertFalse(is_absolute_href(link.href)) else: self.assertTrue(is_absolute_href(link.href)) validate_asset_href_type(item, href) - rels = set([link['rel'] for link in item_dict['links']]) - self.assertEqual('self' in rels, should_include_self) + rels = set([link["rel"] for link in item_dict["links"]]) + self.assertEqual("self" in rels, should_include_self) - def validate_catalog_link_type(href: str, link_type: str, should_include_self: bool): + def validate_catalog_link_type( + href: str, link_type: str, should_include_self: bool + ): cat_dict = ps.StacIO.default().read_json(href) cat = ps.Catalog.from_file(href) - rels = set([link['rel'] for link in cat_dict['links']]) - self.assertEqual('self' in rels, should_include_self) + rels = set([link["rel"] for link in cat_dict["links"]]) + self.assertEqual("self" in rels, should_include_self) for child_link in cat.get_child_links(): child_href = make_absolute_href(child_link.href, href) - validate_catalog_link_type(child_href, link_type, - catalog_type == CatalogType.ABSOLUTE_PUBLISHED) + validate_catalog_link_type( + child_href, + link_type, + catalog_type == CatalogType.ABSOLUTE_PUBLISHED, + ) for item_link in cat.get_item_links(): item_href = make_absolute_href(item_link.href, href) - validate_item_link_type(item_href, link_type, - catalog_type == CatalogType.ABSOLUTE_PUBLISHED) + validate_item_link_type( + item_href, link_type, catalog_type == CatalogType.ABSOLUTE_PUBLISHED + ) - link_type = 'RELATIVE' + link_type = "RELATIVE" if catalog_type == CatalogType.ABSOLUTE_PUBLISHED: - link_type = 'ABSOLUTE' + link_type = "ABSOLUTE" root_should_include_href = catalog_type in [ - CatalogType.ABSOLUTE_PUBLISHED, CatalogType.RELATIVE_PUBLISHED + CatalogType.ABSOLUTE_PUBLISHED, + CatalogType.RELATIVE_PUBLISHED, ] validate_catalog_link_type(root_href, link_type, root_should_include_href) @@ -110,9 +122,12 @@ def test_testcases(self): for catalog in TestCases.all_test_catalogs(): catalog = catalog.full_copy() ctypes = [ - CatalogType.ABSOLUTE_PUBLISHED, CatalogType.RELATIVE_PUBLISHED, - CatalogType.SELF_CONTAINED + CatalogType.ABSOLUTE_PUBLISHED, + CatalogType.RELATIVE_PUBLISHED, + CatalogType.SELF_CONTAINED, ] for catalog_type in ctypes: - with self.subTest(title='Catalog {} [{}]'.format(catalog.id, catalog_type)): + with self.subTest( + title="Catalog {} [{}]".format(catalog.id, catalog_type) + ): self.do_test(catalog, catalog_type) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index ab4c6c9fa..334f4219c 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -6,7 +6,8 @@ TestCases, # type:ignore ARBITRARY_GEOM, # type:ignore ARBITRARY_BBOX, # type:ignore - ARBITRARY_EXTENT) # type:ignore + ARBITRARY_EXTENT, # type:ignore +) from copy import deepcopy from datetime import datetime @@ -16,8 +17,11 @@ from tests.utils.stac_io_mock import MockStacIO # type:ignore -def test_to_from_dict(test_class: unittest.TestCase, stac_object_class: Type[ps.STACObject], - d: Dict[str, Any]) -> None: +def test_to_from_dict( + test_class: unittest.TestCase, + stac_object_class: Type[ps.STACObject], + d: Dict[str, Any], +) -> None: def _parse_times(a_dict: Dict[str, Any]) -> None: for k, v in a_dict.items(): if isinstance(v, dict): @@ -27,7 +31,7 @@ def _parse_times(a_dict: Dict[str, Any]) -> None: if isinstance(vv, dict): _parse_times(vv) else: - if k == 'datetime': + if k == "datetime": if not isinstance(v, datetime): a_dict[k] = parse(v) a_dict[k] = a_dict[k].replace(microsecond=0) diff --git a/tests/utils/stac_io_mock.py b/tests/utils/stac_io_mock.py index 7ebcb0896..138cf9610 100644 --- a/tests/utils/stac_io_mock.py +++ b/tests/utils/stac_io_mock.py @@ -8,6 +8,7 @@ class MockStacIO(ps.StacIO): """Creates a mock that records STAC_IO calls for testing and allows clients to replace STAC_IO functionality, all within a context scope. """ + def __init__(self): self.mock = Mock() @@ -15,6 +16,8 @@ def read_text(self, source: Union[str, ps.Link], *args: Any, **kwargs: Any) -> s self.mock.read_text(source) return ps.StacIO.default().read_text(source) - def write_text(self, dest: Union[str, ps.Link], txt: str, *args: Any, **kwargs: Any) -> None: + def write_text( + self, dest: Union[str, ps.Link], txt: str, *args: Any, **kwargs: Any + ) -> None: self.mock.write_text(dest, txt) ps.StacIO.default().write_text(dest, txt) diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index d3c83f3f4..331fb2f15 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -4,58 +4,86 @@ from typing import Any, Dict, List import pystac as ps -from pystac import (Catalog, Collection, Item, Asset, Extent, TemporalExtent, SpatialExtent, - MediaType) -from pystac.extensions.label import (LabelExtension, LabelOverview, LabelClasses, LabelCount, - LabelType) +from pystac import ( + Catalog, + Collection, + Item, + Asset, + Extent, + TemporalExtent, + SpatialExtent, + MediaType, +) +from pystac.extensions.label import ( + LabelExtension, + LabelOverview, + LabelClasses, + LabelCount, + LabelType, +) TEST_LABEL_CATALOG = { - 'country-1': { - 'area-1-1': { - 'dsm': 'area-1-1_dsm.tif', - 'ortho': 'area-1-1_ortho.tif', - 'labels': 'area-1-1_labels.geojson' + "country-1": { + "area-1-1": { + "dsm": "area-1-1_dsm.tif", + "ortho": "area-1-1_ortho.tif", + "labels": "area-1-1_labels.geojson", + }, + "area-1-2": { + "dsm": "area-1-2_dsm.tif", + "ortho": "area-1-2_ortho.tif", + "labels": "area-1-2_labels.geojson", }, - 'area-1-2': { - 'dsm': 'area-1-2_dsm.tif', - 'ortho': 'area-1-2_ortho.tif', - 'labels': 'area-1-2_labels.geojson' - } }, - 'country-2': { - 'area-2-1': { - 'dsm': 'area-2-1_dsm.tif', - 'ortho': 'area-2-1_ortho.tif', - 'labels': 'area-2-1_labels.geojson' + "country-2": { + "area-2-1": { + "dsm": "area-2-1_dsm.tif", + "ortho": "area-2-1_ortho.tif", + "labels": "area-2-1_labels.geojson", + }, + "area-2-2": { + "dsm": "area-2-2_dsm.tif", + "ortho": "area-2-2_ortho.tif", + "labels": "area-2-2_labels.geojson", }, - 'area-2-2': { - 'dsm': 'area-2-2_dsm.tif', - 'ortho': 'area-2-2_ortho.tif', - 'labels': 'area-2-2_labels.geojson' - } - } + }, } ARBITRARY_GEOM: Dict[str, Any] = { - "type": - "Polygon", - "coordinates": [[[-2.5048828125, 3.8916575492899987], [-1.9610595703125, 3.8916575492899987], - [-1.9610595703125, 4.275202171119132], [-2.5048828125, 4.275202171119132], - [-2.5048828125, 3.8916575492899987]]] + "type": "Polygon", + "coordinates": [ + [ + [-2.5048828125, 3.8916575492899987], + [-1.9610595703125, 3.8916575492899987], + [-1.9610595703125, 4.275202171119132], + [-2.5048828125, 4.275202171119132], + [-2.5048828125, 3.8916575492899987], + ] + ], } ARBITRARY_BBOX: List[float] = [ - ARBITRARY_GEOM['coordinates'][0][0][0], ARBITRARY_GEOM['coordinates'][0][0][1], - ARBITRARY_GEOM['coordinates'][0][1][0], ARBITRARY_GEOM['coordinates'][0][1][1] + ARBITRARY_GEOM["coordinates"][0][0][0], + ARBITRARY_GEOM["coordinates"][0][0][1], + ARBITRARY_GEOM["coordinates"][0][1][0], + ARBITRARY_GEOM["coordinates"][0][1][1], ] -ARBITRARY_EXTENT = Extent(spatial=SpatialExtent.from_coordinates(ARBITRARY_GEOM['coordinates']), - temporal=TemporalExtent.from_now()) # noqa: E126 +ARBITRARY_EXTENT = Extent( + spatial=SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]), + temporal=TemporalExtent.from_now(), +) # noqa: E126 class ExampleInfo: - def __init__(self, path: str, object_type: ps.STACObjectType, stac_version: str, - extensions: List[str], valid: bool) -> None: + def __init__( + self, + path: str, + object_type: ps.STACObjectType, + stac_version: str, + extensions: List[str], + valid: bool, + ) -> None: self.path = path self.object_type = object_type self.stac_version = stac_version @@ -66,34 +94,37 @@ def __init__(self, path: str, object_type: ps.STACObjectType, stac_version: str, class TestCases: @staticmethod def get_path(rel_path: str) -> str: - return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', rel_path)) + return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", rel_path)) @staticmethod def get_examples_info() -> List[ExampleInfo]: examples: List[ExampleInfo] = [] - info_path = TestCases.get_path('data-files/examples/example-info.csv') - with open(TestCases.get_path('data-files/examples/example-info.csv')) as f: + info_path = TestCases.get_path("data-files/examples/example-info.csv") + with open(TestCases.get_path("data-files/examples/example-info.csv")) as f: for row in csv.reader(f): path = os.path.abspath(os.path.join(os.path.dirname(info_path), row[0])) object_type = row[1] stac_version = row[2] extensions: List[str] = [] if row[3]: - extensions = row[3].split('|') + extensions = row[3].split("|") valid = True if len(row) > 4: # The 5th column will be "INVALID" if the example # shouldn't pass validation - valid = row[4] != 'INVALID' + valid = row[4] != "INVALID" examples.append( - ExampleInfo(path=path, - object_type=ps.STACObjectType(object_type), - stac_version=stac_version, - extensions=extensions, - valid=valid)) + ExampleInfo( + path=path, + object_type=ps.STACObjectType(object_type), + stac_version=stac_version, + extensions=extensions, + valid=valid, + ) + ) return examples @staticmethod @@ -105,51 +136,66 @@ def all_test_catalogs() -> List[Catalog]: TestCases.test_case_4(), TestCases.test_case_5(), TestCases.test_case_7(), - TestCases.test_case_8() + TestCases.test_case_8(), ] @staticmethod def test_case_1() -> Catalog: - return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-1/catalog.json')) + return Catalog.from_file( + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) @staticmethod def test_case_2() -> Catalog: - return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-2/catalog.json')) + return Catalog.from_file( + TestCases.get_path("data-files/catalogs/test-case-2/catalog.json") + ) @staticmethod def test_case_3() -> Catalog: - root_cat = Catalog(id='test3', description='test case 3 catalog', title='test case 3 title') - - image_item = Item(id='imagery-item', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) - - image_item.add_asset('ortho', Asset(href='some/geotiff.tiff', media_type=MediaType.GEOTIFF)) + root_cat = Catalog( + id="test3", description="test case 3 catalog", title="test case 3 title" + ) + + image_item = Item( + id="imagery-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) + + image_item.add_asset( + "ortho", Asset(href="some/geotiff.tiff", media_type=MediaType.GEOTIFF) + ) overviews = [ - LabelOverview.create('label', - counts=[LabelCount.create('one', 1), - LabelCount.create('two', 2)]) + LabelOverview.create( + "label", + counts=[LabelCount.create("one", 1), LabelCount.create("two", 2)], + ) ] - label_item = Item(id='label-items', - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.utcnow(), - properties={}) + label_item = Item( + id="label-items", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.utcnow(), + properties={}, + ) LabelExtension.add_to(label_item) label_ext = LabelExtension.ext(label_item) - label_ext.apply(label_description='ML Labels', - label_type=LabelType.VECTOR, - label_properties=['label'], - label_classes=[LabelClasses.create(classes=['one', 'two'], name='label')], - label_tasks=['classification'], - label_methods=['manual'], - label_overviews=overviews) - label_ext.add_source(image_item, assets=['ortho']) + label_ext.apply( + label_description="ML Labels", + label_type=LabelType.VECTOR, + label_properties=["label"], + label_classes=[LabelClasses.create(classes=["one", "two"], name="label")], + label_tasks=["classification"], + label_methods=["manual"], + label_overviews=overviews, + ) + label_ext.add_source(image_item, assets=["ortho"]) root_cat.add_item(image_item) root_cat.add_item(label_item) @@ -162,28 +208,36 @@ def test_case_4(): DrivenData's OpenCities AI Challenge. See: https://www.drivendata.org/competitions/60/building-segmentation-disaster-resilience """ - return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-4/catalog.json')) + return Catalog.from_file( + TestCases.get_path("data-files/catalogs/test-case-4/catalog.json") + ) @staticmethod def test_case_5(): """Based on a subset of https://cbers.stac.cloud/""" - return Catalog.from_file(TestCases.get_path('data-files/catalogs/test-case-5/catalog.json')) + return Catalog.from_file( + TestCases.get_path("data-files/catalogs/test-case-5/catalog.json") + ) @staticmethod def test_case_6(): """Based on a subset of CBERS, contains a root and 4 empty children""" return Catalog.from_file( - TestCases.get_path('data-files/catalogs/cbers-partial/catalog.json')) + TestCases.get_path("data-files/catalogs/cbers-partial/catalog.json") + ) @staticmethod def test_case_7(): """Test case 4 as STAC version 0.8.1""" return Catalog.from_file( - TestCases.get_path('data-files/catalogs/label_catalog-v0.8.1/catalog.json')) + TestCases.get_path("data-files/catalogs/label_catalog-v0.8.1/catalog.json") + ) @staticmethod def test_case_8() -> Collection: """Planet disaster data example catalog, 1.0.0-beta.2""" return Collection.from_file( - TestCases.get_path('data-files/catalogs/' - 'planet-example-v1.0.0-beta.2/collection.json')) + TestCases.get_path( + "data-files/catalogs/" "planet-example-v1.0.0-beta.2/collection.json" + ) + ) diff --git a/tests/validation/test_schema_uri_map.py b/tests/validation/test_schema_uri_map.py index 0f10a4194..787dbaa90 100644 --- a/tests/validation/test_schema_uri_map.py +++ b/tests/validation/test_schema_uri_map.py @@ -7,8 +7,10 @@ class SchemaUriMapTest(unittest.TestCase): def test_gets_schema_uri_for_old_version(self): d = DefaultSchemaUriMap() - uri = d.get_object_schema_uri(ps.STACObjectType.ITEM, '0.8.0') + uri = d.get_object_schema_uri(ps.STACObjectType.ITEM, "0.8.0") self.assertEqual( - uri, 'https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.0/' - 'item-spec/json-schema/item.json') + uri, + "https://raw.githubusercontent.com/radiantearth/stac-spec/v0.8.0/" + "item-spec/json-schema/item.json", + ) diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 6f37fb4b7..117aa2e0c 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -19,17 +19,21 @@ class ValidateTest(unittest.TestCase): def test_validate_current_version(self): - catalog = ps.read_file(TestCases.get_path('data-files/catalogs/test-case-1/' - 'catalog.json')) + catalog = ps.read_file( + TestCases.get_path("data-files/catalogs/test-case-1/" "catalog.json") + ) catalog.validate() collection = ps.read_file( - TestCases.get_path('data-files/catalogs/test-case-1/' - '/country-1/area-1-1/' - 'collection.json')) + TestCases.get_path( + "data-files/catalogs/test-case-1/" + "/country-1/area-1-1/" + "collection.json" + ) + ) collection.validate() - item = ps.read_file(TestCases.get_path('data-files/item/sample-item.json')) + item = ps.read_file(TestCases.get_path("data-files/item/sample-item.json")) item.validate() def test_validate_examples(self): @@ -39,7 +43,7 @@ def test_validate_examples(self): path = example.path valid = example.valid - if stac_version < '0.8': + if stac_version < "0.8": with open(path) as f: stac_json = json.load(f) @@ -50,10 +54,12 @@ def test_validate_examples(self): stac_json = json.load(f) # Check if common properties need to be merged - if stac_version < '1.0': + if stac_version < "1.0": if example.object_type == ps.STACObjectType.ITEM: collection_cache = CollectionCache() - merge_common_properties(stac_json, collection_cache, path) + merge_common_properties( + stac_json, collection_cache, path + ) if valid: pystac.validation.validate_dict(stac_json) @@ -62,16 +68,18 @@ def test_validate_examples(self): try: pystac.validation.validate_dict(stac_json) except STACValidationError as e: - self.assertIsInstance(e.source, jsonschema.ValidationError) + self.assertIsInstance( + e.source, jsonschema.ValidationError + ) raise e def test_validate_error_contains_href(self): # Test that the exception message contains the HREF of the object if available. cat = TestCases.test_case_1() - item = cat.get_item('area-1-1-labels', recursive=True) + item = cat.get_item("area-1-1-labels", recursive=True) assert item.get_self_href() is not None - item.geometry = {'type': 'INVALID'} + item.geometry = {"type": "INVALID"} with self.assertRaises(STACValidationError): try: @@ -92,23 +100,24 @@ def test_validate_all(self): # and make sure it catches the validation error. with TemporaryDirectory() as tmp_dir: - dst_dir = os.path.join(tmp_dir, 'catalog') + dst_dir = os.path.join(tmp_dir, "catalog") # Copy test case 7 to the temporary directory catalog_href = get_opt(TestCases.test_case_7().get_self_href()) shutil.copytree(os.path.dirname(catalog_href), dst_dir) - new_cat_href = os.path.join(dst_dir, 'catalog.json') + new_cat_href = os.path.join(dst_dir, "catalog.json") # Make sure it's valid before modification - pystac.validation.validate_all(ps.StacIO.default().read_json(new_cat_href), - new_cat_href) + pystac.validation.validate_all( + ps.StacIO.default().read_json(new_cat_href), new_cat_href + ) # Modify a contained collection to add an extension for which the # collection is invalid. - with open(os.path.join(dst_dir, 'acc/collection.json')) as f: + with open(os.path.join(dst_dir, "acc/collection.json")) as f: col = json.load(f) - col['stac_extensions'] = ['asset'] - with open(os.path.join(dst_dir, 'acc/collection.json'), 'w') as f: + col["stac_extensions"] = ["asset"] + with open(os.path.join(dst_dir, "acc/collection.json"), "w") as f: json.dump(col, f) stac_dict = ps.StacIO.default().read_json(new_cat_href) @@ -123,18 +132,26 @@ def test_validates_geojson_with_tuple_coordinates(self): validation. """ geom: Dict[str, Any] = { - 'type': - 'Polygon', + "type": "Polygon", # Last , is required to ensure tuple creation. - 'coordinates': (((-115.305, 36.126), (-115.305, 36.128), (-115.307, 36.128), - (-115.307, 36.126), (-115.305, 36.126)), ) + "coordinates": ( + ( + (-115.305, 36.126), + (-115.305, 36.128), + (-115.307, 36.128), + (-115.307, 36.126), + (-115.305, 36.126), + ), + ), } - item = ps.Item(id='test-item', - geometry=geom, - bbox=[-115.308, 36.126, -115.305, 36.129], - datetime=datetime.utcnow(), - properties={}) + item = ps.Item( + id="test-item", + geometry=geom, + bbox=[-115.308, 36.126, -115.305, 36.129], + datetime=datetime.utcnow(), + properties={}, + ) # Should not raise. item.validate() From a1019e624fd3a2193885d5428264864e6a86b803 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 19:05:43 -0400 Subject: [PATCH 34/51] Modify flake8 config to match black --- .flake8 | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.flake8 b/.flake8 index f31f62798..c5ab79442 100644 --- a/.flake8 +++ b/.flake8 @@ -1,14 +1,3 @@ [flake8] -max-line-length = 100 - -## IGNORES - -# E126: yapf conflicts with "continuation line over-indented for hanging indent" - -# E127: flake8 reporting incorrect continuation line indent errors -# on multi-line and multi-level indents - -# W503: flake8 reports this as incorrect, and scripts/format_code -# changes code to it, so let format_code win. - -ignore = E126,E127,W503 \ No newline at end of file +max-line-length = 88 +extend-ignore = E203, W503, E731, E722 \ No newline at end of file From 3f998217ac018a0584f059d166489ecbe10571db Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Fri, 30 Apr 2021 20:26:07 -0400 Subject: [PATCH 35/51] Fixup linting changes with shorter line length requirement --- pystac/asset.py | 30 +++---- pystac/cache.py | 19 +++-- pystac/catalog.py | 76 ++++++++++-------- pystac/collection.py | 61 ++++++++------- pystac/extensions/eo.py | 17 ++-- pystac/extensions/label.py | 66 +++++++++------- pystac/extensions/pointcloud.py | 28 ++++--- pystac/extensions/projection.py | 83 ++++++++++---------- pystac/extensions/sar.py | 47 ++++++----- pystac/extensions/sat.py | 9 ++- pystac/extensions/scientific.py | 3 +- pystac/extensions/timestamps.py | 18 ++--- pystac/extensions/view.py | 39 ++++++---- pystac/item.py | 70 +++++++++-------- pystac/layout.py | 5 +- pystac/link.py | 14 ++-- pystac/serialization/identify.py | 3 +- pystac/stac_io.py | 3 +- pystac/stac_object.py | 32 ++------ pystac/utils.py | 6 +- pystac/validation/schema_uri_map.py | 117 ++++++++++++++++++---------- pystac/validation/stac_validator.py | 20 ++--- pystac/version.py | 20 ++--- tests/extensions/test_file.py | 3 +- tests/test_catalog.py | 6 +- tests/test_item.py | 5 +- tests/test_layout.py | 3 +- tests/utils/test_cases.py | 2 +- tests/validation/test_validate.py | 4 +- 29 files changed, 458 insertions(+), 351 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index 03d560a84..b6c21e954 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -14,29 +14,31 @@ class Asset: can be downloaded or streamed. Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. + href (str): Link to the asset object. Relative and absolute links are both + allowed. title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. + description (str): A description of the Asset providing additional details, + such as how it was processed or created. CommonMark 0.29 syntax MAY be used + for rich text representation. media_type (str): Optional description of the media type. Registered Media Types are preferred. See :class:`~pystac.MediaType` for common media types. - roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) - of the asset. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset + roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview, + data, metadata) of the asset. + properties (dict): Optional, additional properties for this asset. This is used + by extensions as a way to serialize and deserialize properties on asset object JSON. Attributes: - href (str): Link to the asset object. Relative and absolute links are both allowed. + href (str): Link to the asset object. Relative and absolute links are both + allowed. title (str): Optional displayed title for clients and users. - description (str): A description of the Asset providing additional details, such as - how it was processed or created. CommonMark 0.29 syntax MAY be used for rich - text representation. + description (str): A description of the Asset providing additional details, + such as how it was processed or created. CommonMark 0.29 syntax MAY be + used for rich text representation. media_type (str): Optional description of the media type. Registered Media Types are preferred. See :class:`~pystac.MediaType` for common media types. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset + properties (dict): Optional, additional properties for this asset. This is used + by extensions as a way to serialize and deserialize properties on asset object JSON. owner: The Item or Collection this asset belongs to, or None if it has no owner. """ diff --git a/pystac/cache.py b/pystac/cache.py index 38e5cf1c4..a8c9291ce 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -55,7 +55,8 @@ class ResolvedObjectCache: to the cached STACObject. hrefs_to_objects (Dict[str, STACObject]): STAC Object HREFs matched to their cached object. - ids_to_collections (Dict[str, Collection]): Map of collection IDs to collections. + ids_to_collections (Dict[str, Collection]): Map of collection IDs + to collections. """ def __init__( @@ -79,8 +80,8 @@ def get_or_cache(self, obj: "STACObject_Type") -> "STACObject_Type": against the cache. Returns: - STACObject: Either the cached object that has the same cache key as the given - object, or the given object. + STACObject: Either the cached object that has the same cache key as the + given object, or the given object. """ key, is_href = get_cache_key(obj) if is_href: @@ -100,11 +101,12 @@ def get(self, obj: "STACObject_Type") -> Optional["STACObject_Type"]: """Get the cached object that has the same cache key as the given object. Args: - obj (STACObject): The given object who's cache key will be checked against the cache. + obj (STACObject): The given object who's cache key will be checked against + the cache. Returns: - STACObject or None: Either the cached object that has the same cache key as the given - object, or None + STACObject or None: Either the cached object that has the same cache key as + the given object, or None """ key, is_href = get_cache_key(obj) if is_href: @@ -226,8 +228,9 @@ class CollectionCache: """Cache of collections that can be used to avoid re-reading Collection JSON in :func:`pystac.serialization.merge_common_properties `. - The CollectionCache will contain collections as either as dicts or PySTAC Collections, - and will set Collection JSON that it reads in order to merge in common properties. + The CollectionCache will contain collections as either as dicts or PySTAC + Collections, and will set Collection JSON that it reads in order to merge + in common properties. """ def __init__( diff --git a/pystac/catalog.py b/pystac/catalog.py index 2e09f70b6..8cf6655ec 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -107,8 +107,8 @@ class Catalog(STACObject): representation. title (str or None): Optional short descriptive one-line title for the catalog. stac_extensions (List[str]): Optional list of extensions the Catalog implements. - href (str or None): Optional HREF for this catalog, which be set as the catalog's - self link's HREF. + href (str or None): Optional HREF for this catalog, which be set as the + catalog's self link's HREF. catalog_type (str or None): Optional catalog type for this catalog. Must be one of the values in :class`~pystac.CatalogType`. @@ -116,9 +116,10 @@ class Catalog(STACObject): id (str): Identifier for the catalog. description (str): Detailed multi-line description to fully explain the catalog. title (str or None): Optional short descriptive one-line title for the catalog. - stac_extensions (List[str] or None): Optional list of extensions the Catalog implements. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Catalog. + stac_extensions (List[str] or None): Optional list of extensions the Catalog + implements. + extra_fields (dict or None): Extra fields that are part of the top-level JSON + properties of the Catalog. links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this Catalog. catalog_type (str): The catalog type. Defaults to ABSOLUTE_PUBLISHED @@ -133,7 +134,9 @@ class Catalog(STACObject): is read by a StacIO instance.""" DEFAULT_FILE_NAME = "catalog.json" - """Default file name that will be given to this STAC object in a canonical format.""" + """Default file name that will be given to this STAC object in + a canonical format. + """ def __init__( self, @@ -195,8 +198,8 @@ def add_child( Args: child (Catalog or Collection): The child to add. title (str): Optional title to give to the :class:`~pystac.Link` - strategy (HrefLayoutStrategy): The layout strategy to use for setting the self - href of the child. + strategy (HrefLayoutStrategy): The layout strategy to use for setting the + self href of the child. """ # Prevent typo confusion @@ -277,8 +280,9 @@ def get_child(self, id: str, recursive: bool = False) -> Optional["Catalog"]: Args: id (str): The ID of the child to find. - recursive (bool): If True, search this catalog and all children for the item; - otherwise, only search the children of this catalog. Defaults to False. + recursive (bool): If True, search this catalog and all children for the + item; otherwise, only search the children of this catalog. Defaults + to False. Return: Item or None: The item with the given ID, or None if not found. @@ -345,8 +349,9 @@ def get_item(self, id: str, recursive: bool = False) -> Optional["Item_Type"]: Args: id (str): The ID of the item to find. - recursive (bool): If True, search this catalog and all children for the item; - otherwise, only search the items of this catalog. Defaults to False. + recursive (bool): If True, search this catalog and all children for the + item; otherwise, only search the items of this catalog. Defaults + to False. Return: Item or None: The item with the given ID, or None if not found. @@ -501,13 +506,15 @@ def normalize_and_save( in sequence. Args: - root_href (str): The absolute HREF that all links will be normalized against. + root_href (str): The absolute HREF that all links will be normalized + against. catalog_type (str): The catalog type that dictates the structure of the catalog to save. Use a member of :class:`~pystac.CatalogType`. - Defaults to the root catalog.catalog_type or the current catalog catalog_type - if there is no root catalog. - strategy (HrefLayoutStrategy): The layout strategy to use in setting the HREFS - for this catalog. Defaults to :class:`~pystac.layout.BestPracticesLayoutStrategy` + Defaults to the root catalog.catalog_type or the current catalog + catalog_type if there is no root catalog. + strategy (HrefLayoutStrategy): The layout strategy to use in setting the + HREFS for this catalog. Defaults to + :class:`~pystac.layout.BestPracticesLayoutStrategy` """ self.normalize_hrefs(root_href, strategy=strategy) self.save(catalog_type) @@ -587,7 +594,9 @@ def generate_subcatalogs( **kwargs: Any, ) -> List["Catalog"]: """Walks through the catalog and generates subcatalogs - for items based on the template string. See :class:`~pystac.layout.LayoutTemplate` + for items based on the template string. + + See :class:`~pystac.layout.LayoutTemplate` for details on the construction of template strings. This template string will be applied to the items, and subcatalogs will be created that separate and organize the items based on template values. @@ -673,11 +682,11 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: Note: If the catalog type is ``CatalogType.ABSOLUTE_PUBLISHED``, all self links will be included, and hierarchical links be absolute URLs. - If the catalog type is ``CatalogType.RELATIVE_PUBLISHED``, this catalog's self - link will be included, but no child catalog will have self links, and + If the catalog type is ``CatalogType.RELATIVE_PUBLISHED``, this catalog's + self link will be included, but no child catalog will have self links, and hierarchical links will be relative URLs - If the catalog type is ``CatalogType.SELF_CONTAINED``, no self links will be - included and hierarchical links will be relative URLs. + If the catalog type is ``CatalogType.SELF_CONTAINED``, no self links will + be included and hierarchical links will be relative URLs. """ root = self.get_root() if root is None: @@ -699,7 +708,8 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: ) include_self_link = False - # include a self link if this is the root catalog or if ABSOLUTE_PUBLISHED catalog + # include a self link if this is the root catalog + # or if ABSOLUTE_PUBLISHED catalog if root.catalog_type == CatalogType.ABSOLUTE_PUBLISHED: include_self_link = True elif root.catalog_type != CatalogType.SELF_CONTAINED: @@ -716,9 +726,9 @@ def walk( ) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable["Item_Type"]]]: """Walks through children and items of catalogs. - For each catalog in the STAC's tree rooted at this catalog (including this catalog - itself), it yields a 3-tuple (root, subcatalogs, items). The root in that - 3-tuple refers to the current catalog being walked, the subcatalogs are any + For each catalog in the STAC's tree rooted at this catalog (including this + catalog itself), it yields a 3-tuple (root, subcatalogs, items). The root in + that 3-tuple refers to the current catalog being walked, the subcatalogs are any catalogs or collections for which the root is a parent, and items represents any items that have the root as a parent. @@ -764,9 +774,9 @@ def map_items( item_mapper function. Args: - item_mapper (Callable): A function that takes in an item, and returns either - an item or list of items. The item that is passed into the item_mapper - is a copy, so the method can mutate it safely. + item_mapper (Callable): A function that takes in an item, and returns + either an item or list of items. The item that is passed into the + item_mapper is a copy, so the method can mutate it safely. Returns: Catalog: A full copy of this catalog, with items manipulated according @@ -810,10 +820,10 @@ def map_assets( through the asset_mapper function. Args: - asset_mapper (Callable): A function that takes in an key and an Asset, and returns - either an Asset, a (key, Asset), or a dictionary of Assets with unique keys. - The Asset that is passed into the item_mapper is a copy, so the method can - mutate it safely. + asset_mapper (Callable): A function that takes in an key and an Asset, and + returns either an Asset, a (key, Asset), or a dictionary of Assets with + unique keys. The Asset that is passed into the item_mapper is a copy, + so the method can mutate it safely. Returns: Catalog: A full copy of this catalog, with assets manipulated according diff --git a/pystac/collection.py b/pystac/collection.py index c710e0c4b..d34af4b0c 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -143,9 +143,9 @@ class TemporalExtent: Attributes: intervals (List[List[datetime]]): A list of two datetimes wrapped in a list, - representing the temporal extent of a Collection. Open date ranges are represented - by either the start (the first element of the interval) or the end (the - second element of the interval) being None. + representing the temporal extent of a Collection. Open date ranges are + represented by either the start (the first element of the interval) or the + end (the second element of the interval) being None. Note: Datetimes are required to be in UTC. @@ -355,8 +355,8 @@ def from_items(items: Iterable["Item_Type"]) -> "Extent": class Provider: """Provides information about a provider of STAC data. A provider is any of the organizations that captured or processed the content of the collection and therefore - influenced the data offered by this collection. May also include information about the - final storage provider hosting the data. + influenced the data offered by this collection. May also include information about + the final storage provider hosting the data. Args: name (str): The name of the organization or the individual. @@ -507,50 +507,59 @@ class Collection(Catalog): Args: id (str): Identifier for the collection. Must be unique within the STAC. - description (str): Detailed multi-line description to fully explain the collection. - `CommonMark 0.28 syntax `_ MAY be used for rich text - representation. + description (str): Detailed multi-line description to fully explain the + collection. `CommonMark 0.28 syntax `_ MAY + be used for rich text representation. extent (Extent): Spatial and temporal extents that describe the bounds of all items contained within this Collection. - title (str or None): Optional short descriptive one-line title for the collection. - stac_extensions (List[str]): Optional list of extensions the Collection implements. - href (str or None): Optional HREF for this collection, which be set as the collection's - self link's HREF. + title (str or None): Optional short descriptive one-line title for the + collection. + stac_extensions (List[str]): Optional list of extensions the Collection + implements. + href (str or None): Optional HREF for this collection, which be set as the + collection's self link's HREF. catalog_type (str or None): Optional catalog type for this catalog. Must be one of the values in :class`~pystac.CatalogType`. - license (str): Collection's license(s) as a `SPDX License identifier - `_, `various`, or `proprietary`. If collection includes - data with multiple different licenses, use `various` and add a link for each. - Defaults to 'proprietary'. + license (str): Collection's license(s) as a + `SPDX License identifier `_, + `various`, or `proprietary`. If collection includes + data with multiple different licenses, use `various` and add a link for + each. Defaults to 'proprietary'. keywords (List[str]): Optional list of keywords describing the collection. providers (List[Provider]): Optional list of providers of this Collection. summaries (dict): An optional map of property summaries, either a set of values or statistics such as a range. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Collection. + extra_fields (dict or None): Extra fields that are part of the top-level + JSON properties of the Collection. Attributes: id (str): Identifier for the collection. - description (str): Detailed multi-line description to fully explain the collection. + description (str): Detailed multi-line description to fully explain the + collection. extent (Extent): Spatial and temporal extents that describe the bounds of all items contained within this Collection. - title (str or None): Optional short descriptive one-line title for the collection. - stac_extensions (List[str]): Optional list of extensions the Collection implements. - keywords (List[str] or None): Optional list of keywords describing the collection. - providers (List[Provider] or None): Optional list of providers of this Collection. + title (str or None): Optional short descriptive one-line title for the + collection. + stac_extensions (List[str]): Optional list of extensions the Collection + implements. + keywords (List[str] or None): Optional list of keywords describing the + collection. + providers (List[Provider] or None): Optional list of providers of this + Collection. assets (Optional[Dict[str, Asset]]): Optional map of Assets summaries (dict or None): An optional map of property summaries, either a set of values or statistics such as a range. links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this Collection. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Catalog. + extra_fields (dict or None): Extra fields that are part of the top-level + JSON properties of the Catalog. """ STAC_OBJECT_TYPE = STACObjectType.COLLECTION DEFAULT_FILE_NAME = "collection.json" - """Default file name that will be given to this STAC object in a canonical format.""" + """Default file name that will be given to this STAC object + in a canonical format.""" def __init__( self, diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 5c4c08801..15a6c7587 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -44,14 +44,14 @@ def apply( Args: name (str): The name of the band (e.g., "B01", "B02", "B1", "B5", "QA"). - common_name (str): The name commonly used to refer to the band to make it easier - to search for bands across instruments. See the `list of accepted common names - `_. + common_name (str): The name commonly used to refer to the band to make it + easier to search for bands across instruments. See the `list of + accepted common names `_. description (str): Description to fully explain the band. center_wavelength (float): The center wavelength of the band, in micrometers (μm). full_width_half_max (float): Full width at half maximum (FWHM). The width of the band, as measured at half the maximum transmission, in micrometers (μm). - """ + """ # noqa self.name = name self.common_name = common_name self.description = description @@ -79,7 +79,7 @@ def create( center_wavelength (float): The center wavelength of the band, in micrometers (μm). full_width_half_max (float): Full width at half maximum (FWHM). The width of the band, as measured at half the maximum transmission, in micrometers (μm). - """ + """ # noqa b = cls({}) b.apply( name=name, @@ -111,7 +111,7 @@ def common_name(self) -> Optional[str]: Returns: Optional[str] - """ + """ # noqa return self.properties.get("common_name") @common_name.setter @@ -252,8 +252,9 @@ def apply(self, bands: List[Band], cloud_cover: Optional[float] = None) -> None: Args: bands (List[Band]): a list of :class:`~pystac.Band` objects that represent the available bands. - cloud_cover (float or None): The estimate of cloud cover as a percentage (0-100) of the - entire scene. If not available the field should not be provided. + cloud_cover (float or None): The estimate of cloud cover as a percentage + (0-100) of the entire scene. If not available the field should not be + provided. """ self.bands = bands self.cloud_cover = cloud_cover diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 5854d1c31..fc1d4c9c1 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -27,7 +27,8 @@ def __str__(self) -> str: class LabelClasses: """Defines the list of possible class names (e.g., tree, building, car, hippo) - Use LabelClasses.create to create a new instance of LabelClasses from property values. + Use LabelClasses.create to create a new instance of LabelClasses from + property values. """ def __init__(self, properties: Dict[str, Any]): @@ -41,9 +42,11 @@ def apply( """Sets the properties for this LabelClasses. Args: - classes (List[str] or List[int] or List[float]): The different possible class values. - name (str): The property key within the asset's each Feature corresponding to - class labels. If labels are raster-formatted, do not supply; required otherwise. + classes (List[str] or List[int] or List[float]): The different possible + class values. + name (str): The property key within the asset's each Feature corresponding + to class labels. If labels are raster-formatted, do not supply; + required otherwise. """ self.classes = classes self.name = name @@ -57,9 +60,11 @@ def create( """Creates a new LabelClasses. Args: - classes (List[str] or List[int] or List[float]): The different possible class values. - name (str): The property key within the asset's each Feature corresponding to - class labels. If labels are raster-formatted, do not supply; required otherwise. + classes (List[str] or List[int] or List[float]): The different possible + class values. + name (str): The property key within the asset's each Feature corresponding + to class labels. If labels are raster-formatted, do not supply; + required otherwise. Returns: LabelClasses @@ -260,7 +265,8 @@ def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this LabelStatistics. Returns: - dict: The wrapped dict of the LabelStatistics that can be written out as JSON. + dict: The wrapped dict of the LabelStatistics that can be written out as + JSON. """ return {"name": self.name, "value": self.value} @@ -287,9 +293,9 @@ def apply( at least one is required. Args: - property_key (str): The property key within the asset corresponding to class labels - that these counts or statistics are referencing. If the label data is raster data, - this should be None. + property_key (str): The property key within the asset corresponding to + class labels that these counts or statistics are referencing. If the + label data is raster data, this should be None. counts: Optional list of LabelCounts containing counts for categorical data. statistics: Optional list of statistics containing statistics for @@ -312,7 +318,8 @@ def create( at least one is required. Args: - property_key (str): The property key within the asset corresponding to class labels. + property_key (str): The property key within the asset corresponding to + class labels. counts: Optional list of LabelCounts containing counts for categorical data. statistics: Optional list of Statistics containing statistics for @@ -580,7 +587,8 @@ def label_classes(self, v: Optional[List[LabelClasses]]) -> None: @property def label_tasks(self) -> Optional[List[str]]: """Get or set a list of tasks these labels apply to. Usually a subset of 'regression', - 'classification', 'detection', or 'segmentation', but may be arbitrary values. + 'classification', 'detection', or 'segmentation', but may be arbitrary + values. Returns: List[str] or None @@ -601,8 +609,9 @@ def label_tasks(self, v: Optional[List[str]]) -> None: @property def label_methods(self) -> Optional[List[str]]: - """Get or set a list of methods used for labeling. Usually a subset of 'automated' or 'manual', - but may be arbitrary values. + """Get or set a list of methods used for labeling. + + Usually a subset of 'automated' or 'manual', but may be arbitrary values. Returns: List[str] or None @@ -683,8 +692,8 @@ def get_sources(self) -> Iterable[ps.Item]: this LabelItem. Returns: - Generator[Items]: A possibly empty list of source imagery items. Determined by - links of this LabelItem that have ``rel=='source'``. + Generator[Items]: A possibly empty list of source imagery items. Determined + by links of this LabelItem that have ``rel=='source'``. """ return map(lambda x: cast(ps.Item, x), self.obj.get_stac_objects("source")) @@ -698,13 +707,15 @@ def add_labels( """Adds a label asset to this LabelItem. Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. + href (str): Link to the asset object. Relative and absolute links are both + allowed. title (str): Optional displayed title for clients and users. - media_type (str): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. + media_type (str): Optional description of the media type. Registered Media + Types are preferred. See :class:`~pystac.MediaType` for common + media types. + properties (dict): Optional, additional properties for this asset. This is + used by extensions as a way to serialize and deserialize properties on + asset object JSON. """ self.obj.add_asset( @@ -723,11 +734,12 @@ def add_geojson_labels( """Adds a GeoJSON label asset to this LabelItem. Args: - href (str): Link to the asset object. Relative and absolute links are both allowed. + href (str): Link to the asset object. Relative and absolute links are both + allowed. title (str): Optional displayed title for clients and users. - properties (dict): Optional, additional properties for this asset. This is used by - extensions as a way to serialize and deserialize properties on asset - object JSON. + properties (dict): Optional, additional properties for this asset. This is + used by extensions as a way to serialize and deserialize properties on + asset object JSON. """ self.add_labels( href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index af8860965..3908dd189 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -24,7 +24,8 @@ class PointcloudSchema: """Defines a schema for dimension of a pointcloud (e.g., name, size, type) - Use PointCloudSchema.create to create a new instance of PointCloudSchema from properties. + Use PointCloudSchema.create to create a new instance of PointCloudSchema from + properties. """ def __init__(self, properties: Dict[str, Any]) -> None: @@ -36,7 +37,8 @@ def apply(self, name: str, size: int, type: str) -> None: Args: name (str): The name of dimension. size (int): The size of the dimension in bytes. Whole bytes are supported. - type (str): Dimension type. Valid values are `floating`, `unsigned`, and `signed` + type (str): Dimension type. Valid values are `floating`, `unsigned`, and + `signed` """ self.properties["name"] = name self.properties["size"] = size @@ -49,7 +51,8 @@ def create(cls, name: str, size: int, type: str) -> "PointcloudSchema": Args: name (str): The name of dimension. size (int): The size of the dimension in bytes. Whole bytes are supported. - type (str): Dimension type. Valid values are `floating`, `unsigned`, and `signed` + type (str): Dimension type. Valid values are `floating`, `unsigned`, and + `signed` Returns: PointCloudSchema @@ -124,7 +127,8 @@ def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointCloudSchema. Returns: - dict: The wrapped dict of the PointCloudSchema that can be written out as JSON. + dict: The wrapped dict of the PointCloudSchema that can be written out as + JSON. """ return self.properties @@ -132,7 +136,8 @@ def to_dict(self) -> Dict[str, Any]: class PointcloudStatistic: """Defines a single statistic for Pointcloud channel or dimension - Use PointcloudStatistic.create to create a new instance of LabelClasses from property values. + Use PointcloudStatistic.create to create a new instance of LabelClasses from + property values. """ def __init__(self, properties: Dict[str, Any]) -> None: @@ -350,7 +355,8 @@ def to_dict(self) -> Dict[str, Any]: """Returns the dictionary representing the JSON of this PointcloudStatistic. Returns: - dict: The wrapped dict of the PointcloudStatistic that can be written out as JSON. + dict: The wrapped dict of the PointcloudStatistic that can be written out + as JSON. """ return self.properties @@ -387,11 +393,12 @@ def apply( values might include lidar, eopc, radar, sonar, or otherThe type of file or data format of the cloud. encoding (str): REQUIRED. Content encoding or format of the data. - schemas (List[PointcloudSchema]): REQUIRED. A sequential array of items that define the + schemas (List[PointcloudSchema]): REQUIRED. A sequential array of items + that define the dimensions and their types. density (float or None): Number of points per square unit area. - statistics (List[int] or None): A sequential array of items mapping to pc:schemas - defines per-channel statistics. + statistics (List[int] or None): A sequential array of items mapping to + pc:schemas defines per-channel statistics. epsg (str): An EPSG code for the projected coordinates of the pointcloud. """ self.count = count @@ -492,7 +499,8 @@ def density(self, v: Optional[float]) -> None: def statistics(self) -> Optional[List[PointcloudStatistic]]: """Get or sets the statistics for each property of the dataset. - A sequential array of items mapping to pc:schemas defines per-channel statistics. + A sequential array of items mapping to pc:schemas defines per-channel + statistics. Example:: diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 2d859bbfb..393cabdc1 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -35,8 +35,8 @@ class ProjectionExtension( item (Item): The Item that is being extended. Note: - Using ProjectionItemExt to directly wrap an item will add the 'proj' extension ID to - the item's stac_extensions. + Using ProjectionItemExt to directly wrap an item will add the 'proj' extension + ID to the item's stac_extensions. """ def __init__(self, item: ps.Item) -> None: @@ -57,21 +57,23 @@ def apply( Args: epsg (int or None): REQUIRED. EPSG code of the datasource. - wkt2 (str or None): WKT2 string representing the Coordinate Reference System (CRS) that - the ``geometry`` and ``bbox`` fields represent + wkt2 (str or None): WKT2 string representing the Coordinate Reference + System (CRS) that the ``geometry`` and ``bbox`` fields represent projjson (dict or None): PROJJSON dict representing the Coordinate Reference System (CRS) that the ``geometry`` and ``bbox`` fields represent - geometry (dict or None): GeoJSON Polygon dict that defines the footprint of this Item. + geometry (dict or None): GeoJSON Polygon dict that defines the footprint of + this Item. bbox (List[float] or None): Bounding box of the Item in the asset CRS in 2 or 3 dimensions. centroid (dict or None): A dict with members 'lat' and 'lon' that defines coordinates representing the centroid of the item in the asset data CRS. - Coordinates are defined in latitude and longitude, even if the data coordinate - system may not use lat/long. - shape (List[int] or None): Number of pixels in Y and X directions for the default grid. - transform (List[float] or None): The affine transformation coefficients for the - default grid + Coordinates are defined in latitude and longitude, even if the data + coordinate system may not use lat/long. + shape (List[int] or None): Number of pixels in Y and X directions for the + default grid. + transform (List[float] or None): The affine transformation coefficients for + the default grid """ self.epsg = epsg self.wkt2 = wkt2 @@ -86,12 +88,13 @@ def apply( def epsg(self) -> Optional[int]: """Get or sets the EPSG code of the datasource. - A Coordinate Reference System (CRS) is the data reference system (sometimes called a - 'projection') used by the asset data, and can usually be referenced using an - `EPSG code `_. - If the asset data does not have a CRS, such as in the case of non-rectified imagery with - Ground Control Points, epsg should be set to None. - It should also be set to null if a CRS exists, but for which there is no valid EPSG code. + A Coordinate Reference System (CRS) is the data reference system (sometimes + called a 'projection') used by the asset data, and can usually be referenced + using an `EPSG code `_. + If the asset data does not have a CRS, such as in the case of non-rectified + imagery with Ground Control Points, epsg should be set to None. + It should also be set to null if a CRS exists, but for which there is no valid + EPSG code. Returns: int @@ -107,10 +110,11 @@ def wkt2(self) -> Optional[str]: """Get or sets the WKT2 string representing the Coordinate Reference System (CRS) that the proj:geometry and proj:bbox fields represent - This value is a `WKT2 string `_. - If the data does not have a CRS, such as in the case of non-rectified imagery with Ground - Control Points, wkt2 should be set to null. It should also be set to null if a CRS exists, - but for which a WKT2 string does not exist. + This value is a + `WKT2 string `_. + If the data does not have a CRS, such as in the case of non-rectified imagery + with Ground Control Points, wkt2 should be set to null. It should also be set + to null if a CRS exists, but for which a WKT2 string does not exist. Returns: str @@ -126,10 +130,11 @@ def projjson(self) -> Optional[Dict[str, Any]]: """Get or sets the PROJJSON string representing the Coordinate Reference System (CRS) that the proj:geometry and proj:bbox fields represent - This value is a `PROJJSON object `_. - If the data does not have a CRS, such as in the case of non-rectified imagery with Ground - Control Points, projjson should be set to null. It should also be set to null if a - CRS exists, but for which a PROJJSON string does not exist. + This value is a + `PROJJSON object `_. + If the data does not have a CRS, such as in the case of non-rectified imagery + with Ground Control Points, projjson should be set to null. It should also be + set to null if a CRS exists, but for which a PROJJSON string does not exist. The schema for this object can be found `here `_. @@ -149,10 +154,10 @@ def geometry(self) -> Optional[Dict[str, Any]]: This dict should be formatted according the Polygon object format specified in `RFC 7946, sections 3.1.6 `_, - except not necessarily in EPSG:4326 as required by RFC7946. Specified based on the - ``epsg``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326). - Ideally, this will be represented by a Polygon with five coordinates, as the item in - the asset data CRS should be a square aligned to the original CRS grid. + except not necessarily in EPSG:4326 as required by RFC7946. Specified based on + the ``epsg``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326). + Ideally, this will be represented by a Polygon with five coordinates, as the + item in the asset data CRS should be a square aligned to the original CRS grid. Returns: dict @@ -168,12 +173,12 @@ def bbox(self) -> Optional[List[float]]: """Get or sets the bounding box of the assets represented by this item in the asset data CRS. - Specified as 4 or 6 coordinates based on the CRS defined in the ``epsg``, ``projjson`` - or ``wkt2`` properties. First two numbers are coordinates of the lower left corner, - followed by coordinates of upper right corner, e.g., + Specified as 4 or 6 coordinates based on the CRS defined in the ``epsg``, + ``projjson`` or ``wkt2`` properties. First two numbers are coordinates of the + lower left corner, followed by coordinates of upper right corner, e.g., [west, south, east, north], [xmin, ymin, xmax, ymax], [left, down, right, up], - or [west, south, lowest, east, north, highest]. The length of the array must be 2*n - where n is the number of dimensions. + or [west, south, lowest, east, north, highest]. The length of the array + must be 2*n where n is the number of dimensions. Returns: List[float] @@ -188,8 +193,8 @@ def bbox(self, v: Optional[List[float]]) -> None: def centroid(self) -> Optional[Dict[str, float]]: """Get or sets coordinates representing the centroid of the item in the asset data CRS. - Coordinates are defined in latitude and longitude, even if the data coordinate system - does not use lat/long. + Coordinates are defined in latitude and longitude, even if the data coordinate + system does not use lat/long. Exmample:: @@ -208,10 +213,10 @@ def centroid(self, v: Optional[Dict[str, float]]) -> None: def shape(self) -> Optional[List[int]]: """Get or sets the number of pixels in Y and X directions for the default grid. - The shape is an array of integers that represents the number of pixels in the most - common pixel grid used by the item's assets. The number of pixels should be specified - in Y, X order. If the shape is defined in an item's properties it is used as the default - shape for all assets that don't have an overriding shape. + The shape is an array of integers that represents the number of pixels in the + most common pixel grid used by the item's assets. The number of pixels should + be specified in Y, X order. If the shape is defined in an item's properties it + is used as the default shape for all assets that don't have an overriding shape. Returns: List[int] diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 47f9e0274..32f47e770 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -93,32 +93,37 @@ def apply( """Applies sar extension properties to the extended Item. Args: - instrument_mode (str): The name of the sensor acquisition mode that is commonly used. - This should be the short name, if available. For example, WV for "Wave mode." - frequency_band (FrequencyBand): The common name for the frequency band to make it easier - to search for bands across instruments. See section "Common Frequency Band Names" - for a list of accepted names. + instrument_mode (str): The name of the sensor acquisition mode that is + commonly used. This should be the short name, if available. For example, + WV for "Wave mode." + frequency_band (FrequencyBand): The common name for the frequency band to + make it easier to search for bands across instruments. See section + "Common Frequency Band Names" for a list of accepted names. polarizations (List[Polarization]): Any combination of polarizations. product_type (str): The product type, for example SSC, MGD, or SGC. center_frequency (float): Optional center frequency of the instrument in gigahertz (GHz). - resolution_range (float): Optional range resolution, which is the maximum ability to - distinguish two adjacent targets perpendicular to the flight path, in meters (m). - resolution_azimuth (float): Optional azimuth resolution, which is the maximum ability - to distinguish two adjacent targets parallel to the flight path, in meters (m). - pixel_spacing_range (float): Optional range pixel spacing, which is the distance - between adjacent pixels perpendicular to the flight path, in meters (m). Strongly - RECOMMENDED to be specified for products of type GRD. - pixel_spacing_azimuth (float): Optional azimuth pixel spacing, which is the distance - between adjacent pixels parallel to the flight path, in meters (m). Strongly - RECOMMENDED to be specified for products of type GRD. - looks_range (int): Optional number of groups of signal samples (looks) perpendicular - to the flight path. - looks_azimuth (int): Optional number of groups of signal samples (looks) parallel - to the flight path. + resolution_range (float): Optional range resolution, which is the maximum + ability to distinguish two adjacent targets perpendicular to the flight + path, in meters (m). + resolution_azimuth (float): Optional azimuth resolution, which is the + maximum ability to distinguish two adjacent targets parallel to the + flight path, in meters (m). + pixel_spacing_range (float): Optional range pixel spacing, which is the + distance between adjacent pixels perpendicular to the flight path, + in meters (m). Strongly RECOMMENDED to be specified for + products of type GRD. + pixel_spacing_azimuth (float): Optional azimuth pixel spacing, which is the + distance between adjacent pixels parallel to the flight path, in + meters (m). Strongly RECOMMENDED to be specified for products of + type GRD. + looks_range (int): Optional number of groups of signal samples (looks) + perpendicular to the flight path. + looks_azimuth (int): Optional number of groups of signal samples (looks) + parallel to the flight path. looks_equivalent_number (float): Optional equivalent number of looks (ENL). - observation_direction (ObservationDirection): Optional Antenna pointing direction - relative to the flight trajectory of the satellite. + observation_direction (ObservationDirection): Optional Antenna pointing + direction relative to the flight trajectory of the satellite. """ self.instrument_mode = instrument_mode self.frequency_band = frequency_band diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 759a78b9b..7e8a49959 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -54,10 +54,11 @@ def apply( for the sat extension to properties to be valid. 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. + 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. """ self.orbit_state = orbit_state diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 5813203d3..3127d702e 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -165,7 +165,8 @@ def remove_publication(self, publication: Optional[Publication] = None) -> None: """Removes publications from the item. Args: - publication (Publication): The specific publication to remove of None to remove all. + publication (Publication): The specific publication to remove of None to + remove all. """ if PUBLICATIONS not in self.properties: return diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 4536bdaae..8f27d363d 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -54,9 +54,9 @@ def published(self) -> Optional[Datetime]: 'Published' has a different meaning depending on where it is used. If available in - the asset properties, it refers to the timestamps valid for the actual data linked - to the Asset Object. If it comes from the Item properties, it's referencing to - the timestamp valid for the metadata. + the asset properties, it refers to the timestamps valid for the actual data + linked to the Asset Object. If it comes from the Item properties, it's + referencing to the timestamp valid for the metadata. Returns: datetime @@ -75,9 +75,9 @@ def expires(self) -> Optional[Datetime]: 'Unpublished' has a different meaning depending on where it is used. If available in - the asset properties, it refers to the timestamps valid for the actual data linked - to the Asset Object. If it comes from the Item properties, it's referencing to - the timestamp valid for the metadata. + the asset properties, it refers to the timestamps valid for the actual data + linked to the Asset Object. If it comes from the Item properties, it's + referencing to the timestamp valid for the metadata. Returns: datetime @@ -95,9 +95,9 @@ def unpublished(self) -> Optional[Datetime]: 'Unpublished' has a different meaning depending on where it is used. If available in - the asset properties, it refers to the timestamps valid for the actual data linked - to the Asset Object. If it comes from the Item properties, it's referencing to - the timestamp valid for the metadata. + the asset properties, it refers to the timestamps valid for the actual data + linked to the Asset Object. If it comes from the Item properties, it's + referencing to the timestamp valid for the metadata. Returns: datetime diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index c51dd16d6..626450ee6 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -21,9 +21,10 @@ class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): """ViewItemExt is the extension of the Item in the View Geometry Extension. + View Geometry adds metadata related to angles of sensors and other radiance angles - that affect the view of resulting data. It will often be combined with other extensions that - describe the actual data, such as the eo, sat or sar extensions. + that affect the view of resulting data. It will often be combined with other + extensions that describe the actual data, such as the eo, sat or sar extensions. Args: item (Item): The item to be extended. @@ -49,16 +50,19 @@ def apply( Args: off_nadir (float): The angle from the sensor between nadir (straight down) and the scene center. Measured in degrees (0-90). - incidence_angle (float): The incidence angle is the angle between the vertical (normal) - to the intercepting surface and the line of sight back to the satellite at - the scene center. Measured in degrees (0-90). - azimuth (float): Viewing azimuth angle. The angle measured from the sub-satellite - point (point on the ground below the platform) between the scene center and true - north. Measured clockwise from north in degrees (0-360). - sun_azimuth (float): Sun azimuth angle. From the scene center point on the ground, this - is the angle between truth north and the sun. Measured clockwise in degrees (0-360). - sun_elevation (float): Sun elevation angle. The angle from the tangent of the scene - center point to the sun. Measured from the horizon in degrees (0-90). + incidence_angle (float): The incidence angle is the angle between the + vertical (normal) to the intercepting surface and the line of sight + back to the satellite at the scene center. Measured in degrees (0-90). + azimuth (float): Viewing azimuth angle. The angle measured from the + sub-satellite point (point on the ground below the platform) between + the scene center and true north. Measured clockwise from north in + degrees (0-360). + sun_azimuth (float): Sun azimuth angle. From the scene center point on the + ground, this is the angle between truth north and the sun. Measured + clockwise in degrees (0-360). + sun_elevation (float): Sun elevation angle. The angle from the tangent of + the scene center point to the sun. Measured from the horizon in + degrees (0-90). """ self.off_nadir = off_nadir self.incidence_angle = incidence_angle @@ -97,7 +101,9 @@ def incidence_angle(self, v: Optional[float]) -> None: @property def azimuth(self) -> Optional[float]: - """Get or sets the viewing azimuth angle. The angle measured from the sub-satellite + """Get or sets the viewing azimuth angle. + + The angle measured from the sub-satellite point (point on the ground below the platform) between the scene center and true north. Measured clockwise from north in degrees (0-360). @@ -112,8 +118,11 @@ def azimuth(self, v: Optional[float]) -> None: @property def sun_azimuth(self) -> Optional[float]: - """Get or sets the sun azimuth angle. From the scene center point on the ground, this - is the angle between truth north and the sun. Measured clockwise in degrees (0-360). + """Get or sets the sun azimuth angle. + + From the scene center point on the ground, this + is the angle between truth north and the sun. Measured clockwise in + degrees (0-360). Returns: float diff --git a/pystac/item.py b/pystac/item.py index 034d5299b..c8f1f7938 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -480,11 +480,11 @@ def get_created(self, asset: Optional[Asset] = None) -> Optional[Datetime]: returns the Asset's value. Otherwise returns the Item's value. Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + ``created`` and ``updated`` have different meaning depending on where they + are used. If those fields are available in the Item `properties`, it's + referencing to the creation and update times of the metadata. Having those + fields in the Item `assets` refers to the creation and update times of the + actual data linked to in the Asset Object. Returns: datetime @@ -523,11 +523,11 @@ def updated(self) -> Optional[Datetime]: the attribute as a string Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + ``created`` and ``updated`` have different meaning depending on where they + are used. If those fields are available in the Item `properties`, it's + referencing to the creation and update times of the metadata. Having those + fields in the Item `assets` refers to the creation and update times of the + actual data linked to in the Asset Object. Returns: @@ -547,11 +547,11 @@ def get_updated(self, asset: Optional[Asset] = None) -> Optional[Datetime]: returns the Asset's value. Otherwise returns the Item's value. Note: - ``created`` and ``updated`` have different meaning depending on where they are used. - If those fields are available in the Item `properties`, it's referencing to the - creation and update times of the metadata. Having those fields in the Item `assets` - refers to the creation and update times of the actual data linked to - in the Asset Object. + ``created`` and ``updated`` have different meaning depending on where they + are used. If those fields are available in the Item `properties`, it's + referencing to the creation and update times of the metadata. Having those + fields in the Item `assets` refers to the creation and update times of the + actual data linked to in the Asset Object. Returns: datetime @@ -591,12 +591,13 @@ class Item(STACObject): Args: id (str): Provider identifier. Must be unique within the STAC. - geometry (dict): Defines the full footprint of the asset represented by this item, - formatted according to `RFC 7946, section 3.1 (GeoJSON) - `_. - bbox (List[float] or None): Bounding Box of the asset represented by this item using - either 2D or 3D geometries. The length of the array must be 2*n where n is the - number of dimensions. Could also be None in the case of a null geometry. + geometry (dict): Defines the full footprint of the asset represented by this + item, formatted according to + `RFC 7946, section 3.1 (GeoJSON) `_. + bbox (List[float] or None): Bounding Box of the asset represented by this item + using either 2D or 3D geometries. The length of the array must be 2*n + where n is the number of dimensions. Could also be None in the case of a + null geometry. datetime (datetime or None): Datetime associated with this item. If None, a start_datetime and end_datetime must be supplied in the properties. properties (dict): A dictionary of additional metadata for the item. @@ -605,30 +606,33 @@ class Item(STACObject): self link's HREF. collection (Collection or str): The Collection or Collection ID that this item belongs to. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Item. + extra_fields (dict or None): Extra fields that are part of the top-level JSON + properties of the Item. Attributes: id (str): Provider identifier. Unique within the STAC. - geometry (dict): Defines the full footprint of the asset represented by this item, - formatted according to `RFC 7946, section 3.1 (GeoJSON) - `_. - bbox (List[float] or None): Bounding Box of the asset represented by this item using - either 2D or 3D geometries. The length of the array is 2*n where n is the - number of dimensions. Could also be None in the case of a null geometry. + geometry (dict): Defines the full footprint of the asset represented by this + item, formatted according to + `RFC 7946, section 3.1 (GeoJSON) `_. + bbox (List[float] or None): Bounding Box of the asset represented by this item + using either 2D or 3D geometries. The length of the array is 2*n where n + is the number of dimensions. Could also be None in the case of a null + geometry. datetime (datetime or None): Datetime associated with this item. If None, the start_datetime and end_datetime in the common_metadata will supply the datetime range of the Item. properties (dict): A dictionary of additional metadata for the item. - stac_extensions (List[str] or None): Optional list of extensions the Item implements. + stac_extensions (List[str] or None): Optional list of extensions the Item + implements. collection (Collection or None): Collection that this item is a part of. links (List[Link]): A list of :class:`~pystac.Link` objects representing all links associated with this STACObject. assets (Dict[str, Asset]): Dictionary of asset objects that can be downloaded, each with a unique key. - collection_id (str or None): The Collection ID that this item belongs to, if any. - extra_fields (dict or None): Extra fields that are part of the top-level JSON properties - of the Item. + collection_id (str or None): The Collection ID that this item belongs to, if + any. + extra_fields (dict or None): Extra fields that are part of the top-level JSON + properties of the Item. """ STAC_OBJECT_TYPE = STACObjectType.ITEM diff --git a/pystac/layout.py b/pystac/layout.py index c1b664a65..45d581328 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -119,9 +119,8 @@ def _get_template_value( if stac_object.collection_id is not None: return stac_object.collection_id raise TemplateError( - "Item {} does not have a collection ID set; cannot template {} in {}".format( - stac_object, template_var, self.template - ) + f"Item {stac_object} does not have a collection ID set; " + f"cannot template {template_var} in {self.template}" ) else: raise TemplateError( diff --git a/pystac/link.py b/pystac/link.py index 75ba7a62e..6c8a104e7 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -35,8 +35,8 @@ class Link: media_type (str): Optional description of the media type. Registered Media Types are preferred. See :class:`~pystac.MediaType` for common media types. title (str): Optional title for this link. - properties (dict): Optional, additional properties for this link. This is used by - extensions as a way to serialize and deserialize properties on link + properties (dict): Optional, additional properties for this link. This is used + by extensions as a way to serialize and deserialize properties on link object JSON. Attributes: @@ -44,15 +44,17 @@ class Link: target (str or STACObject): The target of the link. If the link is unresolved, or the link is to something that is not a STACObject, the target is an HREF. If resolved, the target is a STACObject. - media_type (str or None): Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. + media_type (str or None): Optional description of the media type. + Registered Media Types are preferred. See + :class:`~pystac.MediaType` for common media types. title (str or None): Optional title for this link. properties (dict or None): Optional, additional properties for this link. This is used by extensions as a way to serialize and deserialize properties on link object JSON. owner (STACObject or None): The owner of this link. The link will use - its owner's root catalog :class:`~pystac.resolved_object_cache.ResolvedObjectCache` - to resolve objects, and will create absolute HREFs from relative HREFs against + its owner's root catalog + :class:`~pystac.resolved_object_cache.ResolvedObjectCache` to resolve + objects, and will create absolute HREFs from relative HREFs against the owner's self HREF. """ diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 821817c59..7f5d05bb4 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -141,7 +141,8 @@ class STACJSONDescription: """Describes the STAC object information for a STAC object represented in JSON Attributes: - object_type (str): Describes the STAC object type. One of :class:`~pystac.STACObjectType`. + object_type (str): Describes the STAC object type. One of + :class:`~pystac.STACObjectType`. version_range (STACVersionRange): The STAC version range that describes what has been identified as potential valid versions of the stac object. extensions (List[str]): List of extension schema URIs for extensions this diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 69ffd2150..cd1b006f5 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -63,7 +63,8 @@ def write_text( The destination to write to from can be specified as a string or a Link. If it's a string, it's the URL of the HREF from which to - read. When writing based on links links, PySTAC will pass in the entire link body. + read. When writing based on links links, PySTAC will pass in the entire + link body. Args: dest (str or pystac.Link): The destination to write to. diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 5e98ffe85..d177f89a1 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -134,8 +134,8 @@ def self_href(self) -> str: :class:`~pystac.Link`. Raises: - ValueError: If the self_href is not set, this method will throw a ValueError. - Use get_self_href if there may not be an href set. + ValueError: If the self_href is not set, this method will throw + a ValueError. Use get_self_href if there may not be an href set. """ result = self.get_self_href() if result is None: @@ -293,17 +293,18 @@ def save_object( """Saves this STAC Object to it's 'self' HREF. Args: - include_self_link (bool): If this is true, include the 'self' link with this object. - Otherwise, leave out the self link. - dest_href (str): Optional HREF to save the file to. If None, the object will be saved - to the object's self href. + include_self_link (bool): If this is true, include the 'self' link with + this object. Otherwise, leave out the self link. + dest_href (str): Optional HREF to save the file to. If None, the object + will be saved to the object's self href. stac_io: Optional instance of StacIO to use. If not provided, will use the instance set on the object's root if available, otherwise will use the default instance. Raises: - :class:`~pystac.STACError`: If no self href is set, this error will be raised. + :class:`~pystac.STACError`: If no self href is set, this error will be + raised. Note: When to include a self link is described in the `Use of Links section of the @@ -382,23 +383,6 @@ def full_copy( return clone - # @property - # def ext(self) -> "ExtensionIndex": - # """Access extensions for this STACObject. - - # Example: - # This example shows accessing a Item's EO extension functionality - # that gets the band information for an asset:: - - # item = pystac.read_file("eo_item.json") - # bands = item.ext.eo.get_asset_bands(item.assets["image"]) - - # Returns: - # ExtensionIndex: The object that can be used to access extension information - # and functionality. - # """ - # return ExtensionIndex(self) - def resolve_links(self) -> None: """Ensure all STACObjects linked to by this STACObject are resolved. This is important for operations such as changing diff --git a/pystac/utils.py b/pystac/utils.py index 482873101..875cea924 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -53,7 +53,8 @@ def make_relative_href( start_href (str): The HREF that the resulting HREF will be relative with respect to. start_is_dir (str): If True, the start_href is treated as a directory. - Otherwise, the start_href is considered to be a file HREF. Defaults to False. + Otherwise, the start_href is considered to be a file HREF. + Defaults to False. Returns: str: The relative HREF. If the source_href and start_href do not share a common @@ -94,7 +95,8 @@ def make_absolute_href( relative paths, if source_href is a relative path. Defaults to the current working directory. start_is_dir (str): If True, the start_href is treated as a directory. - Otherwise, the start_href is considered to be a file HREF. Defaults to False. + Otherwise, the start_href is considered to be a file HREF. + Defaults to False. Returns: str: The absolute HREF. If the source_href is already an absolute href, diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index c9ccab928..678fae0a5 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -21,7 +21,8 @@ def get_object_schema_uri( """Get the schema URI for the given object type and stac version. Args: - object_type (STACObjectType): STAC object type. One of :class:`~pystac.STACObjectType` + object_type (STACObjectType): STAC object type. One of + :class:`~pystac.STACObjectType` stac_version (str): The STAC version of the schema to return. Returns: @@ -31,7 +32,7 @@ def get_object_schema_uri( class DefaultSchemaUriMap(SchemaUriMap): - """Implementation of SchemaUriMap that uses schemas hosted by https://schemas.stacspec.org. + """Implementation of SchemaUriMap that uses schemas hosted by stacspec.org For STAC Versions 0.9.0 or earlier this will use the schemas hosted on the radiantearth/stac-spec GitHub repo. @@ -40,8 +41,8 @@ class DefaultSchemaUriMap(SchemaUriMap): # BASE_URIS contains a list of tuples, the first element is a version range and the # second being the base URI for schemas for that range. The schema URI of a STAC # for a particular version uses the base URI associated with the version range which - # contains it. If the version it outside of any VersionRange, there is no URI for the - # schema. + # contains it. If the version it outside of any VersionRange, there is no URI for + # the schema. BASE_URIS: List[Tuple[STACVersionRange, Callable[[str], str]]] = [ ( STACVersionRange(min_version="1.0.0-beta.1"), @@ -49,19 +50,20 @@ class DefaultSchemaUriMap(SchemaUriMap): ), ( STACVersionRange(min_version="0.8.0", max_version="0.9.0"), - lambda version: "https://raw.githubusercontent.com/radiantearth/stac-spec/v{}".format( - version + lambda version: ( + f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{version}" ), ), ] - # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema URIs - # based on the stac object type and the stac version, using a similar technique as BASE_URIS. - # Uris are contained in a tuple whose first element represents the URI of the latest - # version, so that a search through version ranges is avoided if the STAC being validated + # DEFAULT_SCHEMA_MAP contains a structure that matches 'core' or 'extension' schema + # URIs based on the stac object type and the stac version, using a similar + # technique as BASE_URIS. Uris are contained in a tuple whose first element + # represents the URI of the latest version, so that a search through version + # ranges is avoided if the STAC being validated # is the latest version. If it's a previous version, the stac_version that matches - # the listed version range is used, or else the URI from the latest version is used if - # there are no overrides for previous versions. + # the listed version range is used, or else the URI from the latest version is used + # if there are no overrides for previous versions. DEFAULT_SCHEMA_MAP: Dict[str, Any] = { STACObjectType.CATALOG: ("catalog-spec/json-schema/catalog.json", None), STACObjectType.COLLECTION: ( @@ -123,8 +125,8 @@ class OldExtensionSchemaUriMap: # BASE_URIS contains a list of tuples, the first element is a version range and the # second being the base URI for schemas for that range. The schema URI of a STAC # for a particular version uses the base URI associated with the version range which - # contains it. If the version it outside of any VersionRange, there is no URI for the - # schema. + # contains it. If the version it outside of any VersionRange, there is no URI for + # the schema. @classmethod @lru_cache() def get_base_uris( @@ -133,12 +135,13 @@ def get_base_uris( return [ ( STACVersionRange(min_version="1.0.0-beta.1"), - lambda version: "https://schemas.stacspec.org/v{}".format(version), + lambda version: f"https://schemas.stacspec.org/v{version}", ), ( STACVersionRange(min_version="0.8.0", max_version="0.9.0"), - lambda version: "https://raw.githubusercontent.com/radiantearth/stac-spec/v{}".format( - version + lambda version: ( + "https://raw.githubusercontent.com/" + f"radiantearth/stac-spec/v{version}" ), ), ] @@ -146,32 +149,44 @@ def get_base_uris( # DEFAULT_SCHEMA_MAP contains a structure that matches extension schema URIs # based on the stac object type, extension ID and the stac version. # Uris are contained in a tuple whose first element represents the URI of the latest - # version, so that a search through version ranges is avoided if the STAC being validated - # is the latest version. If it's a previous version, the stac_version that matches - # the listed version range is used, or else the URI from the latest version is used if - # there are no overrides for previous versions. + # version, so that a search through version ranges is avoided if the STAC being + # validated is the latest version. If it's a previous version, the stac_version + # that matches the listed version range is used, or else the URI from the latest + # version is used if there are no overrides for previous versions. @classmethod @lru_cache() def get_schema_map(cls) -> Dict[str, Any]: return { OldExtensionShortIDs.CHECKSUM.value: ( { - ps.STACObjectType.CATALOG: "extensions/checksum/json-schema/schema.json", - ps.STACObjectType.COLLECTION: "extensions/checksum/json-schema/schema.json", - ps.STACObjectType.ITEM: "extensions/checksum/json-schema/schema.json", + ps.STACObjectType.CATALOG: ( + "extensions/checksum/json-schema/schema.json" + ), + ps.STACObjectType.COLLECTION: ( + "extensions/checksum/json-schema/schema.json" + ), + ps.STACObjectType.ITEM: ( + "extensions/checksum/json-schema/schema.json" + ), }, None, ), OldExtensionShortIDs.COLLECTION_ASSETS.value: ( { - ps.STACObjectType.COLLECTION: "extensions/collection-assets/json-schema/schema.json" + ps.STACObjectType.COLLECTION: ( + "extensions/collection-assets/json-schema/schema.json" + ) }, None, ), OldExtensionShortIDs.DATACUBE.value: ( { - ps.STACObjectType.COLLECTION: "extensions/datacube/json-schema/schema.json", - ps.STACObjectType.ITEM: "extensions/datacube/json-schema/schema.json", + ps.STACObjectType.COLLECTION: ( + "extensions/datacube/json-schema/schema.json" + ), + ps.STACObjectType.ITEM: ( + "extensions/datacube/json-schema/schema.json" + ), }, [ ( @@ -189,7 +204,9 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.ITEM_ASSETS.value: ( { - ps.STACObjectType.COLLECTION: "extensions/item-assets/json-schema/schema.json" + ps.STACObjectType.COLLECTION: ( + "extensions/item-assets/json-schema/schema.json" + ) }, None, ), @@ -209,7 +226,9 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.PROJECTION.value: ( { - ps.STACObjectType.ITEM: "extensions/projection/json-schema/schema.json" + ps.STACObjectType.ITEM: ( + "extensions/projection/json-schema/schema.json" + ) }, None, ), @@ -223,35 +242,53 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.SCIENTIFIC.value: ( { - ps.STACObjectType.ITEM: "extensions/scientific/json-schema/schema.json", - ps.STACObjectType.COLLECTION: "extensions/scientific/json-schema/schema.json", + ps.STACObjectType.ITEM: ( + "extensions/scientific/json-schema/schema.json" + ), + ps.STACObjectType.COLLECTION: ( + "extensions/scientific/json-schema/schema.json" + ), }, None, ), OldExtensionShortIDs.SINGLE_FILE_STAC.value: ( { - ps.STACObjectType.CATALOG: "extensions/single-file-stac/json-schema/schema.json" + ps.STACObjectType.CATALOG: ( + "extensions/single-file-stac/json-schema/schema.json" + ) }, None, ), OldExtensionShortIDs.TILED_ASSETS.value: ( { - ps.STACObjectType.CATALOG: "extensions/tiled-assets/json-schema/schema.json", - ps.STACObjectType.COLLECTION: "extensions/tiled-assets/json-schema/schema.json", - ps.STACObjectType.ITEM: "extensions/tiled-assets/json-schema/schema.json", + ps.STACObjectType.CATALOG: ( + "extensions/tiled-assets/json-schema/schema.json" + ), + ps.STACObjectType.COLLECTION: ( + "extensions/tiled-assets/json-schema/schema.json" + ), + ps.STACObjectType.ITEM: ( + "extensions/tiled-assets/json-schema/schema.json" + ), }, None, ), OldExtensionShortIDs.TIMESTAMPS.value: ( { - ps.STACObjectType.ITEM: "extensions/timestamps/json-schema/schema.json" + ps.STACObjectType.ITEM: ( + "extensions/timestamps/json-schema/schema.json" + ) }, None, ), OldExtensionShortIDs.VERSION.value: ( { - ps.STACObjectType.ITEM: "extensions/version/json-schema/schema.json", - ps.STACObjectType.COLLECTION: "extensions/version/json-schema/schema.json", + ps.STACObjectType.ITEM: ( + "extensions/version/json-schema/schema.json" + ), + ps.STACObjectType.COLLECTION: ( + "extensions/version/json-schema/schema.json" + ), }, None, ), @@ -267,7 +304,9 @@ def get_schema_map(cls) -> Dict[str, Any]: ( STACVersionRange(min_version="0.8.0-rc1", max_version="0.9.0"), { - ps.STACObjectType.COLLECTION: "extensions/asset/json-schema/schema.json" + ps.STACObjectType.COLLECTION: ( + "extensions/asset/json-schema/schema.json" + ) }, ) ], diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index f5354a09e..4861e0cd4 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -40,8 +40,8 @@ def validate_core( Args: stac_dict (dict): Dictionary that is the STAC json of the object. - stac_object_type (str): The stac object type of the object encoded in stac_dict. - One of :class:`~pystac.STACObjectType`. + stac_object_type (str): The stac object type of the object encoded + in stac_dict. One of :class:`~pystac.STACObjectType`. stac_version (str): The version of STAC to validate the object against. href (str): Optional HREF of the STAC object being validated. """ @@ -62,8 +62,8 @@ def validate_extension( Args: stac_dict (dict): Dictionary that is the STAC json of the object. - stac_object_type (str): The stac object type of the object encoded in stac_dict. - One of :class:`~pystac.STACObjectType`. + stac_object_type (str): The stac object type of the object encoded in + stac_dict. One of :class:`~pystac.STACObjectType`. stac_version (str): The version of STAC to validate the object against. extension_id (str): The extension ID of the extension to validate against. href (str): Optional HREF of the STAC object being validated. @@ -82,8 +82,8 @@ def validate( Args: stac_dict (dict): Dictionary that is the STAC json of the object. - stac_object_type (str): The stac object type of the object encoded in stac_dict. - One of :class:`~pystac.STACObjectType`. + stac_object_type (str): The stac object type of the object encoded in + stac_dict. One of :class:`~pystac.STACObjectType`. stac_version (str): The version of STAC to validate the object against. extensions (List[str]): Extension IDs for this stac object. href (str): Optional href of the STAC object being validated. @@ -197,8 +197,8 @@ def validate_core( Args: stac_dict (dict): Dictionary that is the STAC json of the object. - stac_object_type (str): The stac object type of the object encoded in stac_dict. - One of :class:`~pystac.STACObjectType`. + stac_object_type (str): The stac object type of the object encoded in + stac_dict. One of :class:`~pystac.STACObjectType`. stac_version (str): The version of STAC to validate the object against. href (str): Optional HREF of the STAC object being validated. @@ -236,8 +236,8 @@ def validate_extension( Args: stac_dict (dict): Dictionary that is the STAC json of the object. - stac_object_type (str): The stac object type of the object encoded in stac_dict. - One of :class:`~pystac.STACObjectType`. + stac_object_type (str): The stac object type of the object encoded in + stac_dict. One of :class:`~pystac.STACObjectType`. stac_version (str): The version of STAC to validate the object against. extension_id (str): The extension ID to validate against. href (str): Optional HREF of the STAC object being validated. diff --git a/pystac/version.py b/pystac/version.py index 426589ab2..4176f0428 100644 --- a/pystac/version.py +++ b/pystac/version.py @@ -34,9 +34,10 @@ def get_stac_version() -> str: """Returns the STAC version PySTAC writes as the "stac_version" property for any object it serializes into JSON. - If a call to ``set_stac_version`` was made, this will return the value it was called with. - Next it will check the environment for a PYSTAC_STAC_VERSION_OVERRIDE variable. Otherwise - it will return the latest STAC version that this version of PySTAC supports. + If a call to ``set_stac_version`` was made, this will return the value it was + called with. Next it will check the environment for a PYSTAC_STAC_VERSION_OVERRIDE + variable. Otherwise it will return the latest STAC version that this version of + PySTAC supports. Returns: str: The STAC Version PySTAC is set up to use. @@ -56,13 +57,14 @@ def set_stac_version(stac_version: Optional[str]) -> None: the version. Args: - stac_version (str): The STAC version to use instead of the latest STAC version that - PySTAC supports (described in STACVersion.DEFAULT_STAC_VERSION). If None, - clear to use the default for this version of PySTAC. + stac_version (str): The STAC version to use instead of the latest STAC version + that PySTAC supports (described in STACVersion.DEFAULT_STAC_VERSION). + If None, clear to use the default for this version of PySTAC. Note: - Setting the STAC version to something besides the default version will not effect - the format of STAC read or written; it will only override the ``stac_version`` property - of the objects being written. Setting this incorrectly can produce invalid STAC. + Setting the STAC version to something besides the default version will not + effect the format of STAC read or written; it will only override the + ``stac_version`` property of the objects being written. Setting this + incorrectly can produce invalid STAC. """ STACVersion.set_stac_version(stac_version) diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index 104ea2dd9..f4e937374 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -78,7 +78,8 @@ def test_asset_nodata(self): def test_migrates_old_checksum(self): example_path = TestCases.get_path( - "data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json" + "data-files/examples/1.0.0-beta.2/" + "extensions/checksum/examples/sentinel1.json" ) item = ps.Item.from_file(example_path) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 34ba375ec..a1951490b 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -84,7 +84,8 @@ def test_create_and_read(self): def test_read_remote(self): # TODO: Move this URL to the main stac-spec repo once the example JSON is fixed. catalog_url = ( - "https://raw.githubusercontent.com/lossyrob/stac-spec/0.9.0/pystac-upgrade-fixes" + "https://raw.githubusercontent.com/lossyrob/stac-spec/" + "0.9.0/pystac-upgrade-fixes" "/extensions/label/examples/multidataset/catalog.json" ) cat = Catalog.from_file(catalog_url) @@ -329,7 +330,8 @@ def test_normalize_href_works_with_label_source_links(self): source = next(iter(LabelExtension.ext(item).get_sources())) self.assertEqual( source.get_self_href(), - "http://example.com/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json", + "http://example.com/country-1/area-1-1/" + "area-1-1-imagery/area-1-1-imagery.json", ) def test_generate_subcatalogs_works_with_custom_properties(self): diff --git a/tests/test_item.py b/tests/test_item.py index be2955d82..4654653f3 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -30,7 +30,10 @@ def test_to_from_dict(self): item = Item.from_dict(item_dict) self.assertEqual( item.get_self_href(), - "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.json", + ( + "http://cool-sat.com/catalog/CS3-20160503_132130_04/" + "CS3-20160503_132130_04.json" + ), ) # test asset creation additional field(s) diff --git a/tests/test_layout.py b/tests/test_layout.py index 4333cdce6..a99d5099b 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -160,7 +160,8 @@ def test_defaults(self): def test_docstring_examples(self): item = ps.Item.from_file( TestCases.get_path( - "data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json" + "data-files/examples/1.0.0-beta.2/item-spec/" + "examples/landsat8-sample.json" ) ) item.common_metadata.license = "CC-BY-3.0" diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 331fb2f15..46e4323ca 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -207,7 +207,7 @@ def test_case_4(): """Test case that is based on a local copy of the Tier 1 dataset from DrivenData's OpenCities AI Challenge. See: https://www.drivendata.org/competitions/60/building-segmentation-disaster-resilience - """ + """ # noqa return Catalog.from_file( TestCases.get_path("data-files/catalogs/test-case-4/catalog.json") ) diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 117aa2e0c..bcc3906f4 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -96,8 +96,8 @@ def test_validate_all(self): pystac.validation.validate_all(stac_dict, catalog_href) - # Modify a 0.8.1 collection in a catalog to be invalid with a since-renamed extension - # and make sure it catches the validation error. + # Modify a 0.8.1 collection in a catalog to be invalid with a + # since-renamed extension and make sure it catches the validation error. with TemporaryDirectory() as tmp_dir: dst_dir = os.path.join(tmp_dir, "catalog") From 07f5cc6442a97d806a009883aa7116d77ee5d91a Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Sun, 2 May 2021 15:05:04 -0400 Subject: [PATCH 36/51] Bump sphinx version Napoleon is now included. --- requirements-dev.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b46289357..3b00ec452 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,10 +6,9 @@ codespell==1.17.1 ipython==7.16.1 jsonschema==3.2.0 pylint==1.9.1 -Sphinx==1.8.0 +Sphinx==3.5.4 sphinx-autobuild==0.7.1 sphinxcontrib-fulltoc==1.2.0 -sphinxcontrib-napoleon==0.7 nbsphinx==0.7.1 coverage==5.2.* From a45d5e605f01bca153a816f874e6e65b3d8d2d4d Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Sun, 2 May 2021 15:05:28 -0400 Subject: [PATCH 37/51] Fix basic sphinx build issues --- docs/api.rst | 162 ++++++++++++++++++++++++++-------------------- docs/concepts.rst | 4 +- pystac/catalog.py | 2 +- pystac/stac_io.py | 10 +-- 4 files changed, 100 insertions(+), 78 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 74a304fdc..9279dfbb1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -127,13 +127,6 @@ Link :members: :undoc-members: -LinkType -~~~~~~~~ - -.. autoclass:: pystac.LinkType - :members: - :undoc-members: - MediaType ~~~~~~~~~ @@ -149,7 +142,7 @@ STAC_IO STAC_IO is the utility mechanism that PySTAC uses for reading and writing. Users of PySTAC can hook into PySTAC by overriding members to utilize their own IO methods. -.. autoclass:: pystac.STAC_IO +.. autoclass:: pystac.stac_io.STAC_IO :members: :undoc-members: @@ -193,17 +186,20 @@ ExtensionError Extensions ---------- -.. autoclass:: pystac.extensions.Extensions - :members: - :undoc-members: +**TEMPORARILY REMOVED** +.. .. autoclass:: pystac.extensions.Extensions +.. :members: +.. :undoc-members: ExtensionIndex ~~~~~~~~~~~~~~ -An ExtensionIndex is accessed through the :attr:`STACObject.ext ` property and is the primary way to access information and functionality around STAC extensions. +**TEMPORARILY REMOVED** + +.. An ExtensionIndex is accessed through the :attr:`STACObject.ext ` property and is the primary way to access information and functionality around STAC extensions. -.. autoclass:: pystac.stac_object.ExtensionIndex - :members: __getitem__, __getattr__, enable, implements +.. .. autoclass:: pystac.stac_object.ExtensionIndex +.. :members: __getitem__, __getattr__, enable, implements EO Extension @@ -214,17 +210,21 @@ These classes are representations of the `EO Extension Spec `_. -.. automodule:: pystac.extensions.single_file_stac - :members: create_single_file_stac +**TEMPORARILY REMOVED** + +.. .. automodule:: pystac.extensions.single_file_stac +.. :members: create_single_file_stac SingleFileSTACCatalogExt ~~~~~~~~~~~~~~~~~~~~~~~~ -.. autoclass:: pystac.extensions.single_file_stac.SingleFileSTACCatalogExt - :members: - :undoc-members: +**TEMPORARILY REMOVED** + +.. .. autoclass:: pystac.extensions.single_file_stac.SingleFileSTACCatalogExt +.. :members: +.. :undoc-members: Version Extension ----------------- @@ -363,18 +379,22 @@ Implements the `Version Extension `_ and the relevant `Best Practices `_ for more information. +See the STAC documentation on `Additional Fields for Assets `_ and the relevant `Best Practices `__ for more information. The implementation of this feature in PySTAC uses the method described here and is consistent across Item and ItemExtensions. The bare property names represent values for the Item only, but for each property where it is possible to set on both the Item or the Asset there is a ``get_`` and ``set_`` methods that optionally take an Asset. For the ``get_`` methods, if the property is found on the Asset, the Asset's value is used; otherwise the Item's value will be used. For the ``set_`` method, if an Asset is passed in the value will be applied to the Asset and not the Item. diff --git a/pystac/catalog.py b/pystac/catalog.py index 8cf6655ec..e7ae96351 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -221,7 +221,7 @@ def add_child( self.add_link(Link.child(child, title=title)) def add_children(self, children: Iterable["Catalog"]) -> None: - """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection`s. + """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection`\s. This method will set each child's parent to this object, and their root to this Catalog's root. diff --git a/pystac/stac_io.py b/pystac/stac_io.py index cd1b006f5..f3d414e73 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -286,7 +286,7 @@ def read_text(cls, uri: str) -> str: Note: This method uses the :func:`STAC_IO.read_text_method - `. If you want to modify the behavior of + `. If you want to modify the behavior of STAC_IO in order to enable additional URI types, replace that member with your own implementation. """ @@ -302,7 +302,7 @@ def write_text(cls, uri: str, txt: str) -> None: Note: This method uses the :func:`STAC_IO.write_text_method - `. If you want to modify the behavior of + `. If you want to modify the behavior of STAC_IO in order to enable additional URI types, replace that member with your own implementation. """ @@ -321,7 +321,7 @@ def read_json(cls, uri: str) -> Dict[str, Any]: Note: This method uses the :func:`STAC_IO.read_text_method - `. If you want to modify the behavior of + `. If you want to modify the behavior of STAC_IO in order to enable additional URI types, replace that member with your own implementation. """ @@ -345,7 +345,7 @@ def read_stac_object( Note: This method uses the :func:`STAC_IO.read_text_method - `. If you want to modify the behavior of + `. If you want to modify the behavior of STAC_IO in order to enable additional URI types, replace that member with your own implementation. """ @@ -362,7 +362,7 @@ def save_json(cls, uri: str, json_dict: Dict[str, Any]) -> None: Note: This method uses the :func:`STAC_IO.write_text_method - `. If you want to modify the behavior of + `. If you want to modify the behavior of STAC_IO in order to enable additional URI types, replace that member with your own implementation. """ From eccdf8c8cd8b96aef1b7106e6e5cc4b35813bb33 Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Sun, 2 May 2021 15:06:00 -0400 Subject: [PATCH 38/51] Add note about Sphinx warnings --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index da67308aa..4e6e96de2 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,14 @@ To build and develop the documentation locally, make sure sphinx is available (w > make livehtml ``` +> Note: You will see some warnings along the lines of +> ``` +> WARNING: duplicate object description of pystac.Collection.id, +> other instance in api, use :noindex: for one of them +> ``` +> for some of the +> classes. This is expected due to [sphinx-doc/sphinx#8664](https://github.com/sphinx-doc/sphinx/issues/8664). + Use 'make' without arguments to see a list of available commands. __Note__: `nbsphinx` requires that a local `pystac` is installed; use `pip install -e .`. From 773a5717eaeac256a28d0f8c5eee1f9cc13c49e2 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 11:13:50 -0400 Subject: [PATCH 39/51] Remove import pystac as ps in favor of import pystac See https://github.com/stac-utils/pystac/pull/309#discussion_r624385581 --- pystac/asset.py | 4 +- pystac/cache.py | 12 ++-- pystac/catalog.py | 52 ++++++++--------- pystac/collection.py | 8 +-- pystac/extensions/base.py | 8 +-- pystac/extensions/datacube.py | 34 +++++------ pystac/extensions/eo.py | 28 ++++----- pystac/extensions/file.py | 30 +++++----- pystac/extensions/hooks.py | 4 +- pystac/extensions/item_assets.py | 16 ++--- pystac/extensions/label.py | 71 +++++++++++++---------- pystac/extensions/pointcloud.py | 52 ++++++++--------- pystac/extensions/projection.py | 24 ++++---- pystac/extensions/sar.py | 24 ++++---- pystac/extensions/sat.py | 24 ++++---- pystac/extensions/scientific.py | 32 +++++----- pystac/extensions/timestamps.py | 22 +++---- pystac/extensions/version.py | 36 ++++++------ pystac/extensions/view.py | 24 ++++---- pystac/item.py | 12 ++-- pystac/layout.py | 14 ++--- pystac/link.py | 20 +++---- pystac/serialization/__init__.py | 16 ++--- pystac/serialization/common_properties.py | 8 +-- pystac/serialization/identify.py | 38 ++++++------ pystac/serialization/migrate.py | 41 ++++++++----- pystac/stac_io.py | 4 +- pystac/stac_object.py | 36 +++++++----- pystac/validation/__init__.py | 10 ++-- pystac/validation/schema_uri_map.py | 62 +++++++++++--------- pystac/validation/stac_validator.py | 4 +- tests/data-files/change_stac_version.py | 6 +- tests/data-files/get_examples.py | 10 ++-- tests/extensions/test_custom.py | 58 +++++++++--------- tests/extensions/test_eo.py | 22 +++---- tests/extensions/test_file.py | 16 ++--- tests/extensions/test_label.py | 28 ++++----- tests/extensions/test_pointcloud.py | 22 +++---- tests/extensions/test_projection.py | 24 ++++---- tests/extensions/test_sar.py | 10 ++-- tests/extensions/test_sat.py | 12 ++-- tests/extensions/test_scientific.py | 18 +++--- tests/extensions/test_timestamps.py | 12 ++-- tests/extensions/test_version.py | 22 +++---- tests/extensions/test_view.py | 16 ++--- tests/serialization/test_identify.py | 6 +- tests/serialization/test_migrate.py | 18 +++--- tests/test_cache.py | 6 +- tests/test_catalog.py | 50 ++++++++-------- tests/test_collection.py | 14 ++--- tests/test_item.py | 48 ++++++++------- tests/test_layout.py | 40 +++++++------ tests/test_link.py | 26 ++++----- tests/test_version.py | 8 +-- tests/test_writing.py | 31 +++++----- tests/utils/__init__.py | 4 +- tests/utils/stac_io_mock.py | 14 +++-- tests/utils/test_cases.py | 6 +- tests/validation/test_schema_uri_map.py | 4 +- tests/validation/test_validate.py | 18 +++--- 60 files changed, 698 insertions(+), 641 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index b6c21e954..b34d25582 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -1,7 +1,7 @@ from copy import copy from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union -import pystac as ps +import pystac from pystac.utils import is_absolute_href, make_absolute_href if TYPE_CHECKING: @@ -64,7 +64,7 @@ def __init__( self.properties = {} # The Item which owns this Asset. - self.owner: Optional[Union[ps.Item, ps.Collection]] = None + self.owner: Optional[Union[pystac.Item, pystac.Collection]] = None def set_owner(self, obj: Union["Collection_Type", "Item_Type"]) -> None: """Sets the owning item of this Asset. diff --git a/pystac/cache.py b/pystac/cache.py index a8c9291ce..117b0f23d 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -2,7 +2,7 @@ from copy import copy from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, cast -import pystac as ps +import pystac if TYPE_CHECKING: from pystac.stac_object import STACObject as STACObject_Type @@ -26,7 +26,7 @@ def get_cache_key(stac_object: "STACObject_Type") -> Tuple[str, bool]: return (href, True) else: ids: List[str] = [] - obj: Optional[ps.STACObject] = stac_object + obj: Optional[pystac.STACObject] = stac_object while obj is not None: ids.append(obj.id) obj = obj.get_parent() @@ -149,7 +149,7 @@ def cache(self, obj: "STACObject_Type") -> None: else: self.id_keys_to_objects[key] = obj - if isinstance(obj, ps.Collection): + if isinstance(obj, pystac.Collection): self.ids_to_collections[obj.id] = obj def remove(self, obj: "STACObject_Type") -> None: @@ -165,7 +165,7 @@ def remove(self, obj: "STACObject_Type") -> None: else: self.id_keys_to_objects.pop(key, None) - if obj.STAC_OBJECT_TYPE == ps.STACObjectType.COLLECTION: + if obj.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION: self.id_keys_to_objects.pop(obj.id, None) def __contains__(self, obj: "STACObject_Type") -> bool: @@ -260,7 +260,7 @@ def cache( href: Optional[str] = None, ) -> None: """Caches a collection JSON.""" - if isinstance(collection, ps.Collection): + if isinstance(collection, pystac.Collection): self.cached_ids[collection.id] = collection else: self.cached_ids[collection["id"]] = collection @@ -295,7 +295,7 @@ def get_by_href( if result is None: return super().get_by_href(href) else: - return cast(ps.Collection, result) + return cast(pystac.Collection, result) def contains_id(self, collection_id: str) -> bool: return self.resolved_object_cache.contains_collection_id( diff --git a/pystac/catalog.py b/pystac/catalog.py index 8cf6655ec..8c47f5139 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -14,7 +14,7 @@ cast, ) -import pystac as ps +import pystac from pystac.stac_object import STACObject from pystac.layout import ( BestPracticesLayoutStrategy, @@ -125,9 +125,9 @@ class Catalog(STACObject): catalog_type (str): The catalog type. Defaults to ABSOLUTE_PUBLISHED """ - STAC_OBJECT_TYPE = ps.STACObjectType.CATALOG + STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG - _stac_io: Optional[ps.StacIO] = None + _stac_io: Optional[pystac.StacIO] = None """Optional instance of StacIO that will be used by default for any IO operations on objects contained by this catalog. Set while reading in a catalog. This is set when a catalog @@ -203,8 +203,8 @@ def add_child( """ # Prevent typo confusion - if isinstance(child, ps.Item): - raise ps.STACError("Cannot add item as child. Use add_item instead.") + if isinstance(child, pystac.Item): + raise pystac.STACError("Cannot add item as child. Use add_item instead.") if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -247,8 +247,8 @@ def add_item( """ # Prevent typo confusion - if isinstance(item, ps.Catalog): - raise ps.STACError("Cannot add catalog as item. Use add_child instead.") + if isinstance(item, pystac.Catalog): + raise pystac.STACError("Cannot add catalog as item. Use add_child instead.") if strategy is None: strategy = BestPracticesLayoutStrategy() @@ -303,7 +303,7 @@ def get_children(self) -> Iterable["Catalog"]: Iterable[Catalog]: Generator of children who's parent is this catalog. """ - return map(lambda x: cast(ps.Catalog, x), self.get_stac_objects("child")) + return map(lambda x: cast(pystac.Catalog, x), self.get_stac_objects("child")) def get_child_links(self) -> List[Link]: """Return all child links of this catalog. @@ -329,7 +329,7 @@ def remove_child(self, child_id: str) -> None: Args: child_id (str): The ID of the child to remove. """ - new_links: List[ps.Link] = [] + new_links: List[pystac.Link] = [] root = self.get_root() for link in self.links: if link.rel != "child": @@ -371,7 +371,7 @@ def get_items(self) -> Iterable["Item_Type"]: Return: Iterable[Item]: Generator of items who's parent is this catalog. """ - return map(lambda x: cast(ps.Item, x), self.get_stac_objects("item")) + return map(lambda x: cast(pystac.Item, x), self.get_stac_objects("item")) def clear_items(self) -> None: """Removes all items from this catalog. @@ -381,7 +381,7 @@ def clear_items(self) -> None: """ for link in self.get_item_links(): if link.is_resolved(): - item = cast(ps.Item, link.target) + item = cast(pystac.Item, link.target) item.set_parent(None) item.set_root(None) @@ -393,14 +393,14 @@ def remove_item(self, item_id: str) -> None: Args: item_id (str): The ID of the item to remove. """ - new_links: List[ps.Link] = [] + new_links: List[pystac.Link] = [] root = self.get_root() for link in self.links: if link.rel != "item": new_links.append(link) else: link.resolve_stac_object(root=root) - item = cast(ps.Item, link.target) + item = cast(pystac.Item, link.target) if item.id != item_id: new_links.append(link) else: @@ -437,7 +437,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d: Dict[str, Any] = { "type": self.STAC_OBJECT_TYPE.value.title(), "id": self.id, - "stac_version": ps.get_stac_version(), + "stac_version": pystac.get_stac_version(), "description": self.description, "links": [link.to_dict() for link in links], } @@ -630,7 +630,7 @@ def generate_subcatalogs( item_links = [lk for lk in self.links if lk.rel == "item"] for link in item_links: link.resolve_stac_object(root=self.get_root()) - item = cast(ps.Item, link.target) + item = cast(pystac.Item, link.target) item_parts = layout_template.get_template_values(item) id_iter = reversed(parent_ids) if all( @@ -652,7 +652,7 @@ def generate_subcatalogs( subcat_desc = "Catalog of items from {} with {} of {}".format( curr_parent.id, k, v ) - subcat = ps.Catalog(id=subcat_id, description=subcat_desc) + subcat = pystac.Catalog(id=subcat_id, description=subcat_desc) curr_parent.add_child(subcat) result.append(subcat) curr_parent = subcat @@ -703,7 +703,7 @@ def save(self, catalog_type: Optional[CatalogType] = None) -> None: for item_link in self.get_item_links(): if item_link.is_resolved(): - cast(ps.Item, item_link.target).save_object( + cast(pystac.Item, item_link.target).save_object( include_self_link=items_include_self_link ) @@ -763,7 +763,7 @@ def validate_all(self) -> None: def _object_links(self) -> List[str]: return ["child", "item"] + ( - ps.EXTENSION_HOOKS.get_extended_object_links(self) or [] + pystac.EXTENSION_HOOKS.get_extended_object_links(self) or [] ) def map_items( @@ -792,10 +792,10 @@ def process_catalog(catalog: Catalog) -> None: item_links: List[Link] = [] for item_link in catalog.get_item_links(): item_link.resolve_stac_object(root=self.get_root()) - mapped = item_mapper(cast(ps.Item, item_link.target)) + mapped = item_mapper(cast(pystac.Item, item_link.target)) if mapped is None: raise Exception("item_mapper cannot return None.") - if isinstance(mapped, ps.Item): + if isinstance(mapped, pystac.Item): item_link.target = mapped item_links.append(item_link) else: @@ -832,12 +832,12 @@ def map_assets( def apply_asset_mapper( tup: Tuple[str, "Asset_Type"] - ) -> List[Tuple[str, ps.Asset]]: + ) -> List[Tuple[str, pystac.Asset]]: k, v = tup result = asset_mapper(k, v) if result is None: raise Exception("asset_mapper cannot return None.") - if isinstance(result, ps.Asset): + if isinstance(result, pystac.Asset): return [(k, result)] elif isinstance(result, tuple): return [result] @@ -847,7 +847,7 @@ def apply_asset_mapper( raise Exception("asset_mapper must return a non-empty list") return assets - def item_mapper(item: ps.Item) -> ps.Item: + def item_mapper(item: pystac.Item) -> pystac.Item: new_assets = [ x for result in map(apply_asset_mapper, item.assets.items()) @@ -887,9 +887,9 @@ def from_dict( migrate: bool = False, ) -> "Catalog": if migrate: - result = ps.read_dict(d, href=href, root=root) + result = pystac.read_dict(d, href=href, root=root) if not isinstance(result, Catalog): - raise ps.STACError(f"{result} is not a Catalog") + raise pystac.STACError(f"{result} is not a Catalog") return result catalog_type = CatalogType.determine_type(d) @@ -933,5 +933,5 @@ def full_copy( def from_file(cls, href: str) -> "Catalog": result = super().from_file(href) if not isinstance(result, Catalog): - raise ps.STACTypeError(f"{result} is not a {Catalog}.") + raise pystac.STACTypeError(f"{result} is not a {Catalog}.") return result diff --git a/pystac/collection.py b/pystac/collection.py index d34af4b0c..89530eb02 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -18,7 +18,7 @@ import dateutil.parser from dateutil import tz -import pystac as ps +import pystac from pystac import STACObjectType, CatalogType from pystac.asset import Asset from pystac.catalog import Catalog @@ -663,9 +663,9 @@ def from_dict( migrate: bool = False, ) -> "Collection": if migrate: - result = ps.read_dict(d, href=href, root=root) + result = pystac.read_dict(d, href=href, root=root) if not isinstance(result, Collection): - raise ps.STACError(f"{result} is not a Catalog") + raise pystac.STACError(f"{result} is not a Catalog") return result catalog_type = CatalogType.determine_type(d) @@ -752,5 +752,5 @@ def full_copy( def from_file(cls, href: str) -> "Collection": result = super().from_file(href) if not isinstance(result, Collection): - raise ps.STACTypeError(f"{result} is not a {Collection}.") + raise pystac.STACTypeError(f"{result} is not a {Collection}.") return result diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 476c74732..d2bc11e5c 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Generic, Iterable, List, Optional, Dict, Any, Type, TypeVar, Union -import pystac as ps +import pystac class ExtensionException(Exception): @@ -9,13 +9,13 @@ class ExtensionException(Exception): class SummariesExtension: - def __init__(self, collection: ps.Collection) -> None: + def __init__(self, collection: pystac.Collection) -> None: self.summaries = collection.summaries def _set_summary( self, prop_key: str, - v: Optional[Union[List[Any], ps.RangeSummary[Any], Dict[str, Any]]], + v: Optional[Union[List[Any], pystac.RangeSummary[Any], Dict[str, Any]]], ) -> None: if v is None: self.summaries.remove(prop_key) @@ -50,7 +50,7 @@ def _set_property( self.properties[prop_name] = v -S = TypeVar("S", bound=ps.STACObject) +S = TypeVar("S", bound=pystac.STACObject) class ExtensionManagementMixin(Generic[S], ABC): diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index 2eacc8f58..f49e7468d 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -1,7 +1,7 @@ from abc import ABC from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -10,7 +10,7 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required, map_opt -T = TypeVar("T", ps.Collection, ps.Item, ps.Asset) +T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/datacube/v1.0.0/schema.json" @@ -59,11 +59,11 @@ def to_dict(self) -> Dict[str, Any]: def from_dict(d: Dict[str, Any]) -> "Dimension": dim_type = d.get(DIM_TYPE_PROP) if dim_type is None: - raise ps.RequiredPropertyMissing("cube_dimension", DIM_TYPE_PROP) + raise pystac.RequiredPropertyMissing("cube_dimension", DIM_TYPE_PROP) if dim_type == "spatial": axis = d.get(DIM_AXIS_PROP) if axis is None: - raise ps.RequiredPropertyMissing("cube_dimension", DIM_AXIS_PROP) + raise pystac.RequiredPropertyMissing("cube_dimension", DIM_AXIS_PROP) if axis == "z": return VerticalSpatialDimension(d) else: @@ -307,7 +307,7 @@ def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> No class DatacubeExtension( Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]], + ExtensionManagementMixin[Union[pystac.Collection, pystac.Item]], ): def apply(self, dimensions: Dict[str, Dimension]) -> None: self.dimensions = dimensions @@ -333,11 +333,11 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "DatacubeExtension[T]": - if isinstance(obj, ps.Collection): + if isinstance(obj, pystac.Collection): return cast(DatacubeExtension[T], CollectionDatacubeExtension(obj)) - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(DatacubeExtension[T], ItemDatacubeExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) else: raise ExtensionException( @@ -345,8 +345,8 @@ def ext(obj: T) -> "DatacubeExtension[T]": ) -class CollectionDatacubeExtension(DatacubeExtension[ps.Collection]): - def __init__(self, collection: ps.Collection): +class CollectionDatacubeExtension(DatacubeExtension[pystac.Collection]): + def __init__(self, collection: pystac.Collection): self.collection = collection self.properties = collection.extra_fields @@ -354,8 +354,8 @@ def __repr__(self) -> str: return "".format(self.collection.id) -class ItemDatacubeExtension(DatacubeExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemDatacubeExtension(DatacubeExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -363,11 +363,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetDatacubeExtension(DatacubeExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetDatacubeExtension(DatacubeExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -377,8 +377,8 @@ def __repr__(self) -> str: class DatacubeExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["datacube"]) - stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + stac_object_types: Set[pystac.STACObjectType] = set( + [pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM] ) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 15a6c7587..08f6f4c79 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -2,7 +2,7 @@ import re from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -14,7 +14,7 @@ from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.utils import get_required, map_opt -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/eo/v1.0.0/schema.json" @@ -231,7 +231,9 @@ def band_description(common_name: str) -> Optional[str]: return None -class EOExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class EOExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] +): """EOItemExt is the extension of the Item in the eo extension which represents a snapshot of the earth for a single date and time. @@ -298,20 +300,20 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "EOExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(EOExtension[T], ItemEOExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(EOExtension[T], AssetEOExtension(obj)) else: raise ExtensionException(f"EO extension does not apply to type {type(obj)}") @staticmethod - def summaries(obj: ps.Collection) -> "SummariesEOExtension": + def summaries(obj: pystac.Collection) -> "SummariesEOExtension": return SummariesEOExtension(obj) -class ItemEOExtension(EOExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemEOExtension(EOExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -340,11 +342,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetEOExtension(EOExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetEOExtension(EOExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -379,7 +381,7 @@ def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: class EOExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["eo"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription @@ -460,7 +462,7 @@ def migrate( ] del obj["properties"]["eo:{}".format(field)] - if version < "1.0.0-beta.1" and info.object_type == ps.STACObjectType.ITEM: + if version < "1.0.0-beta.1" and info.object_type == pystac.STACObjectType.ITEM: # gsd moved from eo to common metadata if "eo:gsd" in obj["properties"]: obj["properties"]["gsd"] = obj["properties"]["eo:gsd"] diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 73123a9de..bdc0e9493 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -6,7 +6,7 @@ ) from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -16,7 +16,7 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/file/v1.0.0/schema.json" @@ -48,7 +48,9 @@ def __str__(self) -> str: OTHER = "other" -class FileExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class FileExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] +): """FileItemExt is the extension of the Item in the file extension which adds file related details such as checksum, data type and size for assets. @@ -140,9 +142,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "FileExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(FileExtension[T], ItemFileExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(FileExtension[T], AssetFileExtension(obj)) else: raise ExtensionException( @@ -150,12 +152,12 @@ def ext(obj: T) -> "FileExtension[T]": ) @staticmethod - def summaries(obj: ps.Collection) -> "SummariesFileExtension": + def summaries(obj: pystac.Collection) -> "SummariesFileExtension": return SummariesFileExtension(obj) -class ItemFileExtension(FileExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemFileExtension(FileExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -163,11 +165,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetFileExtension(FileExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetFileExtension(FileExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -192,7 +194,7 @@ def data_type(self, v: Optional[List[FileDataType]]) -> None: self._set_summary(DATA_TYPE_PROP, map_opt(lambda x: [str(t) for t in x], v)) @property - def size(self) -> Optional[ps.RangeSummary[int]]: + def size(self) -> Optional[pystac.RangeSummary[int]]: """Get or sets the size in bytes of the file Returns: @@ -201,7 +203,7 @@ def size(self) -> Optional[ps.RangeSummary[int]]: return self.summaries.get_range(SIZE_PROP, int) @size.setter - def size(self, v: Optional[ps.RangeSummary[int]]) -> None: + def size(self, v: Optional[pystac.RangeSummary[int]]) -> None: self._set_summary(SIZE_PROP, v) @property @@ -217,7 +219,7 @@ def nodata(self, v: Optional[List[Any]]) -> None: class FileExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["file"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index 9fffa34e2..8608268b3 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -2,7 +2,7 @@ from functools import lru_cache from typing import Any, Dict, Iterable, List, Optional, Set, TYPE_CHECKING -import pystac as ps +import pystac from pystac.extensions import ExtensionError from pystac.serialization.identify import STACJSONDescription, STACVersionID @@ -29,7 +29,7 @@ def prev_extension_ids(self) -> List[str]: @property @abstractmethod - def stac_object_types(self) -> Set[ps.STACObjectType]: + def stac_object_types(self) -> Set[pystac.STACObjectType]: """A set of STACObjectType for which migration logic will be applied.""" pass diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index 9e0fdc955..be5672a68 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Set -import pystac as ps +import pystac from pystac.extensions.base import ExtensionManagementMixin from pystac.extensions.hooks import ExtensionHooks from pystac.serialization.identify import STACJSONDescription, STACVersionID @@ -64,8 +64,8 @@ def roles(self, v: Optional[List[str]]) -> None: else: self.properties[ASSET_ROLES_PROP] = v - def create_asset(self, href: str) -> ps.Asset: - return ps.Asset( + def create_asset(self, href: str) -> pystac.Asset: + return pystac.Asset( href=href, title=self.title, description=self.description, @@ -87,8 +87,8 @@ def create_asset(self, href: str) -> ps.Asset: ) -class ItemAssetsExtension(ExtensionManagementMixin[ps.Collection]): - def __init__(self, collection: ps.Collection) -> None: +class ItemAssetsExtension(ExtensionManagementMixin[pystac.Collection]): + def __init__(self, collection: pystac.Collection) -> None: self.collection = collection @property @@ -112,14 +112,16 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def ext(cls, collection: ps.Collection) -> "ItemAssetsExtension": + def ext(cls, collection: pystac.Collection) -> "ItemAssetsExtension": return cls(collection) class ItemAssetsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["asset", "item-assets"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.COLLECTION]) + stac_object_types: Set[pystac.STACObjectType] = set( + [pystac.STACObjectType.COLLECTION] + ) def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index fc1d4c9c1..79fae6f27 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -4,7 +4,7 @@ from pystac.extensions.base import ExtensionManagementMixin from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast -import pystac as ps +import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions.hooks import ExtensionHooks @@ -82,7 +82,7 @@ def classes(self) -> Union[List[str], List[int], List[float]]: """ result = self.properties.get("classes") if result is None: - raise ps.STACError( + raise pystac.STACError( f"LabelClasses does not contain classes property: {self.properties}" ) return result @@ -90,7 +90,9 @@ def classes(self) -> Union[List[str], List[int], List[float]]: @classes.setter def classes(self, v: Union[List[str], List[int], List[float]]) -> None: if not type(v) is list: - raise ps.STACError("classes must be a list! Invalid input: {}".format(v)) + raise pystac.STACError( + "classes must be a list! Invalid input: {}".format(v) + ) self.properties["classes"] = v @@ -162,7 +164,9 @@ def name(self) -> str: """ result = self.properties.get("name") if result is None: - raise ps.STACError(f"Label count has no name property: {self.properties}") + raise pystac.STACError( + f"Label count has no name property: {self.properties}" + ) return result @name.setter @@ -178,7 +182,9 @@ def count(self) -> int: """ result = self.properties.get("count") if result is None: - raise ps.STACError(f"Label count has no count property: {self.properties}") + raise pystac.STACError( + f"Label count has no count property: {self.properties}" + ) return result @count.setter @@ -234,7 +240,7 @@ def name(self) -> str: """ result = self.properties.get("name") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Label statistics has no name property: {self.properties}" ) return result @@ -252,7 +258,7 @@ def value(self) -> float: """ result = self.properties.get("value") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Label statistics has no value property: {self.properties}" ) return result @@ -360,7 +366,9 @@ def counts(self, v: Optional[List[LabelCount]]) -> None: self.properties.pop("counts", None) else: if not isinstance(v, list): - raise ps.STACError("counts must be a list! Invalid input: {}".format(v)) + raise pystac.STACError( + "counts must be a list! Invalid input: {}".format(v) + ) self.properties["counts"] = [c.to_dict() for c in v] @@ -384,7 +392,7 @@ def statistics(self, v: Optional[List[LabelStatistics]]) -> None: self.properties.pop("statistics", None) else: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "statistics must be a list! Invalid input: {}".format(v) ) @@ -433,7 +441,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties -class LabelExtension(ExtensionManagementMixin[ps.Item]): +class LabelExtension(ExtensionManagementMixin[pystac.Item]): """A LabelItemExt is the extension of the Item in the label extension which represents a polygon, set of polygons, or raster data defining labels and label metadata and should be part of a Collection. @@ -452,7 +460,7 @@ class LabelExtension(ExtensionManagementMixin[ps.Item]): the item's stac_extensions. """ # noqa E501 - def __init__(self, item: ps.Item) -> None: + def __init__(self, item: pystac.Item) -> None: self.obj = item self.schema_uri = SCHEMA_URI @@ -506,7 +514,7 @@ def label_description(self) -> str: """ result = self.obj.properties.get("label:description") if result is None: - raise ps.STACError(f"label:description not set for item {self.obj.id}") + raise pystac.STACError(f"label:description not set for item {self.obj.id}") return result @label_description.setter @@ -518,13 +526,13 @@ def label_type(self) -> LabelType: """Gets or sets an ENUM of either vector label type or raster label type.""" result = self.obj.properties.get("label:type") if result is None: - raise ps.STACError(f"label:type is not set for item {self.obj.id}") + raise pystac.STACError(f"label:type is not set for item {self.obj.id}") return LabelType(result) @label_type.setter def label_type(self, v: LabelType) -> None: if v not in LabelType.ALL: - raise ps.STACError( + raise pystac.STACError( "label_type must be one of " "{}. Invalid input: {}".format(LabelType.ALL, v) ) @@ -549,7 +557,7 @@ def label_properties(self) -> Optional[List[str]]: def label_properties(self, v: Optional[List[str]]) -> None: if v is not None: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "label_properties must be a list! Invalid input: {}".format(v) ) @@ -577,7 +585,7 @@ def label_classes(self, v: Optional[List[LabelClasses]]) -> None: self.obj.properties.pop("label:classes", None) else: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "label_classes must be a list! Invalid input: {}".format(v) ) @@ -601,7 +609,7 @@ def label_tasks(self, v: Optional[List[str]]) -> None: self.obj.properties.pop("label:tasks", None) else: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "label_tasks must be a list! Invalid input: {}".format(v) ) @@ -624,7 +632,7 @@ def label_methods(self, v: Optional[List[str]]) -> None: self.obj.properties.pop("label:methods", None) else: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "label_methods must be a list! Invalid input: {}".format(v) ) @@ -651,7 +659,7 @@ def label_overviews(self, v: Optional[List[LabelOverview]]) -> None: self.obj.properties.pop("label:overviews", None) else: if not isinstance(v, list): - raise ps.STACError( + raise pystac.STACError( "label_overviews must be a list! Invalid input: {}".format(v) ) @@ -663,7 +671,7 @@ def __repr__(self) -> str: def add_source( self, - source_item: ps.Item, + source_item: pystac.Item, title: Optional[str] = None, assets: Optional[List[str]] = None, ) -> None: @@ -678,7 +686,7 @@ def add_source( properties = None if assets is not None: properties = {"label:assets": assets} - link = ps.Link( + link = pystac.Link( "source", source_item, title=title, @@ -687,7 +695,7 @@ def add_source( ) self.obj.add_link(link) - def get_sources(self) -> Iterable[ps.Item]: + def get_sources(self) -> Iterable[pystac.Item]: """Gets any source items that describe the source imagery used to generate this LabelItem. @@ -695,7 +703,7 @@ def get_sources(self) -> Iterable[ps.Item]: Generator[Items]: A possibly empty list of source imagery items. Determined by links of this LabelItem that have ``rel=='source'``. """ - return map(lambda x: cast(ps.Item, x), self.obj.get_stac_objects("source")) + return map(lambda x: cast(pystac.Item, x), self.obj.get_stac_objects("source")) def add_labels( self, @@ -720,7 +728,7 @@ def add_labels( self.obj.add_asset( "labels", - ps.Asset( + pystac.Asset( href=href, title=title, media_type=media_type, properties=properties ), ) @@ -742,7 +750,10 @@ def add_geojson_labels( asset object JSON. """ self.add_labels( - href, title=title, properties=properties, media_type=ps.MediaType.GEOJSON + href, + title=title, + properties=properties, + media_type=pystac.MediaType.GEOJSON, ) @classmethod @@ -750,24 +761,24 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def ext(cls, obj: ps.Item) -> "LabelExtension": + def ext(cls, obj: pystac.Item) -> "LabelExtension": return cls(obj) class LabelExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["label"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) - def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: - if isinstance(so, ps.Item): + def get_object_links(self, so: pystac.STACObject) -> Optional[List[str]]: + if isinstance(so, pystac.Item): return ["source"] return None def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: - if info.object_type == ps.STACObjectType.ITEM and version < "1.0.0": + if info.object_type == pystac.STACObjectType.ITEM and version < "1.0.0": props = obj["properties"] # Migrate 0.8.0-rc1 non-pluralized forms # As it's a common mistake, convert for any pre-1.0.0 version. diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 3908dd189..1af5f82e4 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -1,7 +1,7 @@ from pystac.extensions.hooks import ExtensionHooks from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -9,7 +9,7 @@ ) from pystac.utils import map_opt -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" @@ -70,7 +70,7 @@ def size(self) -> int: """ result = self.properties.get("size") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Pointcloud schema does not have size property: {self.properties}" ) return result @@ -78,7 +78,7 @@ def size(self) -> int: @size.setter def size(self, v: int) -> None: if not isinstance(v, int): - raise ps.STACError("size must be an int! Invalid input: {}".format(v)) + raise pystac.STACError("size must be an int! Invalid input: {}".format(v)) self.properties["size"] = v @@ -91,7 +91,7 @@ def name(self) -> str: """ result = self.properties.get("name") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Pointcloud schema does not have name property: {self.properties}" ) return result @@ -109,7 +109,7 @@ def type(self) -> str: """ result = self.properties.get("type") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Pointcloud schema has no type property: {self.properties}" ) return result @@ -159,10 +159,10 @@ def apply( Args: name (str): REQUIRED. The name of the channel. position (int): Position of the channel in the schema. - average (float) The average of the channel. + average (float): The average of the channel. count (int): The number of elements in the channel. - maximum (float): The maximum value of the channel. - minimum (float): The minimum value of the channel. + maximum (float): The maximum value of the channel. + minimum (float): The minimum value of the channel. stddev (float): The standard deviation of the channel. variance (float): The variance of the channel. """ @@ -192,10 +192,10 @@ def create( Args: name (str): REQUIRED. The name of the channel. position (int): Position of the channel in the schema. - average (float) The average of the channel. + average (float) The average of the channel. count (int): The number of elements in the channel. - maximum (float): The maximum value of the channel. - minimum (float): The minimum value of the channel. + maximum (float): The maximum value of the channel. + minimum (float): The minimum value of the channel. stddev (float): The standard deviation of the channel. variance (float): The variance of the channel. @@ -224,7 +224,7 @@ def name(self) -> str: """ result = self.properties.get("name") if result is None: - raise ps.STACError( + raise pystac.STACError( f"Pointcloud statistics does not have name property: {self.properties}" ) return result @@ -362,7 +362,7 @@ def to_dict(self) -> Dict[str, Any]: class PointcloudExtension( - Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] ): """PointcloudItemExt is the extension of an Item in the PointCloud Extension. The Pointclout extension adds pointcloud information to STAC Items. @@ -418,7 +418,7 @@ def count(self) -> int: """ result = self._get_property(COUNT_PROP, int) if result is None: - raise ps.RequiredPropertyMissing(self, COUNT_PROP) + raise pystac.RequiredPropertyMissing(self, COUNT_PROP) return result @count.setter @@ -434,7 +434,7 @@ def type(self) -> str: """ result = self._get_property(TYPE_PROP, str) if result is None: - raise ps.RequiredPropertyMissing(self, TYPE_PROP) + raise pystac.RequiredPropertyMissing(self, TYPE_PROP) return result @type.setter @@ -453,7 +453,7 @@ def encoding(self) -> str: """ result = self._get_property(ENCODING_PROP, str) if result is None: - raise ps.RequiredPropertyMissing(self, ENCODING_PROP) + raise pystac.RequiredPropertyMissing(self, ENCODING_PROP) return result @encoding.setter @@ -473,7 +473,7 @@ def schemas(self) -> List[PointcloudSchema]: """ result = self._get_property(SCHEMAS_PROP, List[Dict[str, Any]]) if result is None: - raise ps.RequiredPropertyMissing(self, SCHEMAS_PROP) + raise pystac.RequiredPropertyMissing(self, SCHEMAS_PROP) return [PointcloudSchema(s) for s in result] @schemas.setter @@ -520,9 +520,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "PointcloudExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(PointcloudExtension[T], ItemPointcloudExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) else: raise ExtensionException( @@ -530,8 +530,8 @@ def ext(obj: T) -> "PointcloudExtension[T]": ) -class ItemPointcloudExtension(PointcloudExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemPointcloudExtension(PointcloudExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -539,11 +539,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetPointcloudExtension(PointcloudExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetPointcloudExtension(PointcloudExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] self.repr_id = f"href={asset.href} item.id={asset.owner.id}" else: @@ -556,7 +556,7 @@ def __repr__(self) -> str: class PointcloudExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["pointcloud"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) POINTCLOUD_EXTENSION_HOOKS = PointcloudExtensionHooks() diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 393cabdc1..f2a99a67b 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -6,9 +6,9 @@ ) from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast -import pystac as ps +import pystac -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/projection/v1.0.0/schema.json" @@ -23,7 +23,7 @@ class ProjectionExtension( - Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] ): """ProjectionItemExt is the extension of an Item in the Projection Extension. The Projection extension adds projection information to STAC Items. @@ -39,7 +39,7 @@ class ProjectionExtension( ID to the item's stac_extensions. """ - def __init__(self, item: ps.Item) -> None: + def __init__(self, item: pystac.Item) -> None: self.item = item def apply( @@ -254,9 +254,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "ProjectionExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) else: raise ExtensionException( @@ -264,8 +264,8 @@ def ext(obj: T) -> "ProjectionExtension[T]": ) -class ItemProjectionExtension(ProjectionExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemProjectionExtension(ProjectionExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -273,11 +273,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetProjectionExtension(ProjectionExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetProjectionExtension(ProjectionExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -287,7 +287,7 @@ def __repr__(self) -> str: class ProjectionExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["proj", "projection"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) PROJECTION_EXTENSION_HOOKS = ProjectionExtensionHooks() diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 32f47e770..3bb6aae98 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -6,14 +6,14 @@ import enum from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions.base import ExtensionException, ExtensionManagementMixin from pystac.extensions.projection import ProjectionExtension from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required, map_opt -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/sar/v1.0.0/schema.json" @@ -59,7 +59,7 @@ class ObservationDirection(enum.Enum): class SarExtension( - Generic[T], ProjectionExtension[T], ExtensionManagementMixin[ps.Item] + Generic[T], ProjectionExtension[T], ExtensionManagementMixin[pystac.Item] ): """SarItemExt extends Item to add sar properties to a STAC Item. @@ -201,7 +201,7 @@ def polarizations(self) -> List[Polarization]: @polarizations.setter def polarizations(self, values: List[Polarization]) -> None: if not isinstance(values, list): - raise ps.STACError(f'polarizations must be a list. Invalid "{values}"') + raise pystac.STACError(f'polarizations must be a list. Invalid "{values}"') self._set_property(POLARIZATIONS, [v.value for v in values], pop_if_none=False) @property @@ -307,9 +307,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "SarExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(SarExtension[T], ItemSarExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(SarExtension[T], AssetSarExtension(obj)) else: raise ExtensionException( @@ -317,8 +317,8 @@ def ext(obj: T) -> "SarExtension[T]": ) -class ItemSarExtension(SarExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemSarExtension(SarExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -326,11 +326,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetSarExtension(SarExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetSarExtension(SarExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -340,7 +340,7 @@ def __repr__(self) -> str: class SarExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI prev_extension_ids: Set[str] = set(["sar"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 7e8a49959..c105eb422 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -7,7 +7,7 @@ from pystac.extensions.hooks import ExtensionHooks from typing import Generic, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -15,7 +15,7 @@ ) from pystac.utils import map_opt -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/sat/v1.0.0/schema.json" @@ -29,7 +29,9 @@ class OrbitState(enum.Enum): GEOSTATIONARY = "geostationary" -class SatExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class SatExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] +): """SatItemExt extends Item to add sat properties to a STAC Item. Args: @@ -96,9 +98,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "SatExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(SatExtension[T], ItemSatExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(SatExtension[T], AssetSatExtension(obj)) else: raise ExtensionException( @@ -106,8 +108,8 @@ def ext(obj: T) -> "SatExtension[T]": ) -class ItemSatExtension(SatExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemSatExtension(SatExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -115,11 +117,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetSatExtension(SatExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetSatExtension(SatExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -129,7 +131,7 @@ def __repr__(self) -> str: class SatExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["sat"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) SAT_EXTENSION_HOOKS = SatExtensionHooks() diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 3127d702e..caa0d4bfc 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -11,7 +11,7 @@ from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast from urllib import parse -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -20,7 +20,7 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.utils import map_opt -T = TypeVar("T", ps.Collection, ps.Item) +T = TypeVar("T", pystac.Collection, pystac.Item) SCHEMA_URI = "https://stac-extensions.github.io/scientific/v1.0.0/schema.json" @@ -63,11 +63,11 @@ def to_dict(self) -> Dict[str, str]: def from_dict(d: Dict[str, str]) -> "Publication": return Publication(d["doi"], d["citation"]) - def get_link(self) -> ps.Link: - return ps.Link(CITE_AS, doi_to_url(self.doi)) + def get_link(self) -> pystac.Link: + return pystac.Link(CITE_AS, doi_to_url(self.doi)) -def remove_link(links: List[ps.Link], doi: str) -> None: +def remove_link(links: List[pystac.Link], doi: str) -> None: url = doi_to_url(doi) for i, a_link in enumerate(links): if a_link.rel != CITE_AS: @@ -80,11 +80,11 @@ def remove_link(links: List[ps.Link], doi: str) -> None: class ScientificExtension( Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]], + ExtensionManagementMixin[Union[pystac.Collection, pystac.Item]], ): """ScientificItemExt extends Item to add citations and DOIs to a STAC Item.""" - def __init__(self, obj: ps.STACObject) -> None: + def __init__(self, obj: pystac.STACObject) -> None: self.obj = obj def apply( @@ -124,7 +124,7 @@ def doi(self, v: Optional[str]) -> None: if v is not None: self.properties[DOI] = v url = doi_to_url(v) - self.obj.add_link(ps.Link(CITE_AS, url)) + self.obj.add_link(pystac.Link(CITE_AS, url)) @property def citation(self) -> Optional[str]: @@ -194,9 +194,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "ScientificExtension[T]": - if isinstance(obj, ps.Collection): + if isinstance(obj, pystac.Collection): return cast(ScientificExtension[T], CollectionScientificExtension(obj)) - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(ScientificExtension[T], ItemScientificExtension(obj)) else: raise ExtensionException( @@ -204,8 +204,8 @@ def ext(obj: T) -> "ScientificExtension[T]": ) -class CollectionScientificExtension(ScientificExtension[ps.Collection]): - def __init__(self, collection: ps.Collection): +class CollectionScientificExtension(ScientificExtension[pystac.Collection]): + def __init__(self, collection: pystac.Collection): self.collection = collection self.properties = collection.extra_fields self.links = collection.links @@ -215,8 +215,8 @@ def __repr__(self) -> str: return "".format(self.collection.id) -class ItemScientificExtension(ScientificExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemScientificExtension(ScientificExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties self.links = item.links @@ -229,8 +229,8 @@ def __repr__(self) -> str: class ScientificExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["scientific"]) - stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + stac_object_types: Set[pystac.STACObjectType] = set( + [pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM] ) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 8f27d363d..2fabebba1 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -2,7 +2,7 @@ from pystac.extensions.hooks import ExtensionHooks from typing import Generic, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -10,7 +10,7 @@ ) from pystac.utils import datetime_to_str, map_opt, str_to_datetime -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/timestamps/v1.0.0/schema.json" @@ -20,7 +20,7 @@ class TimestampsExtension( - Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item] + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] ): """TimestampsItemExt is the extension of an Item in that allows to specify additional timestamps for assets and metadata. @@ -114,9 +114,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "TimestampsExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: raise ExtensionException( @@ -124,8 +124,8 @@ def ext(obj: T) -> "TimestampsExtension[T]": ) -class ItemTimestampsExtension(TimestampsExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemTimestampsExtension(TimestampsExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -133,11 +133,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetTimestampsExtension(TimestampsExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetTimestampsExtension(TimestampsExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -147,7 +147,7 @@ def __repr__(self) -> str: class TimestampsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids: Set[str] = set(["timestamps"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) TIMESTAMPS_EXTENSION_HOOKS = TimestampsExtensionHooks() diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 995dd53ec..bbf4a1ef7 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -8,7 +8,7 @@ from pystac.utils import get_required, map_opt from typing import Generic, List, Optional, Set, TypeVar, Union, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, @@ -16,7 +16,7 @@ ) from pystac.extensions.hooks import ExtensionHooks -T = TypeVar("T", ps.Collection, ps.Item) +T = TypeVar("T", pystac.Collection, pystac.Item) SCHEMA_URI = "https://stac-extensions.github.io/version/v1.0.0/schema.json" @@ -37,7 +37,7 @@ class VersionExtension( Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Collection, ps.Item]], + ExtensionManagementMixin[Union[pystac.Collection, pystac.Item]], ): """VersionItemExt extends Item to add version and deprecated properties along with links to the latest, predecessor, and successor Items. @@ -53,9 +53,9 @@ class VersionExtension( extension ID to the item's stac_extensions. """ - obj: ps.STACObject + obj: pystac.STACObject - def __init__(self, obj: ps.STACObject) -> None: + def __init__(self, obj: pystac.STACObject) -> None: self.obj = obj def apply( @@ -120,7 +120,7 @@ def latest(self) -> Optional[T]: def latest(self, item: Optional[T]) -> None: self.obj.clear_links(LATEST) if item is not None: - self.obj.add_link(ps.Link(LATEST, item, MEDIA_TYPE)) + self.obj.add_link(pystac.Link(LATEST, item, MEDIA_TYPE)) @property def predecessor(self) -> Optional[T]: @@ -134,7 +134,7 @@ def predecessor(self) -> Optional[T]: def predecessor(self, item: Optional[T]) -> None: self.obj.clear_links(PREDECESSOR) if item is not None: - self.obj.add_link(ps.Link(PREDECESSOR, item, MEDIA_TYPE)) + self.obj.add_link(pystac.Link(PREDECESSOR, item, MEDIA_TYPE)) @property def successor(self) -> Optional[T]: @@ -147,7 +147,7 @@ def successor(self) -> Optional[T]: def successor(self, item: Optional[T]) -> None: self.obj.clear_links(SUCCESSOR) if item is not None: - self.obj.add_link(ps.Link(SUCCESSOR, item, MEDIA_TYPE)) + self.obj.add_link(pystac.Link(SUCCESSOR, item, MEDIA_TYPE)) @classmethod def get_schema_uri(cls) -> str: @@ -155,9 +155,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "VersionExtension[T]": - if isinstance(obj, ps.Collection): + if isinstance(obj, pystac.Collection): return cast(VersionExtension[T], CollectionVersionExtension(obj)) - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(VersionExtension[T], ItemVersionExtension(obj)) else: raise ExtensionException( @@ -165,8 +165,8 @@ def ext(obj: T) -> "VersionExtension[T]": ) -class CollectionVersionExtension(VersionExtension[ps.Collection]): - def __init__(self, collection: ps.Collection): +class CollectionVersionExtension(VersionExtension[pystac.Collection]): + def __init__(self, collection: pystac.Collection): self.collection = collection self.properties = collection.extra_fields self.links = collection.links @@ -176,8 +176,8 @@ def __repr__(self) -> str: return "".format(self.collection.id) -class ItemVersionExtension(VersionExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemVersionExtension(VersionExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties self.links = item.links @@ -190,12 +190,12 @@ def __repr__(self) -> str: class VersionExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI prev_extension_ids: Set[str] = set(["version"]) - stac_object_types: Set[ps.STACObjectType] = set( - [ps.STACObjectType.COLLECTION, ps.STACObjectType.ITEM] + stac_object_types: Set[pystac.STACObjectType] = set( + [pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM] ) - def get_object_links(self, so: ps.STACObject) -> Optional[List[str]]: - if isinstance(so, ps.Collection) or isinstance(so, ps.Item): + def get_object_links(self, so: pystac.STACObject) -> Optional[List[str]]: + if isinstance(so, pystac.Collection) or isinstance(so, pystac.Item): return [LATEST, PREDECESSOR, SUCCESSOR] return None diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 626450ee6..d10d2a580 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -1,14 +1,14 @@ from pystac.extensions.hooks import ExtensionHooks from typing import Generic, Optional, Set, TypeVar, cast -import pystac as ps +import pystac from pystac.extensions.base import ( ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) -T = TypeVar("T", ps.Item, ps.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset) SCHEMA_URI = "https://stac-extensions.github.io/view/v1.0.0/schema.json" @@ -19,7 +19,9 @@ SUN_ELEVATION_PROP = "view:sun_elevation" -class ViewExtension(Generic[T], PropertiesExtension, ExtensionManagementMixin[ps.Item]): +class ViewExtension( + Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item] +): """ViewItemExt is the extension of the Item in the View Geometry Extension. View Geometry adds metadata related to angles of sensors and other radiance angles @@ -153,9 +155,9 @@ def get_schema_uri(cls) -> str: @staticmethod def ext(obj: T) -> "ViewExtension[T]": - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(ViewExtension[T], ItemViewExtension(obj)) - elif isinstance(obj, ps.Asset): + elif isinstance(obj, pystac.Asset): return cast(ViewExtension[T], AssetViewExtension(obj)) else: raise ExtensionException( @@ -163,8 +165,8 @@ def ext(obj: T) -> "ViewExtension[T]": ) -class ItemViewExtension(ViewExtension[ps.Item]): - def __init__(self, item: ps.Item): +class ItemViewExtension(ViewExtension[pystac.Item]): + def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties @@ -172,11 +174,11 @@ def __repr__(self) -> str: return "".format(self.item.id) -class AssetViewExtension(ViewExtension[ps.Asset]): - def __init__(self, asset: ps.Asset): +class AssetViewExtension(ViewExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset): self.asset_href = asset.href self.properties = asset.properties - if asset.owner and isinstance(asset.owner, ps.Item): + if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: @@ -186,7 +188,7 @@ def __repr__(self) -> str: class ViewExtensionHooks(ExtensionHooks): schema_uri = SCHEMA_URI prev_extension_ids: Set[str] = set(["view"]) - stac_object_types: Set[ps.STACObjectType] = set([ps.STACObjectType.ITEM]) + stac_object_types: Set[pystac.STACObjectType] = set([pystac.STACObjectType.ITEM]) VIEW_EXTENSION_HOOKS = ViewExtensionHooks() diff --git a/pystac/item.py b/pystac/item.py index c8f1f7938..a7bb572b5 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -5,7 +5,7 @@ import dateutil.parser -import pystac as ps +import pystac from pystac import STACError, STACObjectType from pystac.asset import Asset from pystac.link import Link @@ -858,7 +858,7 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]: d: Dict[str, Any] = { "type": "Feature", - "stac_version": ps.get_stac_version(), + "stac_version": pystac.get_stac_version(), "id": self.id, "properties": self.properties, "geometry": self.geometry, @@ -899,7 +899,7 @@ def clone(self) -> "Item": return clone def _object_links(self) -> List[str]: - return ["collection"] + (ps.EXTENSION_HOOKS.get_extended_object_links(self)) + return ["collection"] + (pystac.EXTENSION_HOOKS.get_extended_object_links(self)) @classmethod def from_dict( @@ -910,9 +910,9 @@ def from_dict( migrate: bool = False, ) -> "Item": if migrate: - result = ps.read_dict(d, href=href, root=root) + result = pystac.read_dict(d, href=href, root=root) if not isinstance(result, Item): - raise ps.STACError(f"{result} is not a Catalog") + raise pystac.STACError(f"{result} is not a Catalog") return result d = deepcopy(d) @@ -976,5 +976,5 @@ def full_copy( def from_file(cls, href: str) -> "Item": result = super().from_file(href) if not isinstance(result, Item): - raise ps.STACTypeError(f"{result} is not a {Item}.") + raise pystac.STACTypeError(f"{result} is not a {Item}.") return result diff --git a/pystac/layout.py b/pystac/layout.py index 45d581328..da6dd48ea 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -4,7 +4,7 @@ from string import Formatter from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union -import pystac as ps +import pystac if TYPE_CHECKING: from pystac.stac_object import STACObject as STACObject_Type @@ -92,7 +92,7 @@ def _get_template_value( self, stac_object: "STACObject_Type", template_var: str ) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: - if isinstance(stac_object, ps.Item): + if isinstance(stac_object, pystac.Item): # Datetime dt = stac_object.datetime if dt is None: @@ -131,7 +131,7 @@ def _get_template_value( # Allow dot-notation properties for arbitrary object values. props = template_var.split(".") - prop_source: Optional[Union[ps.STACObject, Dict[str, Any]]] = None + prop_source: Optional[Union[pystac.STACObject, Dict[str, Any]]] = None error = TemplateError( "Cannot find property {} on {} for template {}".format( template_var, stac_object, self.template @@ -234,14 +234,14 @@ class HrefLayoutStrategy(ABC): def get_href( self, stac_object: "STACObject_Type", parent_dir: str, is_root: bool = False ) -> str: - if isinstance(stac_object, ps.Item): + if isinstance(stac_object, pystac.Item): return self.get_item_href(stac_object, parent_dir) - elif isinstance(stac_object, ps.Collection): + elif isinstance(stac_object, pystac.Collection): return self.get_collection_href(stac_object, parent_dir, is_root) - elif isinstance(stac_object, ps.Catalog): + elif isinstance(stac_object, pystac.Catalog): return self.get_catalog_href(stac_object, parent_dir, is_root) else: - raise ps.STACError("Unknown STAC object type {}".format(stac_object)) + raise pystac.STACError("Unknown STAC object type {}".format(stac_object)) @abstractmethod def get_catalog_href( diff --git a/pystac/link.py b/pystac/link.py index 6c8a104e7..3e7be46f4 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -1,7 +1,7 @@ from copy import copy from typing import Any, Dict, Optional, TYPE_CHECKING, Union, cast -import pystac as ps +import pystac from pystac.utils import make_absolute_href, make_relative_href, is_absolute_href if TYPE_CHECKING: @@ -105,7 +105,7 @@ def get_href(self) -> Optional[str]: """ # get the self href if self.is_resolved(): - href = cast(ps.STACObject, self.target).get_self_href() + href = cast(pystac.STACObject, self.target).get_self_href() else: href = cast(Optional[str], self.target) @@ -113,7 +113,7 @@ def get_href(self) -> Optional[str]: root = self.owner.get_root() rel_links = ( HIERARCHICAL_LINKS - + ps.EXTENSION_HOOKS.get_extended_object_links(self.owner) + + pystac.EXTENSION_HOOKS.get_extended_object_links(self.owner) ) # if a hierarchical link with an owner and root, and relative catalog if root and root.is_relative() and self.rel in rel_links: @@ -144,7 +144,7 @@ def get_absolute_href(self) -> Optional[str]: and has an unresolved target, this will return a relative HREF. """ if self.is_resolved(): - href = cast(ps.STACObject, self.target).get_self_href() + href = cast(pystac.STACObject, self.target).get_self_href() else: href = cast(Optional[str], self.target) @@ -171,14 +171,14 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": # If it's a relative link, base it off the parent. if not is_absolute_href(target_href): if self.owner is None: - raise ps.STACError( + raise pystac.STACError( "Relative path {} encountered " "without owner or start_href.".format(target_href) ) start_href = self.owner.get_self_href() if start_href is None: - raise ps.STACError( + raise pystac.STACError( "Relative path {} encountered " 'without owner "self" link set.'.format(target_href) ) @@ -186,7 +186,7 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": target_href = make_absolute_href(target_href, start_href) obj = None - stac_io: Optional[ps.StacIO] = None + stac_io: Optional[pystac.StacIO] = None if root is not None: obj = root._resolved_objects.get_by_href(target_href) @@ -196,10 +196,10 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": if stac_io is None: if self.owner is not None: - if isinstance(self.owner, ps.Catalog): + if isinstance(self.owner, pystac.Catalog): stac_io = self.owner._stac_io if stac_io is None: - stac_io = ps.StacIO.default() + stac_io = pystac.StacIO.default() obj = stac_io.read_stac_object(target_href, root=root) obj.set_self_href(target_href) @@ -214,7 +214,7 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link": if ( self.owner and self.rel in ["child", "item"] - and isinstance(self.owner, ps.Catalog) + and isinstance(self.owner, pystac.Catalog) ): self.target.set_parent(self.owner) diff --git a/pystac/serialization/__init__.py b/pystac/serialization/__init__.py index aba1e8276..ad1d6999a 100644 --- a/pystac/serialization/__init__.py +++ b/pystac/serialization/__init__.py @@ -1,7 +1,7 @@ # flake8: noqa from typing import Any, Dict, Optional, TYPE_CHECKING -import pystac as ps +import pystac from pystac.serialization.identify import ( STACVersionRange, # type:ignore identify_stac_object, @@ -30,7 +30,7 @@ def stac_object_from_dict( Note: This is used internally in StacIO instances to deserialize STAC Objects. """ - if identify_stac_object_type(d) == ps.STACObjectType.ITEM: + if identify_stac_object_type(d) == pystac.STACObjectType.ITEM: collection_cache = None if root is not None: collection_cache = root._resolved_objects.as_collection_cache() @@ -42,13 +42,13 @@ def stac_object_from_dict( d = migrate_to_latest(d, info) - if info.object_type == ps.STACObjectType.CATALOG: - return ps.Catalog.from_dict(d, href=href, root=root, migrate=False) + if info.object_type == pystac.STACObjectType.CATALOG: + return pystac.Catalog.from_dict(d, href=href, root=root, migrate=False) - if info.object_type == ps.STACObjectType.COLLECTION: - return ps.Collection.from_dict(d, href=href, root=root, migrate=False) + if info.object_type == pystac.STACObjectType.COLLECTION: + return pystac.Collection.from_dict(d, href=href, root=root, migrate=False) - if info.object_type == ps.STACObjectType.ITEM: - return ps.Item.from_dict(d, href=href, root=root, migrate=False) + if info.object_type == pystac.STACObjectType.ITEM: + return pystac.Item.from_dict(d, href=href, root=root, migrate=False) raise ValueError(f"Unknown STAC object type {info.object_type}") diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index b3c811d97..4da6eadee 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Iterable, List, Optional, Union, cast -import pystac as ps +import pystac from pystac.cache import CollectionCache from pystac.serialization.identify import STACVersionID from pystac.utils import make_absolute_href @@ -28,7 +28,7 @@ def merge_common_properties( """ properties_merged = False - collection: Optional[Union[ps.Collection, Dict[str, Any]]] = None + collection: Optional[Union[pystac.Collection, Dict[str, Any]]] = None collection_id: Optional[str] = None collection_href: Optional[str] = None @@ -79,12 +79,12 @@ def merge_common_properties( collection = collection_cache.get_by_href(collection_href) if collection is None: - collection = ps.StacIO.default().read_json(collection_href) + collection = pystac.StacIO.default().read_json(collection_href) if collection is not None: collection_id = None collection_props: Optional[Dict[str, Any]] = None - if isinstance(collection, ps.Collection): + if isinstance(collection, pystac.Collection): collection_id = collection.id collection_props = collection.extra_fields.get("properties") elif isinstance(collection, dict): diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index 7f5d05bb4..da20ad139 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -2,7 +2,7 @@ from functools import total_ordering from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING, Union, cast -import pystac as ps +import pystac from pystac.version import STACVersion if TYPE_CHECKING: @@ -178,7 +178,7 @@ def _identify_stac_extensions( # assets (collection assets) - if object_type == ps.STACObjectType.ITEMCOLLECTION: + if object_type == pystac.STACObjectType.ITEMCOLLECTION: if "assets" in d: stac_extensions.add("assets") version_range.set_min(STACVersionID("0.8.0")) @@ -207,19 +207,19 @@ def _identify_stac_extensions( version_range.set_min(STACVersionID("0.6.2")) # datacube - if object_type == ps.STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith("cube:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.DATACUBE.value) version_range.set_min(STACVersionID("0.6.1")) # datetime-range (old extension) - if object_type == ps.STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if "dtr:start_datetime" in d["properties"]: stac_extensions.add("datetime-range") version_range.set_min(STACVersionID("0.6.0")) # eo - if object_type == ps.STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith("eo:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.EO.value) if "eo:epsg" in d["properties"]: @@ -234,13 +234,13 @@ def _identify_stac_extensions( version_range.set_max(STACVersionID("0.5.2")) # pointcloud - if object_type == ps.STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith("pc:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.POINTCLOUD.value) version_range.set_min(STACVersionID("0.6.2")) # sar - if object_type == ps.STACObjectType.ITEM: + if object_type == pystac.STACObjectType.ITEM: if any(k.startswith("sar:") for k in cast(Dict[str, Any], d["properties"])): stac_extensions.add(OldExtensionShortIDs.SAR.value) version_range.set_min(STACVersionID("0.6.2")) @@ -278,8 +278,8 @@ def _identify_stac_extensions( # scientific if ( - object_type == ps.STACObjectType.ITEM - or object_type == ps.STACObjectType.COLLECTION + object_type == pystac.STACObjectType.ITEM + or object_type == pystac.STACObjectType.COLLECTION ): if "properties" in d: prop_keys = cast(Dict[str, Any], d["properties"]).keys() @@ -288,7 +288,7 @@ def _identify_stac_extensions( version_range.set_min(STACVersionID("0.6.0")) # Single File STAC - if object_type == ps.STACObjectType.ITEMCOLLECTION: + if object_type == pystac.STACObjectType.ITEMCOLLECTION: if "collections" in d: stac_extensions.add(OldExtensionShortIDs.SINGLE_FILE_STAC.value) version_range.set_min(STACVersionID("0.8.0")) @@ -310,7 +310,7 @@ def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectType_Type object_type = None if "type" in json_dict: # Try to identify using 'type' property - for t in ps.STACObjectType: + for t in pystac.STACObjectType: if json_dict["type"].lower() == t.value.lower(): object_type = t break @@ -322,14 +322,14 @@ def identify_stac_object_type(json_dict: Dict[str, Any]) -> "STACObjectType_Type "0" ): if json_dict["type"] == "FeatureCollection": - object_type = ps.STACObjectType.ITEMCOLLECTION + object_type = pystac.STACObjectType.ITEMCOLLECTION if "extent" in json_dict: - object_type = ps.STACObjectType.COLLECTION + object_type = pystac.STACObjectType.COLLECTION elif "assets" in json_dict: - object_type = ps.STACObjectType.ITEM + object_type = pystac.STACObjectType.ITEM else: - object_type = ps.STACObjectType.CATALOG + object_type = pystac.STACObjectType.CATALOG return object_type @@ -353,11 +353,11 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: if stac_version is None: if ( - object_type == ps.STACObjectType.CATALOG - or object_type == ps.STACObjectType.COLLECTION + object_type == pystac.STACObjectType.CATALOG + or object_type == pystac.STACObjectType.COLLECTION ): version_range.set_max(STACVersionID("0.5.2")) - elif object_type == ps.STACObjectType.ITEM: + elif object_type == pystac.STACObjectType.ITEM: version_range.set_max(STACVersionID("0.7.0")) else: # ItemCollection version_range.set_min(STACVersionID("0.8.0")) @@ -373,7 +373,7 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: # but ItemCollection (except after 0.9.0, when ItemCollection also got # the stac_extensions property). if version_range.is_earlier_than("0.8.0") or ( - object_type == ps.STACObjectType.ITEMCOLLECTION + object_type == pystac.STACObjectType.ITEMCOLLECTION and not version_range.is_later_than("0.8.1") ): stac_extensions = _identify_stac_extensions( diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 5f448268f..a007db170 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -1,7 +1,7 @@ from copy import deepcopy from typing import Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING, Tuple -import pystac as ps +import pystac from pystac.version import STACVersion from pystac.serialization.identify import ( OldExtensionShortIDs, @@ -58,7 +58,7 @@ def _migrate_item_assets( d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> Optional[Set[str]]: if version < "1.0.0-beta.2": - if info.object_type == ps.STACObjectType.COLLECTION: + if info.object_type == pystac.STACObjectType.COLLECTION: if "assets" in d: d["item_assets"] = d["assets"] del d["assets"] @@ -91,10 +91,10 @@ def _get_object_migrations() -> Dict[ str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None] ]: return { - ps.STACObjectType.CATALOG: _migrate_catalog, - ps.STACObjectType.COLLECTION: _migrate_collection, - ps.STACObjectType.ITEM: _migrate_item, - ps.STACObjectType.ITEMCOLLECTION: _migrate_itemcollection, + pystac.STACObjectType.CATALOG: _migrate_catalog, + pystac.STACObjectType.COLLECTION: _migrate_collection, + pystac.STACObjectType.ITEM: _migrate_item, + pystac.STACObjectType.ITEMCOLLECTION: _migrate_itemcollection, } @@ -129,15 +129,24 @@ def _get_removed_extension_migrations() -> Dict[ # property merging has went away these extensions are removed # from the collection. Note that a migrated Collection may still # have a "properties" in extra_fields with the extension fields. - OldExtensionShortIDs.EO.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.FILE.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.LABEL.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.POINTCLOUD.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.PROJECTION.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.SAR.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.SAT.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.TIMESTAMPS.value: ([ps.STACObjectType.COLLECTION], None), - OldExtensionShortIDs.VIEW.value: ([ps.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.EO.value: ([pystac.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.FILE.value: ([pystac.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.LABEL.value: ([pystac.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.POINTCLOUD.value: ( + [pystac.STACObjectType.COLLECTION], + None, + ), + OldExtensionShortIDs.PROJECTION.value: ( + [pystac.STACObjectType.COLLECTION], + None, + ), + OldExtensionShortIDs.SAR.value: ([pystac.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.SAT.value: ([pystac.STACObjectType.COLLECTION], None), + OldExtensionShortIDs.TIMESTAMPS.value: ( + [pystac.STACObjectType.COLLECTION], + None, + ), + OldExtensionShortIDs.VIEW.value: ([pystac.STACObjectType.COLLECTION], None), # tiled-assets was never a fully published extension; # remove reference to the pre-1.0 RC short ID OldExtensionShortIDs.TILED_ASSETS.value: (None, None), @@ -183,7 +192,7 @@ def migrate_to_latest( # Force stac_extensions property, as it makes # downstream migration less complex result["stac_extensions"] = [] - ps.EXTENSION_HOOKS.migrate(result, version, info) + pystac.EXTENSION_HOOKS.migrate(result, version, info) for ext in result["stac_extensions"][:]: if ext in removed_extension_migrations: diff --git a/pystac/stac_io.py b/pystac/stac_io.py index cd1b006f5..850f1e53b 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -17,7 +17,7 @@ from urllib.request import urlopen from urllib.error import HTTPError -import pystac as ps +import pystac import pystac.serialization # Use orjson if available @@ -93,7 +93,7 @@ def stac_object_from_dict( root: Optional["Catalog_Type"] = None, ) -> "STACObject_Type": result = pystac.serialization.stac_object_from_dict(d, href, root) - if isinstance(result, ps.Catalog): + if isinstance(result, pystac.Catalog): # Set the stac_io instance for usage by io operations # where this catalog is the root. result._stac_io = self diff --git a/pystac/stac_object.py b/pystac/stac_object.py index d177f89a1..b0a7088bc 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Dict, Iterable, List, Optional, cast, TYPE_CHECKING -import pystac as ps +import pystac from pystac import STACError from pystac.link import Link from pystac.utils import is_absolute_href, make_absolute_href @@ -175,7 +175,7 @@ def set_self_href(self, href: Optional[str]) -> None: """ root_link = self.get_root_link() if root_link is not None and root_link.is_resolved(): - cast(ps.Catalog, root_link.target)._resolved_objects.remove( + cast(pystac.Catalog, root_link.target)._resolved_objects.remove( cast(STACObject, self) ) @@ -184,7 +184,7 @@ def set_self_href(self, href: Optional[str]) -> None: self.add_link(Link.self_href(href)) if root_link is not None and root_link.is_resolved(): - cast(ps.Catalog, root_link.target)._resolved_objects.cache( + cast(pystac.Catalog, root_link.target)._resolved_objects.cache( cast(STACObject, self) ) @@ -202,8 +202,8 @@ def get_root(self) -> Optional["Catalog_Type"]: if not root_link.is_resolved(): root_link.resolve_stac_object() # Use set_root, so Catalogs can merge ResolvedObjectCache instances. - self.set_root(cast(ps.Catalog, root_link.target)) - return cast(ps.Catalog, root_link.target) + self.set_root(cast(pystac.Catalog, root_link.target)) + return cast(pystac.Catalog, root_link.target) else: return None @@ -223,7 +223,7 @@ def set_root(self, root: Optional["Catalog_Type"]) -> None: if root_link_index is not None: root_link = self.links[root_link_index] if root_link.is_resolved(): - cast(ps.Catalog, root_link.target)._resolved_objects.remove(self) + cast(pystac.Catalog, root_link.target)._resolved_objects.remove(self) if root is None: self.remove_links("root") @@ -248,7 +248,7 @@ def get_parent(self) -> Optional["Catalog_Type"]: """ parent_link = self.get_single_link("parent") if parent_link: - return cast(ps.Catalog, parent_link.resolve_stac_object().target) + return cast(pystac.Catalog, parent_link.resolve_stac_object().target) else: return None @@ -288,7 +288,7 @@ def save_object( self, include_self_link: bool = True, dest_href: Optional[str] = None, - stac_io: Optional[ps.StacIO] = None, + stac_io: Optional[pystac.StacIO] = None, ) -> None: """Saves this STAC Object to it's 'self' HREF. @@ -319,7 +319,7 @@ def save_object( stac_io = root_stac_io if stac_io is None: - stac_io = ps.StacIO.default() + stac_io = pystac.StacIO.default() if dest_href is None: self_href = self.get_self_href() @@ -351,10 +351,10 @@ def full_copy( """ clone = self.clone() - if root is None and isinstance(clone, ps.Catalog): + if root is None and isinstance(clone, pystac.Catalog): root = clone - clone.set_root(cast(ps.Catalog, root)) + clone.set_root(cast(pystac.Catalog, root)) if parent: clone.set_parent(parent) @@ -369,7 +369,9 @@ def full_copy( target = cached_target else: target_parent = None - if link.rel in ["child", "item"] and isinstance(clone, ps.Catalog): + if link.rel in ["child", "item"] and isinstance( + clone, pystac.Catalog + ): target_parent = clone copied_target = target.full_copy(root=root, parent=target_parent) if root is not None: @@ -377,7 +379,7 @@ def full_copy( target = copied_target if link.rel in ["child", "item"]: target.set_root(root) - if isinstance(clone, ps.Catalog): + if isinstance(clone, pystac.Catalog): target.set_parent(clone) link.target = target @@ -432,7 +434,9 @@ def clone(self) -> "STACObject": pass @classmethod - def from_file(cls, href: str, stac_io: Optional[ps.StacIO] = None) -> "STACObject": + def from_file( + cls, href: str, stac_io: Optional[pystac.StacIO] = None + ) -> "STACObject": """Reads a STACObject implementation from a file. Args: @@ -445,7 +449,7 @@ def from_file(cls, href: str, stac_io: Optional[ps.StacIO] = None) -> "STACObjec by the JSON read from the file located at HREF. """ if stac_io is None: - stac_io = ps.StacIO.default() + stac_io = pystac.StacIO.default() if not is_absolute_href(href): href = make_absolute_href(href) @@ -462,7 +466,7 @@ def from_file(cls, href: str, stac_io: Optional[ps.StacIO] = None) -> "STACObjec if root_link is not None: if not root_link.is_resolved(): if root_link.get_absolute_href() == href: - o.set_root(cast(ps.Catalog, o)) + o.set_root(cast(pystac.Catalog, o)) return o @classmethod diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 808da0845..779865069 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -1,7 +1,7 @@ # flake8: noqa from typing import Dict, List, Any, Optional, cast, TYPE_CHECKING -import pystac as ps +import pystac from pystac.serialization.identify import STACVersionID, identify_stac_object from pystac.validation.schema_uri_map import OldExtensionSchemaUriMap from pystac.utils import make_absolute_href @@ -47,7 +47,7 @@ def validate(stac_object: "STACObject_Type") -> List[Any]: return validate_dict( stac_dict=stac_object.to_dict(), stac_object_type=stac_object.STAC_OBJECT_TYPE, - stac_version=ps.get_stac_version(), + stac_version=pystac.get_stac_version(), extensions=stac_object.stac_extensions, href=stac_object.get_self_href(), ) @@ -119,7 +119,7 @@ def _get_uri(ext: str) -> Optional[str]: def validate_all( - stac_dict: Dict[str, Any], href: str, stac_io: Optional[ps.StacIO] = None + stac_dict: Dict[str, Any], href: str, stac_io: Optional[pystac.StacIO] = None ) -> None: """Validate STAC JSON and all contained catalogs, collections and items. @@ -139,7 +139,7 @@ def validate_all( catalog, collection or item has a validation error. """ if stac_io is None: - stac_io = ps.StacIO.default() + stac_io = pystac.StacIO.default() info = identify_stac_object(stac_dict) @@ -152,7 +152,7 @@ def validate_all( href=href, ) - if info.object_type != ps.STACObjectType.ITEM: + if info.object_type != pystac.STACObjectType.ITEM: if "links" in stac_dict: # Account for 0.6 links if isinstance(stac_dict["links"], dict): diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 678fae0a5..a2d62841b 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -3,7 +3,7 @@ from pystac.serialization.identify import OldExtensionShortIDs, STACVersionID from typing import Any, Callable, Dict, List, Optional, Tuple -import pystac as ps +import pystac from pystac.serialization import STACVersionRange from pystac.stac_object import STACObjectType @@ -99,7 +99,7 @@ def get_object_schema_uri( self, object_type: STACObjectType, stac_version: str ) -> Optional[str]: uri = None - is_latest = stac_version == ps.get_stac_version() + is_latest = stac_version == pystac.get_stac_version() if object_type not in self.DEFAULT_SCHEMA_MAP: raise KeyError("Unknown STAC object type {}".format(object_type)) @@ -159,13 +159,13 @@ def get_schema_map(cls) -> Dict[str, Any]: return { OldExtensionShortIDs.CHECKSUM.value: ( { - ps.STACObjectType.CATALOG: ( + pystac.STACObjectType.CATALOG: ( "extensions/checksum/json-schema/schema.json" ), - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/checksum/json-schema/schema.json" ), - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/checksum/json-schema/schema.json" ), }, @@ -173,7 +173,7 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.COLLECTION_ASSETS.value: ( { - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/collection-assets/json-schema/schema.json" ) }, @@ -181,10 +181,10 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.DATACUBE.value: ( { - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/datacube/json-schema/schema.json" ), - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/datacube/json-schema/schema.json" ), }, @@ -192,30 +192,34 @@ def get_schema_map(cls) -> Dict[str, Any]: ( STACVersionRange(min_version="0.5.0", max_version="0.9.0"), { - ps.STACObjectType.COLLECTION: None, - ps.STACObjectType.ITEM: None, + pystac.STACObjectType.COLLECTION: None, + pystac.STACObjectType.ITEM: None, }, ) ], ), OldExtensionShortIDs.EO.value: ( - {ps.STACObjectType.ITEM: "extensions/eo/json-schema/schema.json"}, + {pystac.STACObjectType.ITEM: "extensions/eo/json-schema/schema.json"}, None, ), OldExtensionShortIDs.ITEM_ASSETS.value: ( { - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/item-assets/json-schema/schema.json" ) }, None, ), OldExtensionShortIDs.LABEL.value: ( - {ps.STACObjectType.ITEM: "extensions/label/json-schema/schema.json"}, + { + pystac.STACObjectType.ITEM: ( + "extensions/label/json-schema/schema.json" + ) + }, [ ( STACVersionRange(min_version="0.8.0-rc1", max_version="0.8.1"), - {ps.STACObjectType.ITEM: "extensions/label/schema.json"}, + {pystac.STACObjectType.ITEM: "extensions/label/schema.json"}, ) ], ), @@ -226,26 +230,26 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.PROJECTION.value: ( { - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/projection/json-schema/schema.json" ) }, None, ), OldExtensionShortIDs.SAR.value: ( - {ps.STACObjectType.ITEM: "extensions/sar/json-schema/schema.json"}, + {pystac.STACObjectType.ITEM: "extensions/sar/json-schema/schema.json"}, None, ), OldExtensionShortIDs.SAT.value: ( - {ps.STACObjectType.ITEM: "extensions/sat/json-schema/schema.json"}, + {pystac.STACObjectType.ITEM: "extensions/sat/json-schema/schema.json"}, None, ), OldExtensionShortIDs.SCIENTIFIC.value: ( { - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/scientific/json-schema/schema.json" ), - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/scientific/json-schema/schema.json" ), }, @@ -253,7 +257,7 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.SINGLE_FILE_STAC.value: ( { - ps.STACObjectType.CATALOG: ( + pystac.STACObjectType.CATALOG: ( "extensions/single-file-stac/json-schema/schema.json" ) }, @@ -261,13 +265,13 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.TILED_ASSETS.value: ( { - ps.STACObjectType.CATALOG: ( + pystac.STACObjectType.CATALOG: ( "extensions/tiled-assets/json-schema/schema.json" ), - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/tiled-assets/json-schema/schema.json" ), - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/tiled-assets/json-schema/schema.json" ), }, @@ -275,7 +279,7 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.TIMESTAMPS.value: ( { - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/timestamps/json-schema/schema.json" ) }, @@ -283,17 +287,17 @@ def get_schema_map(cls) -> Dict[str, Any]: ), OldExtensionShortIDs.VERSION.value: ( { - ps.STACObjectType.ITEM: ( + pystac.STACObjectType.ITEM: ( "extensions/version/json-schema/schema.json" ), - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/version/json-schema/schema.json" ), }, None, ), OldExtensionShortIDs.VIEW.value: ( - {ps.STACObjectType.ITEM: "extensions/view/json-schema/schema.json"}, + {pystac.STACObjectType.ITEM: "extensions/view/json-schema/schema.json"}, None, ), # Removed or renamed extensions. @@ -304,7 +308,7 @@ def get_schema_map(cls) -> Dict[str, Any]: ( STACVersionRange(min_version="0.8.0-rc1", max_version="0.9.0"), { - ps.STACObjectType.COLLECTION: ( + pystac.STACObjectType.COLLECTION: ( "extensions/asset/json-schema/schema.json" ) }, @@ -336,7 +340,7 @@ def get_extension_schema_uri( ) -> Optional[str]: uri = None - is_latest = stac_version == ps.get_stac_version() + is_latest = stac_version == pystac.get_stac_version() ext_map = cls.get_schema_map() if extension_id in ext_map: diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 4861e0cd4..1176b1c63 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -4,7 +4,7 @@ from pystac.stac_object import STACObjectType from typing import Any, Dict, List, Optional, Tuple -import pystac as ps +import pystac from pystac.validation import STACValidationError from pystac.validation.schema_uri_map import DefaultSchemaUriMap, SchemaUriMap @@ -145,7 +145,7 @@ def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: if schema_uri not in self.schema_cache: - s = json.loads(ps.StacIO.default().read_text(schema_uri)) + s = json.loads(pystac.StacIO.default().read_text(schema_uri)) self.schema_cache[schema_uri] = s schema = self.schema_cache[schema_uri] diff --git a/tests/data-files/change_stac_version.py b/tests/data-files/change_stac_version.py index af85a1891..bc8ab8b1f 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/data-files/change_stac_version.py @@ -7,9 +7,9 @@ import argparse import json -import pystac as ps +import pystac -TARGET_VERSION = ps.get_stac_version() +TARGET_VERSION = pystac.get_stac_version() def migrate(path: str) -> None: @@ -28,7 +28,7 @@ def migrate(path: str) -> None: path, cur_ver, TARGET_VERSION ) ) - obj = ps.read_dict(stac_json, href=path) + obj = pystac.read_dict(stac_json, href=path) migrated = obj.to_dict(include_self_link=False) with open(path, "w") as f: json.dump(migrated, f, indent=2) diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index a0c3ab015..9811d75de 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional from urllib.error import HTTPError -import pystac as ps +import pystac from pystac.serialization import identify_stac_object @@ -23,7 +23,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: if rel is not None and rel == "collection": href: str = link["href"] try: - json.loads(ps.StacIO.default().read_text(href)) + json.loads(pystac.StacIO.default().read_text(href)) filtered_links.append(link) except (HTTPError, FileNotFoundError, json.decoder.JSONDecodeError): print("===REMOVING UNREADABLE COLLECTION AT {}".format(href)) @@ -46,7 +46,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: args = parser.parse_args() stac_repo = "https://github.com/radiantearth/stac-spec" - stac_spec_tag = "v{}".format(ps.get_stac_version()) + stac_spec_tag = "v{}".format(pystac.get_stac_version()) examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "examples")) @@ -87,7 +87,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: and example_version > args.previous_version ): relpath = "{}/{}".format( - ps.get_stac_version(), + pystac.get_stac_version(), path.replace("{}/".format(tmp_dir), ""), ) target_path = os.path.join(examples_dir, relpath) @@ -98,7 +98,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: # Handle the case where there are collection links that # don't exist. - if info.object_type == ps.STACObjectType.ITEM: + if info.object_type == pystac.STACObjectType.ITEM: js = remove_bad_collection(js) d = os.path.dirname(target_path) diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index 767335dee..9e93baf34 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -4,7 +4,7 @@ from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast import unittest -import pystac as ps +import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions import ExtensionError from pystac.extensions.base import ( @@ -14,7 +14,7 @@ ) from pystac.extensions.hooks import ExtensionHooks -T = TypeVar("T", ps.Catalog, ps.Collection, ps.Item, ps.Asset) +T = TypeVar("T", pystac.Catalog, pystac.Collection, pystac.Item, pystac.Asset) SCHEMA_URI = "https://example.com/v2.0/custom-schema.json" @@ -25,9 +25,9 @@ class CustomExtension( Generic[T], PropertiesExtension, - ExtensionManagementMixin[Union[ps.Catalog, ps.Collection, ps.Item]], + ExtensionManagementMixin[Union[pystac.Catalog, pystac.Collection, pystac.Item]], ): - def __init__(self, obj: Optional[ps.STACObject]) -> None: + def __init__(self, obj: Optional[pystac.STACObject]) -> None: self.obj = obj def apply(self, test_prop: Optional[str]) -> None: @@ -41,9 +41,9 @@ def test_prop(self) -> Optional[str]: def test_prop(self, v: Optional[str]) -> None: self._set_property(TEST_PROP, v) - def add_link(self, target: ps.STACObject) -> None: + def add_link(self, target: pystac.STACObject) -> None: if self.obj is not None: - self.obj.add_link(ps.Link(TEST_LINK_REL, target)) + self.obj.add_link(pystac.Link(TEST_LINK_REL, target)) else: raise ExtensionError(f"{self} does not support links") @@ -53,51 +53,51 @@ def get_schema_uri(cls) -> str: @staticmethod def custom_ext(obj: T) -> "CustomExtension[T]": - if isinstance(obj, ps.Asset): + if isinstance(obj, pystac.Asset): return cast(CustomExtension[T], AssetCustomExtension(obj)) - if isinstance(obj, ps.Item): + if isinstance(obj, pystac.Item): return cast(CustomExtension[T], ItemCustomExtension(obj)) - if isinstance(obj, ps.Collection): + if isinstance(obj, pystac.Collection): return cast(CustomExtension[T], CollectionCustomExtension(obj)) - if isinstance(obj, ps.Catalog): + if isinstance(obj, pystac.Catalog): return cast(CustomExtension[T], CatalogCustomExtension(obj)) raise ExtensionError(f"Custom extension does not apply to {type(obj)}") @staticmethod - def summaries(obj: ps.Collection) -> "SummariesCustomExtension": + def summaries(obj: pystac.Collection) -> "SummariesCustomExtension": return SummariesCustomExtension(obj) -class CatalogCustomExtension(CustomExtension[ps.Catalog]): - def __init__(self, catalog: ps.Catalog) -> None: +class CatalogCustomExtension(CustomExtension[pystac.Catalog]): + def __init__(self, catalog: pystac.Catalog) -> None: self.catalog = catalog self.properties = catalog.extra_fields super().__init__(catalog) -class CollectionCustomExtension(CustomExtension[ps.Collection]): - def __init__(self, collection: ps.Collection) -> None: +class CollectionCustomExtension(CustomExtension[pystac.Collection]): + def __init__(self, collection: pystac.Collection) -> None: self.catalog = collection self.properties = collection.extra_fields super().__init__(collection) -class ItemCustomExtension(CustomExtension[ps.Item]): - def __init__(self, item: ps.Item) -> None: +class ItemCustomExtension(CustomExtension[pystac.Item]): + def __init__(self, item: pystac.Item) -> None: self.catalog = item self.properties = item.properties super().__init__(item) -class AssetCustomExtension(CustomExtension[ps.Asset]): - def __init__(self, asset: ps.Asset) -> None: +class AssetCustomExtension(CustomExtension[pystac.Asset]): + def __init__(self, asset: pystac.Asset) -> None: self.catalog = asset self.properties = asset.properties if asset.owner: - if isinstance(asset.owner, ps.Item): + if isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] - elif isinstance(asset.owner, ps.Collection): + elif isinstance(asset.owner, pystac.Collection): self.additional_read_properties = [asset.owner.extra_fields] super().__init__(None) @@ -117,21 +117,21 @@ class CustomExtensionHooks(ExtensionHooks): prev_extension_ids: Set[str] = set( ["custom", "https://example.com/v1.0/custom-schema.json"] ) - stac_object_types: Set[ps.STACObjectType] = set( + stac_object_types: Set[pystac.STACObjectType] = set( [ - ps.STACObjectType.CATALOG, - ps.STACObjectType.COLLECTION, - ps.STACObjectType.ITEM, + pystac.STACObjectType.CATALOG, + pystac.STACObjectType.COLLECTION, + pystac.STACObjectType.ITEM, ] ) - def get_object_links(self, obj: ps.STACObject) -> Optional[List[str]]: + def get_object_links(self, obj: pystac.STACObject) -> Optional[List[str]]: return [TEST_LINK_REL] def migrate( self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: - if version < "1.0.0-rc2" and info.object_type == ps.STACObjectType.ITEM: + if version < "1.0.0-rc2" and info.object_type == pystac.STACObjectType.ITEM: if "test:old-prop-name" in obj["properties"]: obj["properties"][TEST_PROP] = obj["properties"]["test:old-prop-name"] super().migrate(obj, version, info) @@ -139,10 +139,10 @@ def migrate( class CustomExtensionTest(unittest.TestCase): def setUp(self): - ps.EXTENSION_HOOKS.add_extension_hooks(CustomExtensionHooks()) + pystac.EXTENSION_HOOKS.add_extension_hooks(CustomExtensionHooks()) def tearDown(self) -> None: - ps.EXTENSION_HOOKS.remove_extension_hooks(SCHEMA_URI) + pystac.EXTENSION_HOOKS.remove_extension_hooks(SCHEMA_URI) # TODO: Test custom extensions and extension hooks diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index f0815690d..0fe73ab10 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -1,7 +1,7 @@ import json import unittest -import pystac as ps +import pystac from pystac import Item from pystac.collection import RangeSummary from pystac.utils import get_opt @@ -25,13 +25,13 @@ def test_to_from_dict(self): test_to_from_dict(self, Item, item_dict) def test_validate_eo(self): - item = ps.read_file(self.LANDSAT_EXAMPLE_URI) - item2 = ps.read_file(self.BANDS_IN_ITEM_URI) + item = pystac.read_file(self.LANDSAT_EXAMPLE_URI) + item2 = pystac.read_file(self.BANDS_IN_ITEM_URI) item.validate() item2.validate() def test_bands(self): - item = ps.Item.from_file(self.BANDS_IN_ITEM_URI) + item = pystac.Item.from_file(self.BANDS_IN_ITEM_URI) # Get self.assertIn("eo:bands", item.properties) @@ -57,7 +57,7 @@ def test_bands(self): item.validate() def test_asset_bands(self): - item = ps.Item.from_file(self.LANDSAT_EXAMPLE_URI) + item = pystac.Item.from_file(self.LANDSAT_EXAMPLE_URI) # Get @@ -92,14 +92,14 @@ def test_asset_bands(self): Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] - asset = ps.Asset(href="some/path.tif", media_type=ps.MediaType.GEOTIFF) + asset = pystac.Asset(href="some/path.tif", media_type=pystac.MediaType.GEOTIFF) EOExtension.ext(asset).bands = new_bands item.add_asset("test", asset) self.assertEqual(len(item.assets["test"].properties["eo:bands"]), 3) def test_cloud_cover(self): - item = ps.Item.from_file(self.LANDSAT_EXAMPLE_URI) + item = pystac.Item.from_file(self.LANDSAT_EXAMPLE_URI) # Get self.assertIn("eo:cloud_cover", item.properties) @@ -126,7 +126,7 @@ def test_cloud_cover(self): item.validate() def test_summaries(self): - col = ps.Collection.from_file(self.EO_COLLECTION_URI) + col = pystac.Collection.from_file(self.EO_COLLECTION_URI) eo_summaries = EOExtension.summaries(col) # Get @@ -149,7 +149,7 @@ def test_summaries(self): self.assertEqual(col_dict["summaries"]["eo:cloud_cover"]["minimum"], 1.0) def test_read_pre_09_fields_into_common_metadata(self): - eo_item = ps.Item.from_file( + eo_item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/0.8.1/item-spec/examples/" "landsat8-sample.json" ) @@ -159,7 +159,7 @@ def test_read_pre_09_fields_into_common_metadata(self): self.assertEqual(eo_item.common_metadata.instruments, ["oli_tirs"]) def test_reads_asset_bands_in_pre_1_0_version(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/0.9.0/item-spec/examples/" "landsat8-sample.json" ) @@ -171,7 +171,7 @@ def test_reads_asset_bands_in_pre_1_0_version(self): self.assertEqual(get_opt(bands)[0].common_name, "cirrus") def test_reads_gsd_in_pre_1_0_version(self): - eo_item = ps.Item.from_file( + eo_item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/0.9.0/item-spec/examples/" "landsat8-sample.json" ) diff --git a/tests/extensions/test_file.py b/tests/extensions/test_file.py index f4e937374..b0952ecf0 100644 --- a/tests/extensions/test_file.py +++ b/tests/extensions/test_file.py @@ -1,7 +1,7 @@ import json import unittest -import pystac as ps +import pystac from tests.utils import TestCases, test_to_from_dict from pystac.extensions.file import FileExtension, FileDataType @@ -15,14 +15,14 @@ def setUp(self): def test_to_from_dict(self): with open(self.FILE_EXAMPLE_URI) as f: item_dict = json.load(f) - test_to_from_dict(self, ps.Item, item_dict) + test_to_from_dict(self, pystac.Item, item_dict) def test_validate_file(self): - item = ps.Item.from_file(self.FILE_EXAMPLE_URI) + item = pystac.Item.from_file(self.FILE_EXAMPLE_URI) item.validate() def test_asset_size(self): - item = ps.Item.from_file(self.FILE_EXAMPLE_URI) + item = pystac.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get @@ -35,7 +35,7 @@ def test_asset_size(self): item.validate() def test_asset_checksum(self): - item = ps.Item.from_file(self.FILE_EXAMPLE_URI) + item = pystac.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get @@ -51,7 +51,7 @@ def test_asset_checksum(self): item.validate() def test_asset_data_type(self): - item = ps.Item.from_file(self.FILE_EXAMPLE_URI) + item = pystac.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get @@ -64,7 +64,7 @@ def test_asset_data_type(self): item.validate() def test_asset_nodata(self): - item = ps.Item.from_file(self.FILE_EXAMPLE_URI) + item = pystac.Item.from_file(self.FILE_EXAMPLE_URI) asset = item.assets["thumbnail"] # Get @@ -81,7 +81,7 @@ def test_migrates_old_checksum(self): "data-files/examples/1.0.0-beta.2/" "extensions/checksum/examples/sentinel1.json" ) - item = ps.Item.from_file(example_path) + item = pystac.Item.from_file(example_path) self.assertTrue(FileExtension.has_extension(item)) self.assertEqual( diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index c085f785f..d6c08920d 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -3,7 +3,7 @@ import unittest from tempfile import TemporaryDirectory -import pystac as ps +import pystac from pystac import Catalog, Item, CatalogType from pystac.extensions.label import ( LabelExtension, @@ -48,7 +48,7 @@ def test_from_file(self): label_example_2.validate() def test_from_file_pre_081(self): - d = ps.StacIO.default().read_json(self.label_example_1_uri) + d = pystac.StacIO.default().read_json(self.label_example_1_uri) d["stac_version"] = "0.8.0-rc1" d["properties"]["label:property"] = d["properties"]["label:properties"] @@ -59,7 +59,7 @@ def test_from_file_pre_081(self): d["properties"].pop("label:methods") d["properties"]["label:task"] = d["properties"]["label:tasks"] d["properties"].pop("label:tasks") - label_example_1 = ps.Item.from_dict(d, migrate=True) + label_example_1 = pystac.Item.from_dict(d, migrate=True) self.assertEqual(len(LabelExtension.ext(label_example_1).label_tasks or []), 2) @@ -78,7 +78,9 @@ def test_get_sources(self): def test_validate_label(self): with open(self.label_example_1_uri) as f: label_example_1_dict = json.load(f) - pystac.validation.validate_dict(label_example_1_dict, ps.STACObjectType.ITEM) + pystac.validation.validate_dict( + label_example_1_dict, pystac.STACObjectType.ITEM + ) with TemporaryDirectory() as tmp_dir: cat_dir = os.path.join(tmp_dir, "catalog") @@ -100,7 +102,7 @@ def test_read_label_item_owns_asset(self): self.assertEqual(item.assets[asset_key].owner, item) def test_label_description(self): - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:description", label_item.properties) @@ -115,7 +117,7 @@ def test_label_description(self): label_item.validate() def test_label_type(self): - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:type", label_item.properties) @@ -128,8 +130,8 @@ def test_label_type(self): label_item.validate() def test_label_properties(self): - label_item = ps.Item.from_file(self.label_example_1_uri) - label_item2 = ps.Item.from_file(self.label_example_2_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) + label_item2 = pystac.Item.from_file(self.label_example_2_uri) # Get self.assertIn("label:properties", label_item.properties) @@ -145,7 +147,7 @@ def test_label_properties(self): def test_label_classes(self): # Get - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) label_classes = LabelExtension.ext(label_item).label_classes self.assertEqual(len(get_opt(label_classes)), 2) @@ -170,7 +172,7 @@ def test_label_classes(self): label_item.validate() def test_label_tasks(self): - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:tasks", label_item.properties) @@ -183,7 +185,7 @@ def test_label_tasks(self): label_item.validate() def test_label_methods(self): - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) # Get self.assertIn("label:methods", label_item.properties) @@ -199,11 +201,11 @@ def test_label_methods(self): def test_label_overviews(self): # Get - label_item = ps.Item.from_file(self.label_example_1_uri) + label_item = pystac.Item.from_file(self.label_example_1_uri) label_ext = LabelExtension.ext(label_item) label_overviews = get_opt(label_ext.label_overviews) - label_item2 = ps.Item.from_file(self.label_example_2_uri) + label_item2 = pystac.Item.from_file(self.label_example_2_uri) label_ext2 = LabelExtension.ext(label_item2) label_overviews2 = get_opt(label_ext2.label_overviews) diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 944901020..3a1b0d80f 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -4,7 +4,7 @@ # from copy import deepcopy -import pystac as ps +import pystac from pystac.extensions.pointcloud import ( PointcloudExtension, PointcloudSchema, @@ -24,7 +24,7 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, ps.Item, d) + test_to_from_dict(self, pystac.Item, d) def test_apply(self): item = next(iter(TestCases.test_case_2().get_all_items())) @@ -41,11 +41,11 @@ def test_apply(self): self.assertTrue(PointcloudExtension.has_extension(item)) def test_validate_pointcloud(self): - item = ps.read_file(self.example_uri) + item = pystac.read_file(self.example_uri) item.validate() def test_count(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:count", pc_item.properties) @@ -62,12 +62,12 @@ def test_count(self): # Cannot test validation errors until the pointcloud schema.json syntax is fixed # Ensure setting bad count fails validation - with self.assertRaises(ps.STACValidationError): + with self.assertRaises(pystac.STACValidationError): PointcloudExtension.ext(pc_item).count = "not_an_int" # type:ignore pc_item.validate() def test_type(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:type", pc_item.properties) @@ -82,7 +82,7 @@ def test_type(self): pc_item.validate def test_encoding(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:encoding", pc_item.properties) @@ -97,7 +97,7 @@ def test_encoding(self): pc_item.validate def test_schemas(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:schemas", pc_item.properties) @@ -115,7 +115,7 @@ def test_schemas(self): pc_item.validate def test_statistics(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:statistics", pc_item.properties) @@ -148,7 +148,7 @@ def test_statistics(self): pc_item.validate def test_density(self): - pc_item = ps.Item.from_file(self.example_uri) + pc_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("pc:density", pc_item.properties) pc_density = PointcloudExtension.ext(pc_item).density @@ -202,5 +202,5 @@ def test_pointcloud_statistics(self): self.assertEqual(getattr(stat, k), val) def test_statistics_accessor_when_no_stats(self): - pc_item = ps.Item.from_file(self.example_uri_no_statistics) + pc_item = pystac.Item.from_file(self.example_uri_no_statistics) self.assertEqual(PointcloudExtension.ext(pc_item).statistics, None) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 66cbab86a..1580d0f6b 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -3,7 +3,7 @@ import unittest from copy import deepcopy -import pystac as ps +import pystac from pystac.validation import STACValidationError from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt @@ -81,7 +81,7 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, ps.Item, d) + test_to_from_dict(self, pystac.Item, d) def test_apply(self): item = next(iter(TestCases.test_case_2().get_all_items())) @@ -100,7 +100,7 @@ def test_apply(self): ) def test_partial_apply(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) ProjectionExtension.ext(proj_item).apply(epsg=1111) @@ -108,11 +108,11 @@ def test_partial_apply(self): proj_item.validate() def test_validate_proj(self): - item = ps.Item.from_file(self.example_uri) + item = pystac.Item.from_file(self.example_uri) item.validate() def test_epsg(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:epsg", proj_item.properties) @@ -144,7 +144,7 @@ def test_epsg(self): proj_item.validate def test_wkt2(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:wkt2", proj_item.properties) @@ -179,7 +179,7 @@ def test_wkt2(self): proj_item.validate() def test_projjson(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:projjson", proj_item.properties) @@ -222,7 +222,7 @@ def test_projjson(self): proj_item.validate() def test_geometry(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:geometry", proj_item.properties) @@ -263,7 +263,7 @@ def test_geometry(self): proj_item.validate() def test_bbox(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:bbox", proj_item.properties) @@ -296,7 +296,7 @@ def test_bbox(self): proj_item.validate() def test_centroid(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:centroid", proj_item.properties) @@ -337,7 +337,7 @@ def test_centroid(self): proj_item.validate() def test_shape(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:shape", proj_item.properties) @@ -371,7 +371,7 @@ def test_shape(self): proj_item.validate() def test_transform(self): - proj_item = ps.Item.from_file(self.example_uri) + proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:transform", proj_item.properties) diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index c1e14e60d..ae3920b6b 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -4,15 +4,17 @@ from typing import List import unittest -import pystac as ps +import pystac from pystac.extensions import sar from pystac.extensions.sar import SarExtension -def make_item() -> ps.Item: +def make_item() -> pystac.Item: asset_id = "my/items/2011" start = datetime.datetime(2020, 11, 7) - item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = pystac.Item( + id=asset_id, geometry=None, bbox=None, datetime=start, properties={} + ) SarExtension.add_to(item) return item @@ -131,7 +133,7 @@ def test_polarization_must_be_list(self): # Skip type hint as we are passing in an incorrect polarization. polarizations = sar.Polarization.HV product_type: str = "Some product" - with self.assertRaises(ps.STACError): + with self.assertRaises(pystac.STACError): SarExtension.ext(self.item).apply( mode, frequency_band, diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index ef3850535..19afaa75d 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -5,16 +5,18 @@ from pystac.validation import STACValidationError import unittest -import pystac as ps +import pystac from pystac.extensions import sat from pystac.extensions.sat import SatExtension -def make_item() -> ps.Item: +def make_item() -> pystac.Item: """Create basic test items that are only slightly different.""" asset_id = "an/asset" start = datetime.datetime(2018, 1, 2) - item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = pystac.Item( + id=asset_id, geometry=None, bbox=None, datetime=start, properties={} + ) SatExtension.add_to(item) return item @@ -91,7 +93,7 @@ def test_from_dict(self): "assets": {}, "stac_extensions": ["sat"], } - item = ps.Item.from_dict(d) + item = pystac.Item.from_dict(d) self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) self.assertEqual(relative_orbit, SatExtension.ext(item).relative_orbit) @@ -103,7 +105,7 @@ def test_to_from_dict(self): self.assertEqual(orbit_state.value, d["properties"][sat.ORBIT_STATE]) self.assertEqual(relative_orbit, d["properties"][sat.RELATIVE_ORBIT]) - item = ps.Item.from_dict(d) + item = pystac.Item.from_dict(d) self.assertEqual(orbit_state, SatExtension.ext(item).orbit_state) self.assertEqual(relative_orbit, SatExtension.ext(item).relative_orbit) diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index da0ef22c4..e0dc57cc9 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -3,7 +3,7 @@ import datetime import unittest -import pystac as ps +import pystac from pystac.extensions import scientific from pystac.extensions.scientific import ScientificExtension @@ -27,10 +27,12 @@ ] -def make_item() -> ps.Item: +def make_item() -> pystac.Item: asset_id = "USGS/GAP/CONUS/2011" start = datetime.datetime(2011, 1, 2) - item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = pystac.Item( + id=asset_id, geometry=None, bbox=None, datetime=start, properties={} + ) item.set_self_href(URL_TEMPLATE % 2011) ScientificExtension.add_to(item) @@ -175,15 +177,15 @@ def test_remove_all_publications_with_none(self): self.item.validate() -def make_collection() -> ps.Collection: +def make_collection() -> pystac.Collection: asset_id = "my/thing" start = datetime.datetime(2018, 8, 24) end = start + datetime.timedelta(5, 4, 3, 2, 1) bboxes = [[-180.0, -90.0, 180.0, 90.0]] - spatial_extent = ps.SpatialExtent(bboxes) - temporal_extent = ps.TemporalExtent([[start, end]]) - extent = ps.Extent(spatial_extent, temporal_extent) - collection = ps.Collection(asset_id, "desc", extent) + spatial_extent = pystac.SpatialExtent(bboxes) + temporal_extent = pystac.TemporalExtent([[start, end]]) + extent = pystac.Extent(spatial_extent, temporal_extent) + collection = pystac.Collection(asset_id, "desc", extent) collection.set_self_href(URL_TEMPLATE % 2019) ScientificExtension.add_to(collection) diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index 587a6b725..dad6dff3f 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -2,7 +2,7 @@ import unittest from datetime import datetime -import pystac as ps +import pystac from pystac.extensions.timestamps import TimestampsExtension from pystac.utils import get_opt, str_to_datetime, datetime_to_str from tests.utils import TestCases, test_to_from_dict @@ -20,7 +20,7 @@ def setUp(self): self.sample_datetime = str_to_datetime(self.sample_datetime_str) def test_to_from_dict(self): - test_to_from_dict(self, ps.Item, self.item_dict) + test_to_from_dict(self, pystac.Item, self.item_dict) def test_apply(self): item = next(iter(TestCases.test_case_2().get_all_items())) @@ -59,11 +59,11 @@ def test_apply(self): self.assertNotIn(p, item.properties) def test_validate_timestamps(self): - item = ps.read_file(self.example_uri) + item = pystac.read_file(self.example_uri) item.validate() def test_expires(self): - timestamps_item = ps.Item.from_file(self.example_uri) + timestamps_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("expires", timestamps_item.properties) @@ -105,7 +105,7 @@ def test_expires(self): timestamps_item.validate() def test_published(self): - timestamps_item = ps.Item.from_file(self.example_uri) + timestamps_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("published", timestamps_item.properties) @@ -147,7 +147,7 @@ def test_published(self): timestamps_item.validate() def test_unpublished(self): - timestamps_item = ps.Item.from_file(self.example_uri) + timestamps_item = pystac.Item.from_file(self.example_uri) # Get self.assertNotIn("unpublished", timestamps_item.properties) diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 30d865a0e..1d8451e9e 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -4,7 +4,7 @@ from pystac.validation import STACValidationError import unittest -import pystac as ps +import pystac from pystac.extensions import version from pystac.extensions.version import VersionExtension from tests.utils import TestCases @@ -12,12 +12,14 @@ URL_TEMPLATE: str = "http://example.com/catalog/%s.json" -def make_item(year: int) -> ps.Item: +def make_item(year: int) -> pystac.Item: """Create basic test items that are only slightly different.""" asset_id = f"USGS/GAP/CONUS/{year}" start = datetime.datetime(year, 1, 2) - item = ps.Item(id=asset_id, geometry=None, bbox=None, datetime=start, properties={}) + item = pystac.Item( + id=asset_id, geometry=None, bbox=None, datetime=start, properties={} + ) item.set_self_href(URL_TEMPLATE % year) VersionExtension.add_to(item) @@ -203,16 +205,16 @@ def test_multiple_link_setting(self): self.assertEqual(expected_href, links[0].get_href()) -def make_collection(year: int) -> ps.Collection: +def make_collection(year: int) -> pystac.Collection: asset_id = f"my/collection/of/things/{year}" start = datetime.datetime(2014, 8, 10) end = datetime.datetime(year, 1, 3, 4, 5) bboxes = [[-180.0, -90.0, 180.0, 90.0]] - spatial_extent = ps.SpatialExtent(bboxes) - temporal_extent = ps.TemporalExtent([[start, end]]) - extent = ps.Extent(spatial_extent, temporal_extent) + spatial_extent = pystac.SpatialExtent(bboxes) + temporal_extent = pystac.TemporalExtent([[start, end]]) + extent = pystac.Extent(spatial_extent, temporal_extent) - collection = ps.Collection(asset_id, "desc", extent) + collection = pystac.Collection(asset_id, "desc", extent) collection.set_self_href(URL_TEMPLATE % year) VersionExtension.add_to(collection) @@ -312,9 +314,9 @@ def test_full_copy(self): # Fetch two collections from the catalog col1 = cat.get_child("area-1-1", recursive=True) - assert isinstance(col1, ps.Collection) + assert isinstance(col1, pystac.Collection) col2 = cat.get_child("area-2-2", recursive=True) - assert isinstance(col2, ps.Collection) + assert isinstance(col2, pystac.Collection) # Enable the version extension on each, and link them # as if they are different versions of the same Collection diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 1580dfbaf..ee96a7107 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -1,7 +1,7 @@ import json import unittest -import pystac as ps +import pystac from pystac.extensions.view import ViewExtension from tests.utils import TestCases, test_to_from_dict @@ -14,7 +14,7 @@ def setUp(self): def test_to_from_dict(self): with open(self.example_uri) as f: d = json.load(f) - test_to_from_dict(self, ps.Item, d) + test_to_from_dict(self, pystac.Item, d) def test_apply(self): item = next(iter(TestCases.test_case_2().get_all_items())) @@ -36,12 +36,12 @@ def test_apply(self): self.assertEqual(ViewExtension.ext(item).sun_elevation, 5.0) def test_validate_view(self): - item = ps.Item.from_file(self.example_uri) + item = pystac.Item.from_file(self.example_uri) self.assertTrue(ViewExtension.has_extension(item)) item.validate() def test_off_nadir(self): - view_item = ps.Item.from_file(self.example_uri) + view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:off_nadir", view_item.properties) @@ -74,7 +74,7 @@ def test_off_nadir(self): view_item.validate() def test_incidence_angle(self): - view_item = ps.Item.from_file(self.example_uri) + view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:incidence_angle", view_item.properties) @@ -111,7 +111,7 @@ def test_incidence_angle(self): view_item.validate() def test_azimuth(self): - view_item = ps.Item.from_file(self.example_uri) + view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:azimuth", view_item.properties) @@ -144,7 +144,7 @@ def test_azimuth(self): view_item.validate() def test_sun_azimuth(self): - view_item = ps.Item.from_file(self.example_uri) + view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:sun_azimuth", view_item.properties) @@ -179,7 +179,7 @@ def test_sun_azimuth(self): view_item.validate() def test_sun_elevation(self): - view_item = ps.Item.from_file(self.example_uri) + view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:sun_elevation", view_item.properties) diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index 0adff5ba4..97c1e6436 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -1,7 +1,7 @@ import unittest from urllib.error import HTTPError -import pystac as ps +import pystac from pystac.cache import CollectionCache from pystac.serialization import ( identify_stac_object, @@ -22,8 +22,8 @@ def test_identify(self): for example in self.examples: with self.subTest(example.path): path = example.path - d = ps.StacIO.default().read_json(path) - if identify_stac_object_type(d) == ps.STACObjectType.ITEM: + d = pystac.StacIO.default().read_json(path) + if identify_stac_object_type(d) == pystac.STACObjectType.ITEM: try: merge_common_properties( d, json_href=path, collection_cache=collection_cache diff --git a/tests/serialization/test_migrate.py b/tests/serialization/test_migrate.py index 777168161..c4c9ed6f9 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/serialization/test_migrate.py @@ -2,7 +2,7 @@ from pystac.extensions.view import ViewExtension import unittest -import pystac as ps +import pystac from pystac.cache import CollectionCache from pystac.serialization import ( identify_stac_object, @@ -25,8 +25,8 @@ def test_migrate(self): with self.subTest(example.path): path = example.path - d = ps.StacIO.default().read_json(path) - if identify_stac_object_type(d) == ps.STACObjectType.ITEM: + d = pystac.StacIO.default().read_json(path) + if identify_stac_object_type(d) == pystac.STACObjectType.ITEM: merge_common_properties( d, json_href=path, collection_cache=collection_cache ) @@ -40,7 +40,7 @@ def test_migrate(self): self.assertEqual(migrated_info.object_type, info.object_type) self.assertEqual( migrated_info.version_range.latest_valid_version(), - ps.get_stac_version(), + pystac.get_stac_version(), ) # Ensure all stac_extensions are schema URIs @@ -50,13 +50,13 @@ def test_migrate(self): ) # Test that PySTAC can read it without errors. - if info.object_type != ps.STACObjectType.ITEMCOLLECTION: + if info.object_type != pystac.STACObjectType.ITEMCOLLECTION: self.assertIsInstance( - ps.read_dict(migrated_d, href=path), ps.STACObject + pystac.read_dict(migrated_d, href=path), pystac.STACObject ) def test_migrates_removed_extension(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/0.7.0/extensions/sar/" "examples/sentinel1.json" ) @@ -68,7 +68,7 @@ def test_migrates_removed_extension(self): ) def test_migrates_added_extension(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/0.8.1/item-spec/" "examples/planet-sample.json" ) @@ -80,7 +80,7 @@ def test_migrates_added_extension(self): self.assertEqual(view_ext.off_nadir, 1) def test_migrates_renamed_extension(self): - collection = ps.Collection.from_file( + collection = pystac.Collection.from_file( TestCases.get_path( "data-files/examples/0.9.0/extensions/asset/" "examples/example-landsat8.json" diff --git a/tests/test_cache.py b/tests/test_cache.py index ac2430c9d..1fcc75965 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -2,13 +2,13 @@ from pystac.utils import get_opt import unittest -import pystac as ps +import pystac from pystac.cache import ResolvedObjectCache, ResolvedObjectCollectionCache from tests.utils import TestCases -def create_catalog(suffix: Any, include_href: bool = True) -> ps.Catalog: - return ps.Catalog( +def create_catalog(suffix: Any, include_href: bool = True) -> pystac.Catalog: + return pystac.Catalog( id="test {}".format(suffix), description="test desc {}".format(suffix), href=( diff --git a/tests/test_catalog.py b/tests/test_catalog.py index a1951490b..7d0c0e993 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -6,7 +6,7 @@ from datetime import datetime from collections import defaultdict -import pystac as ps +import pystac from pystac import ( Catalog, Collection, @@ -27,7 +27,7 @@ def test_determine_type_for_absolute_published(self): cat = TestCases.test_case_1() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat_json = ps.StacIO.default().read_json( + cat_json = pystac.StacIO.default().read_json( os.path.join(tmp_dir, "catalog.json") ) @@ -38,7 +38,7 @@ def test_determine_type_for_relative_published(self): cat = TestCases.test_case_2() with TemporaryDirectory() as tmp_dir: cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) - cat_json = ps.StacIO.default().read_json( + cat_json = pystac.StacIO.default().read_json( os.path.join(tmp_dir, "catalog.json") ) @@ -46,7 +46,7 @@ def test_determine_type_for_relative_published(self): self.assertEqual(catalog_type, CatalogType.RELATIVE_PUBLISHED) def test_determine_type_for_self_contained(self): - cat_json = ps.StacIO.default().read_json( + cat_json = pystac.StacIO.default().read_json( TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") ) catalog_type = CatalogType.determine_type(cat_json) @@ -188,13 +188,13 @@ def test_clear_children_sets_parent_and_root_to_None(self): def test_add_child_throws_if_item(self): cat = TestCases.test_case_1() item = next(iter(cat.get_all_items())) - with self.assertRaises(ps.STACError): + with self.assertRaises(pystac.STACError): cat.add_child(item) # type:ignore def test_add_item_throws_if_child(self): cat = TestCases.test_case_1() child = next(iter(cat.get_children())) - with self.assertRaises(ps.STACError): + with self.assertRaises(pystac.STACError): cat.add_item(child) # type:ignore def test_get_child_returns_none_if_not_found(self): @@ -288,7 +288,7 @@ def test_save_uses_previous_catalog_type(self): href = catalog.self_href catalog.save() - cat2 = ps.Catalog.from_file(href) + cat2 = pystac.Catalog.from_file(href) self.assertEqual(cat2.catalog_type, CatalogType.SELF_CONTAINED) def test_clone_uses_previous_catalog_type(self): @@ -304,7 +304,7 @@ def test_normalize_hrefs_sets_all_hrefs(self): self.assertTrue(root.get_self_href().startswith("http://example.com")) for link in root.links: if link.is_resolved(): - target_href = cast(ps.STACObject, link.target).self_href + target_href = cast(pystac.STACObject, link.target).self_href else: target_href = link.absolute_href self.assertTrue( @@ -357,9 +357,9 @@ def test_generate_subcatalogs_does_not_change_item_count(self): with TemporaryDirectory() as tmp_dir: catalog.normalize_hrefs(tmp_dir) - catalog.save(ps.CatalogType.SELF_CONTAINED) + catalog.save(pystac.CatalogType.SELF_CONTAINED) - cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) + cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) for child in cat2.get_children(): actual = len(list(child.get_all_items())) expected = item_counts[child.id] @@ -469,7 +469,7 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self): self.assertEqual(len(subcats), 2, msg=" for item '{}'".format(item.id)) def test_map_items(self): - def item_mapper(item: ps.Item) -> ps.Item: + def item_mapper(item: pystac.Item) -> pystac.Item: item.properties["ITEM_MAPPER"] = "YEP" return item @@ -490,7 +490,7 @@ def item_mapper(item: ps.Item) -> ps.Item: self.assertFalse("ITEM_MAPPER" in item.properties) def test_map_items_multiple(self): - def item_mapper(item: ps.Item) -> List[ps.Item]: + def item_mapper(item: pystac.Item) -> List[pystac.Item]: item2 = item.clone() item2.id = item2.id + "_2" item.properties["ITEM_MAPPER_1"] = "YEP" @@ -554,11 +554,11 @@ def test_map_items_multiple_2(self): item2.add_asset("ortho", Asset(href="/some/other/ortho.tif")) kitten.add_item(item2) - def modify_item_title(item: ps.Item) -> ps.Item: + def modify_item_title(item: pystac.Item) -> pystac.Item: item.properties["title"] = "Some title" return item - def create_label_item(item: ps.Item) -> List[ps.Item]: + def create_label_item(item: pystac.Item) -> List[pystac.Item]: # Assumes the GEOJSON labels are in the # same location as the image img_href = item.assets["ortho"].href @@ -596,7 +596,7 @@ def create_label_item(item: ps.Item) -> List[ps.Item]: def test_map_assets_single(self): changed_asset = "d43bead8-e3f8-4c51-95d6-e24e750a402b" - def asset_mapper(key: str, asset: ps.Asset) -> ps.Asset: + def asset_mapper(key: str, asset: pystac.Asset) -> pystac.Asset: if key == changed_asset: asset.title = "NEW TITLE" @@ -626,8 +626,8 @@ def test_map_assets_tup(self): changed_assets: List[str] = [] def asset_mapper( - key: str, asset: ps.Asset - ) -> Union[ps.Asset, Tuple[str, ps.Asset]]: + key: str, asset: pystac.Asset + ) -> Union[pystac.Asset, Tuple[str, pystac.Asset]]: if asset.media_type and "geotiff" in asset.media_type: asset.title = "NEW TITLE" changed_assets.append(key) @@ -663,8 +663,8 @@ def test_map_assets_multi(self): changed_assets = [] def asset_mapper( - key: str, asset: ps.Asset - ) -> Union[ps.Asset, Dict[str, ps.Asset]]: + key: str, asset: pystac.Asset + ) -> Union[pystac.Asset, Dict[str, pystac.Asset]]: if asset.media_type and "geotiff" in asset.media_type: changed_assets.append(key) mod1 = asset.clone() @@ -782,7 +782,7 @@ def test_extra_fields(self): self.assertTrue("type" in cat_json) self.assertEqual(cat_json["type"], "FeatureCollection") - read_cat = ps.Catalog.from_file(p) + read_cat = pystac.Catalog.from_file(p) self.assertTrue("type" in read_cat.extra_fields) self.assertEqual(read_cat.extra_fields["type"], "FeatureCollection") @@ -800,9 +800,9 @@ def test_validate_all(self): item.geometry = {"type": "INVALID", "coordinates": "NONE"} with TemporaryDirectory() as tmp_dir: cat.normalize_hrefs(tmp_dir) - cat.save(catalog_type=ps.CatalogType.SELF_CONTAINED) + cat.save(catalog_type=pystac.CatalogType.SELF_CONTAINED) - cat2 = ps.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) + cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) with self.assertRaises(STACValidationError): cat2.validate_all() @@ -925,7 +925,7 @@ def test_resolve_planet(self): def test_handles_children_with_same_id(self): # This catalog has the root and child collection share an ID. - cat = ps.Catalog.from_file( + cat = pystac.Catalog.from_file( TestCases.get_path("data-files/invalid/shared-id/catalog.json") ) items = list(cat.get_all_items()) @@ -942,9 +942,9 @@ def test_catalog_with_href_caches_by_href(self): class FullCopyTest(unittest.TestCase): - def check_link(self, link: ps.Link, tag: str): + def check_link(self, link: pystac.Link, tag: str): if link.is_resolved(): - target_href: str = cast(ps.STACObject, link.target).self_href + target_href: str = cast(pystac.STACObject, link.target).self_href else: target_href = str(link.target) self.assertTrue( diff --git a/tests/test_collection.py b/tests/test_collection.py index 574e24375..3ae88ef2c 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -5,7 +5,7 @@ from datetime import datetime from dateutil import tz -import pystac as ps +import pystac from pystac.extensions.eo import EOExtension from pystac.validation import validate_dict from pystac import Collection, Item, Extent, SpatialExtent, TemporalExtent, CatalogType @@ -33,14 +33,14 @@ def test_read_eo_items_are_heritable(self): def test_save_uses_previous_catalog_type(self): collection = TestCases.test_case_8() - assert collection.STAC_OBJECT_TYPE == ps.STACObjectType.COLLECTION + assert collection.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION self.assertEqual(collection.catalog_type, CatalogType.SELF_CONTAINED) with TemporaryDirectory() as tmp_dir: collection.normalize_hrefs(tmp_dir) href = collection.self_href collection.save() - collection2 = ps.Collection.from_file(href) + collection2 = pystac.Collection.from_file(href) self.assertEqual(collection2.catalog_type, CatalogType.SELF_CONTAINED) def test_clone_uses_previous_catalog_type(self): @@ -54,12 +54,12 @@ def test_multiple_extents(self): col1 = cat1.get_child("country-1").get_child("area-1-1") col1.validate() self.assertIsInstance(col1, Collection) - validate_dict(col1.to_dict(), ps.STACObjectType.COLLECTION) + validate_dict(col1.to_dict(), pystac.STACObjectType.COLLECTION) multi_ext_uri = TestCases.get_path("data-files/collections/multi-extent.json") with open(multi_ext_uri) as f: multi_ext_dict = json.load(f) - validate_dict(multi_ext_dict, ps.STACObjectType.COLLECTION) + validate_dict(multi_ext_dict, pystac.STACObjectType.COLLECTION) self.assertIsInstance(Collection.from_dict(multi_ext_dict), Collection) multi_ext_col = Collection.from_file(multi_ext_uri) @@ -88,7 +88,7 @@ def test_extra_fields(self): self.assertTrue("test" in col_json) self.assertEqual(col_json["test"], "extra") - read_col = ps.Collection.from_file(p) + read_col = pystac.Collection.from_file(p) self.assertTrue("test" in read_col.extra_fields) self.assertEqual(read_col.extra_fields["test"], "extra") @@ -162,7 +162,7 @@ def test_supplying_href_in_init_does_not_fail(self): self.assertEqual(collection.get_self_href(), test_href) def test_collection_with_href_caches_by_href(self): - collection = ps.Collection.from_file( + collection = pystac.Collection.from_file( TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") ) cache = collection._resolved_objects diff --git a/tests/test_item.py b/tests/test_item.py index 4654653f3..2e38bfcf0 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -5,7 +5,7 @@ import unittest from tempfile import TemporaryDirectory -import pystac as ps +import pystac from pystac import Asset, Item, Provider from pystac.validation import validate_dict import pystac.serialization.common_properties @@ -75,7 +75,9 @@ def test_asset_absolute_href(self): self.assertEqual(expected_href, actual_href) def test_extra_fields(self): - item = ps.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + item = pystac.Item.from_file( + TestCases.get_path("data-files/item/sample-item.json") + ) item.extra_fields["test"] = "extra" @@ -87,13 +89,13 @@ def test_extra_fields(self): self.assertTrue("test" in item_json) self.assertEqual(item_json["test"], "extra") - read_item = ps.Item.from_file(p) + read_item = pystac.Item.from_file(p) self.assertTrue("test" in read_item.extra_fields) self.assertEqual(read_item.extra_fields["test"], "extra") def test_clearing_collection(self): collection = TestCases.test_case_4().get_child("acc") - assert isinstance(collection, ps.Collection) + assert isinstance(collection, pystac.Collection) item = next(iter(collection.get_all_items())) self.assertEqual(item.collection_id, collection.id) item.set_collection(None) @@ -113,9 +115,11 @@ def test_datetime_ISO8601_format(self): self.assertEqual("2016-05-03T13:22:30.040000Z", formatted_time) def test_null_datetime(self): - item = ps.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + item = pystac.Item.from_file( + TestCases.get_path("data-files/item/sample-item.json") + ) - with self.assertRaises(ps.STACError): + with self.assertRaises(pystac.STACError): Item( "test", geometry=item.geometry, @@ -138,7 +142,7 @@ def test_null_datetime(self): null_dt_item.validate() def test_get_set_asset_datetime(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) item_datetime = item.datetime @@ -186,7 +190,7 @@ def test_null_geometry(self): with open(m) as f: item_dict = json.load(f) - validate_dict(item_dict, ps.STACObjectType.ITEM) + validate_dict(item_dict, pystac.STACObjectType.ITEM) item = Item.from_dict(item_dict) self.assertIsInstance(item, Item) @@ -198,7 +202,7 @@ def test_null_geometry(self): item_dict["bbox"] def test_0_9_item_with_no_extensions_does_not_read_collection_data(self): - item_json = ps.StacIO.default().read_json( + item_json = pystac.StacIO.default().read_json( TestCases.get_path("data-files/examples/hand-0.9.0/010100/010100.json") ) assert item_json.get("stac_extensions") is None @@ -448,7 +452,7 @@ def test_common_metadata_basics(self): self.assertEqual(x.properties["gsd"], example_gsd) def test_asset_start_datetime(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -471,7 +475,7 @@ def test_asset_start_datetime(self): self.assertEqual(cm.start_datetime, item_value) def test_asset_end_datetime(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -494,7 +498,7 @@ def test_asset_end_datetime(self): self.assertEqual(cm.end_datetime, item_value) def test_asset_license(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -517,14 +521,14 @@ def test_asset_license(self): self.assertEqual(cm.license, item_value) def test_asset_providers(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata item_value = get_opt(cm.providers) a2_known_value = [ - ps.Provider( + pystac.Provider( name="USGS", url="https://landsat.usgs.gov/", roles=["producer", "licensor"], @@ -540,7 +544,7 @@ def test_asset_providers(self): # Set set_value = [ - ps.Provider( + pystac.Provider( name="John Snow", url="https://cholera.com/", roles=["producer"] ) ] @@ -552,7 +556,7 @@ def test_asset_providers(self): self.assertEqual(get_opt(cm.providers)[0].to_dict(), item_value[0].to_dict()) def test_asset_platform(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -575,7 +579,7 @@ def test_asset_platform(self): self.assertEqual(cm.platform, item_value) def test_asset_instruments(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -598,7 +602,7 @@ def test_asset_instruments(self): self.assertEqual(cm.instruments, item_value) def test_asset_constellation(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -621,7 +625,7 @@ def test_asset_constellation(self): self.assertEqual(cm.constellation, item_value) def test_asset_mission(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -644,7 +648,7 @@ def test_asset_mission(self): self.assertEqual(cm.mission, item_value) def test_asset_gsd(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -667,7 +671,7 @@ def test_asset_gsd(self): self.assertEqual(cm.gsd, item_value) def test_asset_created(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata @@ -690,7 +694,7 @@ def test_asset_created(self): self.assertEqual(cm.created, item_value) def test_asset_updated(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) cm = item.common_metadata diff --git a/tests/test_layout.py b/tests/test_layout.py index a99d5099b..3ed7e2815 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -4,7 +4,7 @@ from pystac.collection import Collection import unittest -import pystac as ps +import pystac from pystac.layout import ( LayoutTemplate, CustomLayoutStrategy, @@ -25,7 +25,7 @@ def test_templates_item_datetime(self): template = LayoutTemplate("${year}/${month}/${day}/${date}/item.json") - item = ps.Item( + item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, @@ -54,7 +54,7 @@ def test_templates_item_start_datetime(self): template = LayoutTemplate("${year}/${month}/${day}/${date}/item.json") - item = ps.Item( + item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, @@ -108,7 +108,7 @@ def test_nested_properties(self): template = LayoutTemplate("${test.prop}/${ext:extra.test.prop}/item.json") - item = ps.Item( + item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, @@ -133,7 +133,7 @@ def test_substitute_with_colon_properties(self): template = LayoutTemplate("${ext:prop}/item.json") - item = ps.Item( + item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, @@ -158,7 +158,7 @@ def test_defaults(self): self.assertEqual(path, "yes/collection.json") def test_docstring_examples(self): - item = ps.Item.from_file( + item = pystac.Item.from_file( TestCases.get_path( "data-files/examples/1.0.0-beta.2/item-spec/" "examples/landsat8-sample.json" @@ -182,27 +182,29 @@ def test_docstring_examples(self): class CustomLayoutStrategyTest(unittest.TestCase): - def get_custom_catalog_func(self) -> Callable[[ps.Catalog, str, bool], str]: - def fn(cat: ps.Catalog, parent_dir: str, is_root: bool): + def get_custom_catalog_func(self) -> Callable[[pystac.Catalog, str, bool], str]: + def fn(cat: pystac.Catalog, parent_dir: str, is_root: bool): return os.path.join(parent_dir, "cat/{}/{}.json".format(is_root, cat.id)) return fn - def get_custom_collection_func(self) -> Callable[[ps.Collection, str, bool], str]: - def fn(col: ps.Collection, parent_dir: str, is_root: bool): + def get_custom_collection_func( + self, + ) -> Callable[[pystac.Collection, str, bool], str]: + def fn(col: pystac.Collection, parent_dir: str, is_root: bool): return os.path.join(parent_dir, "col/{}/{}.json".format(is_root, col.id)) return fn - def get_custom_item_func(self) -> Callable[[ps.Item, str], str]: - def fn(item: ps.Item, parent_dir: str): + def get_custom_item_func(self) -> Callable[[pystac.Item, str], str]: + def fn(item: pystac.Item, parent_dir: str): return os.path.join(parent_dir, "item/{}.json".format(item.id)) return fn def test_produces_layout_for_catalog(self): strategy = CustomLayoutStrategy(catalog_func=self.get_custom_catalog_func()) - cat = ps.Catalog(id="test", description="test desc") + cat = pystac.Catalog(id="test", description="test desc") href = strategy.get_href(cat, parent_dir="http://example.com", is_root=True) self.assertEqual(href, "http://example.com/cat/True/test.json") @@ -213,7 +215,7 @@ def test_produces_fallback_layout_for_catalog(self): item_func=self.get_custom_item_func(), fallback_strategy=fallback, ) - cat = ps.Catalog(id="test", description="test desc") + cat = pystac.Catalog(id="test", description="test desc") href = strategy.get_href(cat, parent_dir="http://example.com") expected = fallback.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, expected) @@ -273,14 +275,14 @@ def _get_collection(self) -> Collection: def test_produces_layout_for_catalog(self): strategy = TemplateLayoutStrategy(catalog_template=self.TEST_CATALOG_TEMPLATE) - cat = ps.Catalog(id="test", description="test-desc") + cat = pystac.Catalog(id="test", description="test-desc") href = strategy.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, "http://example.com/cat/test/test-desc/catalog.json") def test_produces_layout_for_catalog_with_filename(self): template = "cat/${id}/${description}/${id}.json" strategy = TemplateLayoutStrategy(catalog_template=template) - cat = ps.Catalog(id="test", description="test-desc") + cat = pystac.Catalog(id="test", description="test-desc") href = strategy.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, "http://example.com/cat/test/test-desc/test.json") @@ -291,7 +293,7 @@ def test_produces_fallback_layout_for_catalog(self): item_template=self.TEST_ITEM_TEMPLATE, fallback_strategy=fallback, ) - cat = ps.Catalog(id="test", description="test desc") + cat = pystac.Catalog(id="test", description="test desc") href = strategy.get_href(cat, parent_dir="http://example.com") expected = fallback.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, expected) @@ -373,14 +375,14 @@ def setUp(self): self.strategy = BestPracticesLayoutStrategy() def test_produces_layout_for_root_catalog(self): - cat = ps.Catalog(id="test", description="test desc") + cat = pystac.Catalog(id="test", description="test desc") href = self.strategy.get_href( cat, parent_dir="http://example.com", is_root=True ) self.assertEqual(href, "http://example.com/catalog.json") def test_produces_layout_for_child_catalog(self): - cat = ps.Catalog(id="test", description="test desc") + cat = pystac.Catalog(id="test", description="test desc") href = self.strategy.get_href(cat, parent_dir="http://example.com") self.assertEqual(href, "http://example.com/test/catalog.json") diff --git a/tests/test_link.py b/tests/test_link.py index 8639eb101..d916f1f07 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -1,17 +1,17 @@ import datetime import unittest -import pystac as ps +import pystac from tests.utils.test_cases import ARBITRARY_EXTENT TEST_DATETIME: datetime.datetime = datetime.datetime(2020, 3, 14, 16, 32) class LinkTest(unittest.TestCase): - item: ps.Item + item: pystac.Item def setUp(self): - self.item = ps.Item( + self.item = pystac.Item( id="test-item", geometry=None, bbox=None, @@ -22,7 +22,7 @@ def setUp(self): def test_minimal(self): rel = "my rel" target = "https://example.com/a/b" - link = ps.Link(rel, target) + link = pystac.Link(rel, target) self.assertEqual(target, link.get_href()) self.assertEqual(target, link.get_absolute_href()) @@ -57,7 +57,7 @@ def test_relative(self): rel = "my rel" target = "../elsewhere" mime_type = "example/stac_thing" - link = ps.Link(rel, target, mime_type, "a title", properties={"a": "b"}) + link = pystac.Link(rel, target, mime_type, "a title", properties={"a": "b"}) expected_dict = { "rel": rel, "href": target, @@ -69,7 +69,7 @@ def test_relative(self): 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 = ps.Catalog(id="test", description="test desc") + catalog = pystac.Catalog(id="test", description="test desc") catalog.add_item(self.item) catalog.set_self_href("/some/href") @@ -77,7 +77,7 @@ def test_link_does_not_fail_if_href_is_none(self): self.assertIsNone(link.get_href()) def test_resolve_stac_object_no_root_and_target_is_item(self): - link = ps.Link("my rel", target=self.item) + link = pystac.Link("my rel", target=self.item) link.resolve_stac_object() @@ -92,22 +92,22 @@ def test_from_dict_round_trip(self): {"rel": "self", "href": "t"}, ] for d in test_cases: - d2 = ps.Link.from_dict(d).to_dict() + d2 = pystac.Link.from_dict(d).to_dict() self.assertEqual(d, d2) def test_from_dict_failures(self): for d in [{}, {"href": "t"}, {"rel": "r"}]: with self.assertRaises(KeyError): - ps.Link.from_dict(d) + pystac.Link.from_dict(d) def test_collection(self): - c = ps.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) - link = ps.Link.collection(c) + c = pystac.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) + link = pystac.Link.collection(c) expected = {"rel": "collection", "href": None, "type": "application/json"} self.assertEqual(expected, link.to_dict()) def test_child(self): - c = ps.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) - link = ps.Link.child(c) + c = pystac.Collection("collection id", "desc", extent=ARBITRARY_EXTENT) + link = pystac.Link.child(c) expected = {"rel": "child", "href": None, "type": "application/json"} self.assertEqual(expected, link.to_dict()) diff --git a/tests/test_version.py b/tests/test_version.py index 4598b025f..cc48ff1b7 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,21 +1,21 @@ import os import unittest -import pystac as ps +import pystac from tests.utils import TestCases class VersionTest(unittest.TestCase): def setUp(self): self._prev_env_version = os.environ.get("PYSTAC_STAC_VERSION_OVERRIDE") - self._prev_version = ps.get_stac_version() + self._prev_version = pystac.get_stac_version() def tearDown(self): if self._prev_env_version is None: os.environ.pop("PYSTAC_STAC_VERSION_OVERRIDE", None) else: os.environ["PYSTAC_STAC_VERSION_OVERRIDE"] = self._prev_env_version - ps.set_stac_version(None) + pystac.set_stac_version(None) def test_override_stac_version_with_environ(self): @@ -27,7 +27,7 @@ def test_override_stac_version_with_environ(self): def test_override_stac_version_with_call(self): override_version = "1.0.0-delta.2" - ps.set_stac_version(override_version) + pystac.set_stac_version(override_version) cat = TestCases.test_case_1() d = cat.to_dict() self.assertEqual(d["stac_version"], override_version) diff --git a/tests/test_writing.py b/tests/test_writing.py index 44af0f732..508896280 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -1,7 +1,7 @@ import unittest from tempfile import TemporaryDirectory -import pystac as ps +import pystac from pystac import Collection, CatalogType, HIERARCHICAL_LINKS from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href from pystac.validation import validate_dict @@ -14,7 +14,7 @@ class STACWritingTest(unittest.TestCase): and ensure that links are correctly set to relative or absolute. """ - def validate_catalog(self, catalog: ps.Catalog): + def validate_catalog(self, catalog: pystac.Catalog): catalog.validate() validated_count = 1 @@ -28,11 +28,11 @@ def validate_catalog(self, catalog: ps.Catalog): return validated_count def validate_file(self, path: str, object_type: str): - d = ps.StacIO.default().read_json(path) - return validate_dict(d, ps.STACObjectType(object_type)) + d = pystac.StacIO.default().read_json(path) + return validate_dict(d, pystac.STACObjectType(object_type)) - def validate_link_types(self, root_href: str, catalog_type: ps.CatalogType): - def validate_asset_href_type(item: ps.Item, item_href: str): + def validate_link_types(self, root_href: str, catalog_type: pystac.CatalogType): + def validate_asset_href_type(item: pystac.Item, item_href: str): for asset in item.assets.values(): if not is_absolute_href(asset.href): is_valid = not is_absolute_href(asset.href) @@ -47,10 +47,11 @@ def validate_asset_href_type(item: ps.Item, item_href: str): def validate_item_link_type( href: str, link_type: str, should_include_self: bool ): - item_dict = ps.StacIO.default().read_json(href) - item = ps.Item.from_file(href) + item_dict = pystac.StacIO.default().read_json(href) + item = pystac.Item.from_file(href) rel_links = ( - HIERARCHICAL_LINKS + ps.EXTENSION_HOOKS.get_extended_object_links(item) + HIERARCHICAL_LINKS + + pystac.EXTENSION_HOOKS.get_extended_object_links(item) ) for link in item.get_links(): if not link.rel == "self": @@ -67,8 +68,8 @@ def validate_item_link_type( def validate_catalog_link_type( href: str, link_type: str, should_include_self: bool ): - cat_dict = ps.StacIO.default().read_json(href) - cat = ps.Catalog.from_file(href) + cat_dict = pystac.StacIO.default().read_json(href) + cat = pystac.Catalog.from_file(href) rels = set([link["rel"] for link in cat_dict["links"]]) self.assertEqual("self" in rels, should_include_self) @@ -98,7 +99,7 @@ def validate_catalog_link_type( validate_catalog_link_type(root_href, link_type, root_should_include_href) - def do_test(self, catalog: ps.Catalog, catalog_type: ps.CatalogType): + def do_test(self, catalog: pystac.Catalog, catalog_type: pystac.CatalogType): with TemporaryDirectory() as tmp_dir: catalog.normalize_hrefs(tmp_dir) self.validate_catalog(catalog) @@ -110,13 +111,13 @@ def do_test(self, catalog: ps.Catalog, catalog_type: ps.CatalogType): for parent, _, items in catalog.walk(): if issubclass(type(parent), Collection): - stac_object_type = ps.STACObjectType.COLLECTION + stac_object_type = pystac.STACObjectType.COLLECTION else: - stac_object_type = ps.STACObjectType.CATALOG + stac_object_type = pystac.STACObjectType.CATALOG self.validate_file(parent.self_href, stac_object_type) for item in items: - self.validate_file(item.self_href, ps.STACObjectType.ITEM) + self.validate_file(item.self_href, pystac.STACObjectType.ITEM) def test_testcases(self): for catalog in TestCases.all_test_catalogs(): diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 334f4219c..0502b15eb 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -13,13 +13,13 @@ from datetime import datetime from dateutil.parser import parse -import pystac as ps +import pystac from tests.utils.stac_io_mock import MockStacIO # type:ignore def test_to_from_dict( test_class: unittest.TestCase, - stac_object_class: Type[ps.STACObject], + stac_object_class: Type[pystac.STACObject], d: Dict[str, Any], ) -> None: def _parse_times(a_dict: Dict[str, Any]) -> None: diff --git a/tests/utils/stac_io_mock.py b/tests/utils/stac_io_mock.py index 138cf9610..57904df5c 100644 --- a/tests/utils/stac_io_mock.py +++ b/tests/utils/stac_io_mock.py @@ -1,10 +1,10 @@ from typing import Any, Union from unittest.mock import Mock -import pystac as ps +import pystac -class MockStacIO(ps.StacIO): +class MockStacIO(pystac.StacIO): """Creates a mock that records STAC_IO calls for testing and allows clients to replace STAC_IO functionality, all within a context scope. """ @@ -12,12 +12,14 @@ class MockStacIO(ps.StacIO): def __init__(self): self.mock = Mock() - def read_text(self, source: Union[str, ps.Link], *args: Any, **kwargs: Any) -> str: + def read_text( + self, source: Union[str, pystac.Link], *args: Any, **kwargs: Any + ) -> str: self.mock.read_text(source) - return ps.StacIO.default().read_text(source) + return pystac.StacIO.default().read_text(source) def write_text( - self, dest: Union[str, ps.Link], txt: str, *args: Any, **kwargs: Any + self, dest: Union[str, pystac.Link], txt: str, *args: Any, **kwargs: Any ) -> None: self.mock.write_text(dest, txt) - ps.StacIO.default().write_text(dest, txt) + pystac.StacIO.default().write_text(dest, txt) diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 46e4323ca..4faae0f6c 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -3,7 +3,7 @@ import csv from typing import Any, Dict, List -import pystac as ps +import pystac from pystac import ( Catalog, Collection, @@ -79,7 +79,7 @@ class ExampleInfo: def __init__( self, path: str, - object_type: ps.STACObjectType, + object_type: pystac.STACObjectType, stac_version: str, extensions: List[str], valid: bool, @@ -119,7 +119,7 @@ def get_examples_info() -> List[ExampleInfo]: examples.append( ExampleInfo( path=path, - object_type=ps.STACObjectType(object_type), + object_type=pystac.STACObjectType(object_type), stac_version=stac_version, extensions=extensions, valid=valid, diff --git a/tests/validation/test_schema_uri_map.py b/tests/validation/test_schema_uri_map.py index 787dbaa90..dd6954a7a 100644 --- a/tests/validation/test_schema_uri_map.py +++ b/tests/validation/test_schema_uri_map.py @@ -1,13 +1,13 @@ import unittest -import pystac as ps +import pystac from pystac.validation.schema_uri_map import DefaultSchemaUriMap class SchemaUriMapTest(unittest.TestCase): def test_gets_schema_uri_for_old_version(self): d = DefaultSchemaUriMap() - uri = d.get_object_schema_uri(ps.STACObjectType.ITEM, "0.8.0") + uri = d.get_object_schema_uri(pystac.STACObjectType.ITEM, "0.8.0") self.assertEqual( uri, diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index bcc3906f4..2221c98a6 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -9,7 +9,7 @@ import jsonschema -import pystac as ps +import pystac import pystac.validation from pystac.cache import CollectionCache from pystac.serialization.common_properties import merge_common_properties @@ -19,12 +19,12 @@ class ValidateTest(unittest.TestCase): def test_validate_current_version(self): - catalog = ps.read_file( + catalog = pystac.read_file( TestCases.get_path("data-files/catalogs/test-case-1/" "catalog.json") ) catalog.validate() - collection = ps.read_file( + collection = pystac.read_file( TestCases.get_path( "data-files/catalogs/test-case-1/" "/country-1/area-1-1/" @@ -33,7 +33,7 @@ def test_validate_current_version(self): ) collection.validate() - item = ps.read_file(TestCases.get_path("data-files/item/sample-item.json")) + item = pystac.read_file(TestCases.get_path("data-files/item/sample-item.json")) item.validate() def test_validate_examples(self): @@ -55,7 +55,7 @@ def test_validate_examples(self): # Check if common properties need to be merged if stac_version < "1.0": - if example.object_type == ps.STACObjectType.ITEM: + if example.object_type == pystac.STACObjectType.ITEM: collection_cache = CollectionCache() merge_common_properties( stac_json, collection_cache, path @@ -92,7 +92,7 @@ def test_validate_all(self): for test_case in TestCases.all_test_catalogs(): catalog_href = test_case.get_self_href() if catalog_href is not None: - stac_dict = ps.StacIO.default().read_json(catalog_href) + stac_dict = pystac.StacIO.default().read_json(catalog_href) pystac.validation.validate_all(stac_dict, catalog_href) @@ -109,7 +109,7 @@ def test_validate_all(self): # Make sure it's valid before modification pystac.validation.validate_all( - ps.StacIO.default().read_json(new_cat_href), new_cat_href + pystac.StacIO.default().read_json(new_cat_href), new_cat_href ) # Modify a contained collection to add an extension for which the @@ -120,7 +120,7 @@ def test_validate_all(self): with open(os.path.join(dst_dir, "acc/collection.json"), "w") as f: json.dump(col, f) - stac_dict = ps.StacIO.default().read_json(new_cat_href) + stac_dict = pystac.StacIO.default().read_json(new_cat_href) with self.assertRaises(STACValidationError): pystac.validation.validate_all(stac_dict, new_cat_href) @@ -145,7 +145,7 @@ def test_validates_geojson_with_tuple_coordinates(self): ), } - item = ps.Item( + item = pystac.Item( id="test-item", geometry=geom, bbox=[-115.308, 36.126, -115.305, 36.129], From d3e573bcd5d7d5cdc7b4749ca8ee76c13fc3f05c Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 12:31:29 -0400 Subject: [PATCH 40/51] Move extension exceptions into pystac.errors --- pystac/__init__.py | 2 ++ pystac/errors.py | 16 ++++++++++++++++ pystac/extensions/__init__.py | 7 ------- pystac/extensions/base.py | 4 ---- pystac/extensions/datacube.py | 3 +-- pystac/extensions/eo.py | 5 +++-- pystac/extensions/file.py | 3 +-- pystac/extensions/hooks.py | 3 +-- pystac/extensions/pointcloud.py | 3 +-- pystac/extensions/projection.py | 5 ++--- pystac/extensions/sar.py | 4 ++-- pystac/extensions/sat.py | 3 +-- pystac/extensions/scientific.py | 3 +-- pystac/extensions/timestamps.py | 3 +-- pystac/extensions/version.py | 3 +-- pystac/extensions/view.py | 3 +-- tests/extensions/test_custom.py | 7 ++++--- 17 files changed, 38 insertions(+), 39 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index b61c21b8c..cf9b7890c 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -6,6 +6,8 @@ from pystac.errors import ( STACError, # type:ignore STACTypeError, # type:ignore + ExtensionAlreadyExistsError, # type:ignore + ExtensionTypeError, # type:ignore RequiredPropertyMissing, # type:ignore ) diff --git a/pystac/errors.py b/pystac/errors.py index bf5a80f92..0cb29ed77 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -19,6 +19,22 @@ class STACTypeError(Exception): pass +class ExtensionTypeError(Exception): + """An ExtensionTypeError is raised when an extension is used against + an object that the extension does not apply to + """ + + pass + + +class ExtensionAlreadyExistsError(Exception): + """An ExtensionAlreadyExistsError is raised when extension hooks + are registered with PySTAC if there are already hooks registered + for an extension with the same ID.""" + + pass + + class RequiredPropertyMissing(Exception): """This error is raised when a required value was expected to be there but was missing or None. This will happen, for example, diff --git a/pystac/extensions/__init__.py b/pystac/extensions/__init__.py index bb6919ca7..e69de29bb 100644 --- a/pystac/extensions/__init__.py +++ b/pystac/extensions/__init__.py @@ -1,7 +0,0 @@ -# flake8: noqa - - -class ExtensionError(Exception): - """An error related to the construction of extensions.""" - - pass diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index d2bc11e5c..191f4521e 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -4,10 +4,6 @@ import pystac -class ExtensionException(Exception): - pass - - class SummariesExtension: def __init__(self, collection: pystac.Collection) -> None: self.summaries = collection.summaries diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index f49e7468d..36308ef83 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -3,7 +3,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -340,7 +339,7 @@ def ext(obj: T) -> "DatacubeExtension[T]": elif isinstance(obj, pystac.Asset): return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"Datacube extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index 08f6f4c79..f7e8f8172 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -4,7 +4,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension, @@ -305,7 +304,9 @@ def ext(obj: T) -> "EOExtension[T]": elif isinstance(obj, pystac.Asset): return cast(EOExtension[T], AssetEOExtension(obj)) else: - raise ExtensionException(f"EO extension does not apply to type {type(obj)}") + raise pystac.ExtensionTypeError( + f"EO extension does not apply to type {type(obj)}" + ) @staticmethod def summaries(obj: pystac.Collection) -> "SummariesEOExtension": diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index bdc0e9493..072d51301 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -8,7 +8,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, SummariesExtension, @@ -147,7 +146,7 @@ def ext(obj: T) -> "FileExtension[T]": elif isinstance(obj, pystac.Asset): return cast(FileExtension[T], AssetFileExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index 8608268b3..33a7583d6 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -3,7 +3,6 @@ from typing import Any, Dict, Iterable, List, Optional, Set, TYPE_CHECKING import pystac -from pystac.extensions import ExtensionError from pystac.serialization.identify import STACJSONDescription, STACVersionID if TYPE_CHECKING: @@ -69,7 +68,7 @@ def __init__(self, hooks: Iterable[ExtensionHooks]): def add_extension_hooks(self, hooks: ExtensionHooks) -> None: e_id = hooks.schema_uri if e_id in self.hooks: - raise ExtensionError( + raise pystac.ExtensionAlreadyExistsError( "ExtensionDefinition with id '{}' already exists.".format(e_id) ) diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 1af5f82e4..5d79259a7 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -3,7 +3,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -525,7 +524,7 @@ def ext(obj: T) -> "PointcloudExtension[T]": elif isinstance(obj, pystac.Asset): return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index f2a99a67b..199b90401 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -1,6 +1,5 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -196,7 +195,7 @@ def centroid(self) -> Optional[Dict[str, float]]: Coordinates are defined in latitude and longitude, even if the data coordinate system does not use lat/long. - Exmample:: + Example:: item.ext.proj.centroid = { 'lat': 0.0, 'lon': 0.0 } @@ -259,7 +258,7 @@ def ext(obj: T) -> "ProjectionExtension[T]": elif isinstance(obj, pystac.Asset): return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 3bb6aae98..879101a3c 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -8,7 +8,7 @@ import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID -from pystac.extensions.base import ExtensionException, ExtensionManagementMixin +from pystac.extensions.base import ExtensionManagementMixin from pystac.extensions.projection import ProjectionExtension from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required, map_opt @@ -312,7 +312,7 @@ def ext(obj: T) -> "SarExtension[T]": elif isinstance(obj, pystac.Asset): return cast(SarExtension[T], AssetSarExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index c105eb422..ca83f9361 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -9,7 +9,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -103,7 +102,7 @@ def ext(obj: T) -> "SatExtension[T]": elif isinstance(obj, pystac.Asset): return cast(SatExtension[T], AssetSatExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index caa0d4bfc..fabc7672e 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -13,7 +13,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -199,7 +198,7 @@ def ext(obj: T) -> "ScientificExtension[T]": if isinstance(obj, pystac.Item): return cast(ScientificExtension[T], ItemScientificExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 2fabebba1..107d602eb 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -4,7 +4,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -119,7 +118,7 @@ def ext(obj: T) -> "TimestampsExtension[T]": elif isinstance(obj, pystac.Asset): return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index bbf4a1ef7..87cfc2931 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -10,7 +10,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -160,7 +159,7 @@ def ext(obj: T) -> "VersionExtension[T]": if isinstance(obj, pystac.Item): return cast(VersionExtension[T], ItemVersionExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"File extension does not apply to type {type(obj)}" ) diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index d10d2a580..68771d77d 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -3,7 +3,6 @@ import pystac from pystac.extensions.base import ( - ExtensionException, ExtensionManagementMixin, PropertiesExtension, ) @@ -160,7 +159,7 @@ def ext(obj: T) -> "ViewExtension[T]": elif isinstance(obj, pystac.Asset): return cast(ViewExtension[T], AssetViewExtension(obj)) else: - raise ExtensionException( + raise pystac.ExtensionTypeError( f"View extension does not apply to type {type(obj)}" ) diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index 9e93baf34..f3939b040 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -6,7 +6,6 @@ import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID -from pystac.extensions import ExtensionError from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -45,7 +44,7 @@ def add_link(self, target: pystac.STACObject) -> None: if self.obj is not None: self.obj.add_link(pystac.Link(TEST_LINK_REL, target)) else: - raise ExtensionError(f"{self} does not support links") + raise pystac.ExtensionAlreadyExistsError(f"{self} does not support links") @classmethod def get_schema_uri(cls) -> str: @@ -62,7 +61,9 @@ def custom_ext(obj: T) -> "CustomExtension[T]": if isinstance(obj, pystac.Catalog): return cast(CustomExtension[T], CatalogCustomExtension(obj)) - raise ExtensionError(f"Custom extension does not apply to {type(obj)}") + raise pystac.ExtensionTypeError( + f"Custom extension does not apply to {type(obj)}" + ) @staticmethod def summaries(obj: pystac.Collection) -> "SummariesCustomExtension": From f22b5876a33f5675cb8cadc7f1adff37e437d574 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 12:38:53 -0400 Subject: [PATCH 41/51] Move STACValidationError to pystac.errors --- pystac/__init__.py | 2 +- pystac/errors.py | 15 +++++++++++++++ pystac/validation/__init__.py | 15 --------------- pystac/validation/stac_validator.py | 5 ++--- tests/extensions/test_projection.py | 7 +++---- tests/extensions/test_sat.py | 5 ++--- tests/extensions/test_version.py | 5 ++--- tests/test_catalog.py | 3 +-- tests/validation/test_validate.py | 11 +++++------ 9 files changed, 31 insertions(+), 37 deletions(-) diff --git a/pystac/__init__.py b/pystac/__init__.py index cf9b7890c..d9a17eb5f 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -9,6 +9,7 @@ ExtensionAlreadyExistsError, # type:ignore ExtensionTypeError, # type:ignore RequiredPropertyMissing, # type:ignore + STACValidationError, # type:ignore ) from typing import Any, Dict, Optional @@ -34,7 +35,6 @@ from pystac.item import Item, Asset, CommonMetadata # type:ignore import pystac.validation -from pystac.validation import STACValidationError # type:ignore import pystac.extensions.hooks import pystac.extensions.datacube diff --git a/pystac/errors.py b/pystac/errors.py index 0cb29ed77..39bad4c7a 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -53,3 +53,18 @@ def __init__( ) -> None: msg = msg or f"{repr(obj)} does not have required property {prop}" super().__init__(msg) + + +class STACValidationError(Exception): + """Represents a validation error. Thrown by validation calls if the STAC JSON + is invalid. + + Args: + source (object): Source of the exception. Type will be determined by the + validation implementation. For the default JsonSchemaValidator this will a + the ``jsonschema.ValidationError``. + """ + + def __init__(self, message: str, source: Optional[Any] = None): + super().__init__(message) + self.source = source diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 779865069..b56e244f1 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -11,21 +11,6 @@ from pystac.stac_object import STACObjectType as STACObjectType_Type -class STACValidationError(Exception): - """Represents a validation error. Thrown by validation calls if the STAC JSON - is invalid. - - Args: - source (object): Source of the exception. Type will be determined by the - validation implementation. For the default JsonSchemaValidator this will a - the ``jsonschema.ValidationError``. - """ - - def __init__(self, message: str, source: Optional[Any] = None): - super().__init__(message) - self.source = source - - # Import after above class definition from pystac.validation.stac_validator import STACValidator, JsonSchemaSTACValidator diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 1176b1c63..777b4307d 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, Tuple import pystac -from pystac.validation import STACValidationError from pystac.validation.schema_uri_map import DefaultSchemaUriMap, SchemaUriMap try: @@ -220,7 +219,7 @@ def validate_core( msg = self._get_error_message( schema_uri, stac_object_type, None, href, stac_dict.get("id") ) - raise STACValidationError(msg, source=e) from e + raise pystac.STACValidationError(msg, source=e) from e def validate_extension( self, @@ -258,7 +257,7 @@ def validate_extension( msg = self._get_error_message( schema_uri, stac_object_type, extension_id, href, stac_dict.get("id") ) - raise STACValidationError(msg, source=e) from e + raise pystac.STACValidationError(msg, source=e) from e except Exception as e: logger.error(f"Exception while validating {stac_object_type} href: {href}") logger.exception(e) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 1580d0f6b..c77274645 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -4,7 +4,6 @@ from copy import deepcopy import pystac -from pystac.validation import STACValidationError from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt from tests.utils import TestCases, test_to_from_dict @@ -217,7 +216,7 @@ def test_projjson(self): proj_item.validate() # Ensure setting bad projjson fails validation - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).projjson = {"bad": "data"} proj_item.validate() @@ -258,7 +257,7 @@ def test_geometry(self): proj_item.validate() # Ensure setting bad geometry fails validation - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).geometry = {"bad": "data"} proj_item.validate() @@ -332,7 +331,7 @@ def test_centroid(self): proj_item.validate() # Ensure setting bad centroid fails validation - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).centroid = {"lat": 2.0, "lng": 3.0} proj_item.validate() diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index 19afaa75d..6831f2a7a 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -2,7 +2,6 @@ import datetime from typing import Any, Dict -from pystac.validation import STACValidationError import unittest import pystac @@ -32,7 +31,7 @@ def test_stac_extensions(self): def test_no_args_fails(self): SatExtension.ext(self.item).apply() - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): self.item.validate() def test_orbit_state(self): @@ -54,7 +53,7 @@ def test_relative_orbit(self): def test_relative_orbit_no_negative(self): negative_relative_orbit = -2 SatExtension.ext(self.item).apply(None, negative_relative_orbit) - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): self.item.validate() def test_both(self): diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 1d8451e9e..f036260e3 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -1,7 +1,6 @@ """Tests for pystac.extensions.version.""" import datetime -from pystac.validation import STACValidationError import unittest import pystac @@ -99,7 +98,7 @@ def test_successor(self): self.item.validate() def test_fail_validate(self): - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): self.item.validate() def test_all_links(self): @@ -296,7 +295,7 @@ def test_successor(self): self.collection.validate() def test_fail_validate(self): - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): self.collection.validate() def test_validate_all(self): diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 7d0c0e993..d7ad5a3a8 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -17,7 +17,6 @@ HIERARCHICAL_LINKS, ) from pystac.extensions.label import LabelClasses, LabelExtension, LabelType -from pystac.validation import STACValidationError from pystac.utils import is_absolute_href from tests.utils import TestCases, ARBITRARY_GEOM, ARBITRARY_BBOX, MockStacIO @@ -804,7 +803,7 @@ def test_validate_all(self): cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): cat2.validate_all() def test_set_hrefs_manually(self): diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 2221c98a6..12b8d33cb 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -13,7 +13,6 @@ import pystac.validation from pystac.cache import CollectionCache from pystac.serialization.common_properties import merge_common_properties -from pystac.validation import STACValidationError from tests.utils import TestCases @@ -64,10 +63,10 @@ def test_validate_examples(self): if valid: pystac.validation.validate_dict(stac_json) else: - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): try: pystac.validation.validate_dict(stac_json) - except STACValidationError as e: + except pystac.STACValidationError as e: self.assertIsInstance( e.source, jsonschema.ValidationError ) @@ -81,10 +80,10 @@ def test_validate_error_contains_href(self): item.geometry = {"type": "INVALID"} - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): try: item.validate() - except STACValidationError as e: + except pystac.STACValidationError as e: self.assertTrue(get_opt(item.get_self_href()) in str(e)) raise e @@ -122,7 +121,7 @@ def test_validate_all(self): stac_dict = pystac.StacIO.default().read_json(new_cat_href) - with self.assertRaises(STACValidationError): + with self.assertRaises(pystac.STACValidationError): pystac.validation.validate_all(stac_dict, new_cat_href) def test_validates_geojson_with_tuple_coordinates(self): From aa1cdf8d9f7c7282d84434a811a9cfb4bbb6ed34 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 13:09:06 -0400 Subject: [PATCH 42/51] Issue deprecation warnings for STAC_IO --- pystac/stac_io.py | 16 ++++++++++++++++ tests/test_stac_io.py | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/test_stac_io.py diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 60b4cfc51..94e391875 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -12,6 +12,7 @@ Type, Union, ) +import warnings from urllib.parse import urlparse from urllib.request import urlopen @@ -256,11 +257,21 @@ class STAC_IO: @staticmethod def read_text_method(uri: str) -> str: + warnings.warn( + "STAC_IO is deprecated. " + "Please use instances of StacIO (e.g. StacIO.default()).", + DeprecationWarning, + ) return StacIO.default().read_text(uri) @staticmethod def write_text_method(uri: str, txt: str) -> None: """Default method for writing text.""" + warnings.warn( + "STAC_IO is deprecated. " + "Please use instances of StacIO (e.g. StacIO.default()).", + DeprecationWarning, + ) return StacIO.default().write_text(uri, txt) @staticmethod @@ -269,6 +280,11 @@ def stac_object_from_dict( href: Optional[str] = None, root: Optional["Catalog_Type"] = None, ) -> "STACObject_Type": + warnings.warn( + "STAC_IO is deprecated. " + "Please use instances of StacIO (e.g. StacIO.default()).", + DeprecationWarning, + ) return pystac.serialization.stac_object_from_dict(d, href, root) # This is set in __init__.py diff --git a/tests/test_stac_io.py b/tests/test_stac_io.py new file mode 100644 index 000000000..b744ca4ee --- /dev/null +++ b/tests/test_stac_io.py @@ -0,0 +1,20 @@ +import unittest +import warnings + +from pystac.stac_io import STAC_IO +from tests.utils import TestCases + + +class StacIOTest(unittest.TestCase): + def test_stac_io_issues_warnings(self): + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + STAC_IO.read_text( + TestCases.get_path("data-files/collections/multi-extent.json") + ) + + # Verify some things + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) From 568058362891c38be4540c44c96d1657418b87a3 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 13:16:02 -0400 Subject: [PATCH 43/51] Fix stray docstring escape char breaking CI --- pystac/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pystac/catalog.py b/pystac/catalog.py index c5c58afd9..f0baa37b8 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -221,7 +221,7 @@ def add_child( self.add_link(Link.child(child, title=title)) def add_children(self, children: Iterable["Catalog"]) -> None: - """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection`\s. + """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection` objects. This method will set each child's parent to this object, and their root to this Catalog's root. From af2a6e495c140e1a77b8f23bc9c4ac2aa32aaf92 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 3 May 2021 13:42:37 -0400 Subject: [PATCH 44/51] Fix typo --- pystac/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pystac/asset.py b/pystac/asset.py index b34d25582..2f69f5d7c 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -84,7 +84,7 @@ def get_absolute_href(self) -> Optional[str]: href is relative). Returns: - str: The absolute HREF of this asset, or a relative HREF is an absolute HREF + str: The absolute HREF of this asset, or a relative HREF if an absolute HREF cannot be determined. """ if not is_absolute_href(self.href): From 3e219565e74ddf8b9b53cb5402b47ddba1663d15 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 10:00:20 -0400 Subject: [PATCH 45/51] Use Union[Catalog, Collection] as child type in Catalog. This makes it more explicit that children are either a Catalog or a Collection, even though in PySTAC a Collection inherits from a Catalog. --- pystac/catalog.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pystac/catalog.py b/pystac/catalog.py index f0baa37b8..9218cd9b1 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -27,6 +27,7 @@ if TYPE_CHECKING: from pystac.item import Asset as Asset_Type, Item as Item_Type + from pystac.collection import Collection as Collection_Type class CatalogType(str, Enum): @@ -187,7 +188,7 @@ def is_relative(self) -> bool: def add_child( self, - child: "Catalog", + child: Union["Catalog", "Collection_Type"], title: Optional[str] = None, strategy: Optional[HrefLayoutStrategy] = None, ) -> None: @@ -220,7 +221,9 @@ def add_child( self.add_link(Link.child(child, title=title)) - def add_children(self, children: Iterable["Catalog"]) -> None: + def add_children( + self, children: Iterable[Union["Catalog", "Collection_Type"]] + ) -> None: """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection` objects. This method will set each child's parent to this object, and their root to this Catalog's root. @@ -275,7 +278,9 @@ def add_items(self, items: Iterable["Item_Type"]) -> None: for item in items: self.add_item(item) - def get_child(self, id: str, recursive: bool = False) -> Optional["Catalog"]: + def get_child( + self, id: str, recursive: bool = False + ) -> Optional[Union["Catalog", "Collection_Type"]]: """Gets the child of this catalog with the given ID, if it exists. Args: @@ -285,7 +290,8 @@ def get_child(self, id: str, recursive: bool = False) -> Optional["Catalog"]: to False. Return: - Item or None: The item with the given ID, or None if not found. + Optional Catalog or Collection: The child with the given ID, + or None if not found. """ if not recursive: return next((c for c in self.get_children() if c.id == id), None) @@ -296,14 +302,17 @@ def get_child(self, id: str, recursive: bool = False) -> Optional["Catalog"]: return child return None - def get_children(self) -> Iterable["Catalog"]: + def get_children(self) -> Iterable[Union["Catalog", "Collection_Type"]]: """Return all children of this catalog. Return: - Iterable[Catalog]: Generator of children who's parent + Iterable[Catalog or Collection]: Iterable of children who's parent is this catalog. """ - return map(lambda x: cast(pystac.Catalog, x), self.get_stac_objects("child")) + return map( + lambda x: cast(Union[pystac.Catalog, pystac.Collection], x), + self.get_stac_objects("child"), + ) def get_child_links(self) -> List[Link]: """Return all child links of this catalog. From 62366861e66c05afddf920858fedd75c9341d5dd Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 10:06:19 -0400 Subject: [PATCH 46/51] Allow all `from_file` overrides to set a stac_io implementation --- pystac/catalog.py | 4 ++-- pystac/collection.py | 6 ++++-- pystac/item.py | 4 ++-- pystac/stac_object.py | 3 +-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pystac/catalog.py b/pystac/catalog.py index 9218cd9b1..428f9c84c 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -939,8 +939,8 @@ def full_copy( return cast(Catalog, super().full_copy(root, parent)) @classmethod - def from_file(cls, href: str) -> "Catalog": - result = super().from_file(href) + def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Catalog": + result = super().from_file(href, stac_io) if not isinstance(result, Catalog): raise pystac.STACTypeError(f"{result} is not a {Catalog}.") return result diff --git a/pystac/collection.py b/pystac/collection.py index 89530eb02..0dafe09b7 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -749,8 +749,10 @@ def full_copy( return cast(Collection, super().full_copy(root, parent)) @classmethod - def from_file(cls, href: str) -> "Collection": - result = super().from_file(href) + def from_file( + cls, href: str, stac_io: Optional[pystac.StacIO] = None + ) -> "Collection": + result = super().from_file(href, stac_io) if not isinstance(result, Collection): raise pystac.STACTypeError(f"{result} is not a {Collection}.") return result diff --git a/pystac/item.py b/pystac/item.py index a7bb572b5..2b319d028 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -973,8 +973,8 @@ def full_copy( return cast(Item, super().full_copy(root, parent)) @classmethod - def from_file(cls, href: str) -> "Item": - result = super().from_file(href) + def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Item": + result = super().from_file(href, stac_io) if not isinstance(result, Item): raise pystac.STACTypeError(f"{result} is not a {Item}.") return result diff --git a/pystac/stac_object.py b/pystac/stac_object.py index b0a7088bc..8a303a665 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -453,9 +453,8 @@ def from_file( if not is_absolute_href(href): href = make_absolute_href(href) - d = stac_io.read_json(href) - o = stac_io.stac_object_from_dict(d, href, None) + o = stac_io.read_stac_object(href) # Set the self HREF, if it's not already set to something else. if o.get_self_href() is None: From 20afd2cb914b3b3be2106440fd040ef4411919cf Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 10:10:58 -0400 Subject: [PATCH 47/51] Asset.get_absoulte_href returns None if no abs href possible --- pystac/asset.py | 17 +++++++++-------- pystac/utils.py | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index 2f69f5d7c..7c8353df3 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -79,19 +79,20 @@ def set_owner(self, obj: Union["Collection_Type", "Item_Type"]) -> None: def get_absolute_href(self) -> Optional[str]: """Gets the absolute href for this asset, if possible. - If this Asset has no associated Item, this will return whatever the - href is (as it cannot determine the absolute path, if the asset - href is relative). + If this Asset has no associated Item, and the asset HREF is a relative path, + this method will return None. Returns: - str: The absolute HREF of this asset, or a relative HREF if an absolute HREF - cannot be determined. + str: The absolute HREF of this asset, or None if an absolute HREF could not + be determined. """ - if not is_absolute_href(self.href): + if is_absolute_href(self.href): + return self.href + else: if self.owner is not None: return make_absolute_href(self.href, self.owner.get_self_href()) - - return self.href + else: + return None def to_dict(self) -> Dict[str, Any]: """Generate a dictionary representing the JSON of this Asset. diff --git a/pystac/utils.py b/pystac/utils.py index 875cea924..d1c9a78af 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -100,8 +100,7 @@ def make_absolute_href( Returns: str: The absolute HREF. If the source_href is already an absolute href, - then it will be returned unchanged. If the source_href it None, it will - return None. + then it will be returned unchanged. """ if start_href is None: start_href = os.getcwd() From 9d1ffee65cb308688324c26bfcb4cf6712f660a8 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 11 Mar 2021 10:18:09 -0500 Subject: [PATCH 48/51] Add test files for 1.0.0-RC3 --- tests/data-files/examples/example-info.csv | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index 712c14d67..3b664cc23 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -135,4 +135,12 @@ "sentinel-0.6.0/sentinel-2-l1c/9/catalog.json","CATALOG","0.6.0","" "sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","" "hand-0.9.0/collection.json","COLLECTION","0.9.0","" -"hand-0.8.1/collection.json","COLLECTION","0.8.1","" \ No newline at end of file +"hand-0.8.1/collection.json","COLLECTION","0.8.1","" +"1.0.0-RC1/catalog.json","CATALOG","1.0.0-rc.1","" +"1.0.0-RC1/collection-only/collection.json","COLLECTION","1.0.0-rc.1","" +"1.0.0-RC1/collection.json","COLLECTION","1.0.0-rc.1","" +"1.0.0-RC1/collectionless-item.json","COLLECTION","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" +"1.0.0-RC1/core-item.json","ITEM","1.0.0-rc.1","" +"1.0.0-RC1/extended-item.json","ITEM","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/scientific/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" +"1.0.0-RC1/extensions-collection/collection.json","COLLECTION","1.0.0-rc.1","" +"1.0.0-RC1/extensions-collection/proj-example/proj-example.json","ITEM","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json" From 4e99310e841094b250eeaef4b6352c8f28eb0849 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 12:12:12 -0400 Subject: [PATCH 49/51] Ensure stac_extensions is a list for consistency --- pystac/serialization/migrate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index a007db170..79b5be6a6 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -202,6 +202,10 @@ def migrate_to_latest( migration_fn(result, version, info) result["stac_extensions"].remove(ext) - result["stac_version"] = STACVersion.DEFAULT_STAC_VERSION + result["stac_version"] = STACVersion.DEFAULT_STAC_VERSION + else: + # Ensure stac_extensions property for consistency + if "stac_extensions" not in result: + result["stac_extensions"] = [] return result From c71a716e01b93ce9d5141b3da736ec2280ed5c69 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 12:12:29 -0400 Subject: [PATCH 50/51] Add 1.0.0-RC3 examples --- tests/data-files/examples/1.0.0-RC3/README.md | 91 ++++++ .../examples/1.0.0-RC3/catalog.json | 42 +++ .../collection-with-schemas.json | 273 +++++++++++++++++ .../1.0.0-RC3/collection-only/collection.json | 229 ++++++++++++++ .../examples/1.0.0-RC3/collection.json | 110 +++++++ .../1.0.0-RC3/collectionless-item.json | 140 +++++++++ .../examples/1.0.0-RC3/core-item.json | 122 ++++++++ .../examples/1.0.0-RC3/extended-item.json | 196 ++++++++++++ .../extensions-collection/collection.json | 66 ++++ .../proj-example/proj-example.json | 282 ++++++++++++++++++ .../examples/1.0.0-RC3/simple-item.json | 79 +++++ tests/data-files/examples/example-info.csv | 16 +- 12 files changed, 1638 insertions(+), 8 deletions(-) create mode 100644 tests/data-files/examples/1.0.0-RC3/README.md create mode 100644 tests/data-files/examples/1.0.0-RC3/catalog.json create mode 100644 tests/data-files/examples/1.0.0-RC3/collection-only/collection-with-schemas.json create mode 100644 tests/data-files/examples/1.0.0-RC3/collection-only/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC3/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC3/collectionless-item.json create mode 100644 tests/data-files/examples/1.0.0-RC3/core-item.json create mode 100644 tests/data-files/examples/1.0.0-RC3/extended-item.json create mode 100644 tests/data-files/examples/1.0.0-RC3/extensions-collection/collection.json create mode 100644 tests/data-files/examples/1.0.0-RC3/extensions-collection/proj-example/proj-example.json create mode 100644 tests/data-files/examples/1.0.0-RC3/simple-item.json diff --git a/tests/data-files/examples/1.0.0-RC3/README.md b/tests/data-files/examples/1.0.0-RC3/README.md new file mode 100644 index 000000000..a4d046a94 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/README.md @@ -0,0 +1,91 @@ +# STAC Examples + +This directory contains various examples for all parts of the STAC specification. +It is structured to be two valid STACs, meaning both [catalog.json](catalog.json) and [collection.json](collection.json) +should successfully load in various tools. They do not follow *all* the [best practices](../best-practices.md) for STAC, mostly +due to the fact that they contrive examples to show the spec and we are hosting in GitHub. But we note below where they differ from an ideal catalog. + +The various fields are mostly fictional, to be able to demonstrate the various aspects of the spec as tersely as possible. To get a sense +of real world STAC implementations we recommend exploring the [stac-examples](http://github.com/stac-utils/stac-examples) repo, which +gathers in one place copies of STAC [Items](../item-spec/item-spec.md) and [Collection](../collection-spec/collection-spec.md) +from a number of different production catalogs that all follow good STAC practices. And you should also explore the various catalogs +listed on [STAC Index](http://stacindex.org), to see full catalogs in production. + +## Organization + +This directory contains two STAC implementations, both valid, but simplified a bit to be illustrative of the key concepts, so +they do not quite follow all the best practices. + +### Simple Collection + +This STAC implementation consists of three files, all contained at the root of the examples directory + +**[collection.json](collection.json)** is a minimal 'simple collection', that links to three items. + +**[simple-item.json](simple-item.json)** is the most minimal possible compliant Item record. Most all data will +include additional fields, as STAC is designed to be a minimal common subset. But it is useful for showing exactly what is +required. + +**[core-item.json](core-item.json)** is a more realistic example, for a hypothetical analytic image +acquisition from a satellite company called 'Remote Data'. It includes additional fields covering the [common +metadata](../item-spec/common-metadata.md). It also links to a variety of assets that is typical for +satellite imagery, as most providers include a number of complementary files. + +**[extended-item.json](extended-item.json)** is arguably an even more realistic example, as it includes a number of the +[extensions](../extensions/) that are commonly used, to demonstrate how implementations tend to start with the core, and add in +a number of the core extensions. + +**[collectionless-item.json](collectionless-item.json)** demonstrates the common metadata that is only used when an Item does not have +a collection. It is recommended to organize items in collections, but we wanted to show how this works. This is not technically in the +'simple collection' of this section, but it follows the same pattern, so is included here. + +### Nested Catalog + +This STAC implementation shows a common pattern, starting with a catalog that links to a number of distinct collections, which may +link down to a number of items. + +**[catalog.json](catalog.json)** is a minimal catalog implementation, linking to two other collections. + +**[collection-only/collection.json](collection-only/collection.json)** is a collection that does not link to any items. This +demonstrates how is is possible to make use of STAC Collections without needing items, to serve as nice summarizing metadata for +tools that work with full layers / collections. This example collection is based on real Sentinel-2 values, so is not quite fictional, +but should be taken as just an example. + +**[extensions-collection/collection.json](extensions-collection/collection.json)** contains a small number of items, that demonstrate +more functionality available in STAC [extensions](../extensions/). These are linked to directly from the individual extensions. These +items follow the recommendations for [Catalog Layout Best Practices](../best-practices.md#catalog-layout). + +## In Depth + +As mentioned above, the files in this examples directory form valid STAC implementations. They are all based on a +fictional remote sensing company called 'Remote Data', with a URL at remotedata.io. This domain has not been set up, so those links +will not work, but any valid data provider should provide valid links to their homepage. + +The examples use the `rd:` prefix to show how providers can use custom fields when there are not set fields. In the examples these +do not link to a schema which is completely valid, but it is recommended that providers do write a JSON schema that can validate +their custom fields (we will work to add an example schema for the `rd:` fields in the future). + +### Catalog Type + +One of the most important STAC Best Practices is to [use links consistently](../best-practices.md#use-of-links), following one of the +described 'catalog types'. The catalogs described here are [Relative Published Catalogs](../best-practices.md#relative-published-catalog), +that use absolute URL's to refer to their assets (so would be an example of a [Self-contained Metadata +Only](../best-practices.md#self-contained-metadata-only) catalog that is published). + +### Differences with STAC Best Practices + +One of the most important documents in this repository is the one about [best practices](../best-practices.md). It describes a number +of practical recommendations gained by people actually implementing STAC. The core spec is designed to be as flexible as possible, so +that it is not too rigid and unable to handle unanticipated needs. But we recommend following as many of the best practices as is +feasible, as it will help ensure various STAC tools work much better. The examples in this folder don't align with all the best +practices, mostly because they are meant to demonstrate things as tersely as possible, and also because they live directly inside +a github repository. As many people will look at these examples and take them as 'how things should be' we felt its important to +highlight where things here differ from the actual best practices. + +#### Catalog Layout + +Another important recommendations concerns the [layout of STAC catalogs](../best-practices.md#catalog-layout). This is important +for tools to be able to expect a certain layout, and most tools will follow the described layout. The simple collection that consists +of the collection.json and its 3 linked items violates this. This is done to be able to show item examples directly in the root of +the 'examples' folder, so people don't have to dig deep into folders to get a quick example. But a proper catalog layout would +put the items in sub-directories, along with their assets. diff --git a/tests/data-files/examples/1.0.0-RC3/catalog.json b/tests/data-files/examples/1.0.0-RC3/catalog.json new file mode 100644 index 000000000..1003b20b7 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/catalog.json @@ -0,0 +1,42 @@ +{ + "id": "examples", + "type": "Catalog", + "stac_version": "1.0.0-rc.3", + "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.", + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./extensions-collection/collection.json", + "type": "application/json", + "title": "Collection Demonstrating STAC Extensions" + }, + { + "rel": "child", + "href": "./collection-only/collection.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "child", + "href": "./collection-only/collection-with-schemas.json", + "type": "application/json", + "title": "Collection with no items (standalone with JSON Schemas)" + }, + { + "rel": "item", + "href": "./collectionless-item.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0-rc.3/examples/catalog.json", + "type": "application/json" + } + ] +} diff --git a/tests/data-files/examples/1.0.0-RC3/collection-only/collection-with-schemas.json b/tests/data-files/examples/1.0.0-RC3/collection-only/collection-with-schemas.json new file mode 100644 index 000000000..1cddc65c7 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/collection-only/collection-with-schemas.json @@ -0,0 +1,273 @@ +{ + "stac_version": "1.0.0-rc.3", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "type": "Collection", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-2A", + "description": "The SENTINEL-2 mission is a land monitoring constellation of two satellites each equipped with a MSI (Multispectral Imager) instrument covering 13 spectral bands providing high resolution optical imagery (i.e., 10m, 20m, 60 m) every 10 days with one satellite and 5 days with two satellites", + "license": "proprietary", + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -82.852377834669, + 180, + 82.819463367711 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2017-04-12T02:57:21.459000Z", + "2021-04-22T11:30:12.767000Z" + ] + ] + } + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json" + }, + { + "rel": "root", + "href": "../catalog.json" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + }, + { + "name": "AWS", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/sentinel-2/" + }, + { + "name": "jeobrowser", + "roles": [ + "processor" + ], + "url": "https://github.com/jjrom/resto" + } + ], + "summaries": { + "datetime": { + "minimum": "2017-04-12T02:57:21Z", + "maximum": "2021-04-22T11:30:12Z" + }, + "instruments": { + "type": "string", + "const": "msi", + "title": "Multispectral Intrument", + "count": 6613431 + }, + "resto:landcover": { + "type": "string", + "oneOf": [ + { + "const": "cultivated", + "title": "Cultivated", + "count": 490750 + }, + { + "const": "desert", + "title": "Desert", + "count": 543120 + }, + { + "const": "flooded", + "title": "Flooded", + "count": 5187 + }, + { + "const": "forest", + "title": "Forest", + "count": 767807 + }, + { + "const": "herbaceous", + "title": "Herbaceous", + "count": 674281 + }, + { + "const": "ice", + "title": "Ice", + "count": 231285 + }, + { + "const": "urban", + "title": "Urban", + "count": 1219 + }, + { + "const": "water", + "title": "Water", + "count": 2303314 + } + ] + }, + "resto:location": { + "type": "string", + "oneOf": [ + { + "const": "tropical", + "title": "Tropical", + "count": 1807474 + }, + { + "const": "southern", + "title": "Southern", + "count": 1671685 + }, + { + "const": "northern", + "title": "Northern", + "count": 4876669 + }, + { + "const": "equatorial", + "title": "Equatorial", + "count": 27302 + }, + { + "const": "coastal", + "title": "Coastal", + "count": 1495516 + } + ] + }, + "platform": { + "type": "string", + "oneOf": [ + { + "const": "sentinel-2b", + "title": "Sentinel 2B", + "count": 3495597 + }, + { + "const": "sentinel-2a", + "title": "Sentinel 2A", + "count": 3117831 + } + ] + }, + "resto:season": { + "type": "integer", + "oneOf": [ + { + "const": 0, + "title": "Winter", + "count": 1621108 + }, + { + "const": 2, + "title": "Summer", + "count": 2279472 + }, + { + "const": 1, + "title": "Spring", + "count": 1577067 + }, + { + "const": 3, + "title": "Autumn", + "count": 1098015 + } + ] + }, + "eo:bands": [ + { + "title": "B1", + "common_name": "coastal", + "center_wavelength": 4.439, + "gsd": 60 + }, + { + "title": "B2", + "common_name": "blue", + "center_wavelength": 4.966, + "gsd": 10 + }, + { + "title": "B3", + "common_name": "green", + "center_wavelength": 5.6, + "gsd": 10 + }, + { + "title": "B4", + "common_name": "red", + "center_wavelength": 6.645, + "gsd": 10 + }, + { + "title": "B5", + "center_wavelength": 7.039, + "gsd": 20 + }, + { + "title": "B6", + "center_wavelength": 7.402, + "gsd": 20 + }, + { + "title": "B7", + "center_wavelength": 7.825, + "gsd": 20 + }, + { + "title": "B8", + "common_name": "nir", + "center_wavelength": 8.351, + "gsd": 10 + }, + { + "title": "B8A", + "center_wavelength": 8.648, + "gsd": 20 + }, + { + "title": "B9", + "center_wavelength": 9.45, + "gsd": 60 + }, + { + "title": "B10", + "center_wavelength": 1.3735, + "gsd": 60 + }, + { + "title": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "gsd": 20 + }, + { + "title": "B12", + "common_name": "swir22", + "center_wavelength": 2.2024, + "gsd": 20 + } + ] + } +} \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0-RC3/collection-only/collection.json b/tests/data-files/examples/1.0.0-RC3/collection-only/collection.json new file mode 100644 index 000000000..c5cb7a2cc --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/collection-only/collection.json @@ -0,0 +1,229 @@ +{ + "type": "Collection", + "stac_version": "1.0.0-rc.3", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-1C", + "description": "Sentinel-2 is a wide-swath, high-resolution, multi-spectral\nimaging mission supporting Copernicus Land Monitoring studies,\nincluding the monitoring of vegetation, soil and water cover,\nas well as observation of inland waterways and coastal areas.\n\nThe Sentinel-2 data contain 13 UINT16 spectral bands representing\nTOA reflectance scaled by 10000. See the [Sentinel-2 User Handbook](https://sentinel.esa.int/documents/247904/685211/Sentinel-2_User_Handbook)\nfor details. In addition, three QA bands are present where one\n(QA60) is a bitmask band with cloud mask information. For more\ndetails, [see the full explanation of how cloud masks are computed.](https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-1c/cloud-masks)\n\nEach Sentinel-2 product (zip archive) may contain multiple\ngranules. Each granule becomes a separate Earth Engine asset.\nEE asset ids for Sentinel-2 assets have the following format:\nCOPERNICUS/S2/20151128T002653_20151128T102149_T56MNN. Here the\nfirst numeric part represents the sensing date and time, the\nsecond numeric part represents the product generation date and\ntime, and the final 6-character string is a unique granule identifier\nindicating its UTM grid reference (see [MGRS](https://en.wikipedia.org/wiki/Military_Grid_Reference_System)).\n\nFor more details on Sentinel-2 radiometric resoltuon, [see this page](https://earth.esa.int/web/sentinel/user-guides/sentinel-2-msi/resolutions/radiometric).\n", + "license": "proprietary", + "keywords": [ + "copernicus", + "esa", + "eu", + "msi", + "radiance", + "sentinel" + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2015-06-23T00:00:00Z", + null + ] + ] + } + }, + "assets": { + "metadata_iso_19139": { + "roles": [ + "metadata", + "iso-19139" + ], + "href": "https://storage.googleapis.com/open-cogs/stac-examples/sentinel-2-iso-19139.xml", + "title": "ISO 19139 metadata", + "type": "application/vnd.iso.19139+xml" + } + }, + "summaries": { + "datetime": { + "minimum": "2015-06-23T00:00:00Z", + "maximum": "2019-07-10T13:44:56Z" + }, + "platform": [ + "sentinel-2a", + "sentinel-2b" + ], + "constellation": [ + "sentinel-2" + ], + "instruments": [ + "msi" + ], + "view:off_nadir": { + "minimum": 0, + "maximum": 100 + }, + "view:sun_elevation": { + "minimum": 6.78, + "maximum": 89.9 + }, + "gsd": [ + 10, + 30, + 60 + ], + "proj:epsg": [ + 32601, + 32602, + 32603, + 32604, + 32605, + 32606, + 32607, + 32608, + 32609, + 32610, + 32611, + 32612, + 32613, + 32614, + 32615, + 32616, + 32617, + 32618, + 32619, + 32620, + 32621, + 32622, + 32623, + 32624, + 32625, + 32626, + 32627, + 32628, + 32629, + 32630, + 32631, + 32632, + 32633, + 32634, + 32635, + 32636, + 32637, + 32638, + 32639, + 32640, + 32641, + 32642, + 32643, + 32644, + 32645, + 32646, + 32647, + 32648, + 32649, + 32650, + 32651, + 32652, + 32653, + 32654, + 32655, + 32656, + 32657, + 32658, + 32659, + 32660 + ], + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 4.439 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 4.966 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 5.6 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 6.645 + }, + { + "name": "B5", + "center_wavelength": 7.039 + }, + { + "name": "B6", + "center_wavelength": 7.402 + }, + { + "name": "B7", + "center_wavelength": 7.825 + }, + { + "name": "B8", + "common_name": "nir", + "center_wavelength": 8.351 + }, + { + "name": "B8A", + "center_wavelength": 8.648 + }, + { + "name": "B9", + "center_wavelength": 9.45 + }, + { + "name": "B10", + "center_wavelength": 1.3735 + }, + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137 + }, + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.2024 + } + ] + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json" + }, + { + "rel": "root", + "href": "../catalog.json" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ] +} diff --git a/tests/data-files/examples/1.0.0-RC3/collection.json b/tests/data-files/examples/1.0.0-RC3/collection.json new file mode 100644 index 000000000..790bcf8da --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/collection.json @@ -0,0 +1,110 @@ +{ + "id": "simple-collection", + "type": "Collection", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "stac_version": "1.0.0-rc.3", + "description": "A simple collection demonstrating core catalog fields with links to a couple of items", + "title": "Simple Example Collection", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "summaries": { + "platform": [ + "cool_sat1", + "cool_sat2" + ], + "constellation": [ + "ion" + ], + "instruments": [ + "cool_sensor_v1", + "cool_sensor_v2" + ], + "gsd": { + "minimum": 0.512, + "maximum": 0.66 + }, + "eo:cloud_cover": { + "minimum": 1.2, + "maximum": 1.2 + }, + "proj:epsg": { + "minimum": 32659, + "maximum": 32659 + }, + "view:sun_elevation": { + "minimum": 54.9, + "maximum": 54.9 + }, + "view:off_nadir": { + "minimum": 3.8, + "maximum": 3.8 + }, + "view:sun_azimuth": { + "minimum": 135.7, + "maximum": 135.7 + } + }, + "links": [ + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./simple-item.json", + "type": "application/geo+json", + "title": "Simple Item" + }, + { + "rel": "item", + "href": "./core-item.json", + "type": "application/geo+json", + "title": "Core Item" + }, + { + "rel": "item", + "href": "./extended-item.json", + "type": "application/geo+json", + "title": "Extended Item" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0-rc.3/examples/collection.json", + "type": "application/json" + } + ] +} diff --git a/tests/data-files/examples/1.0.0-RC3/collectionless-item.json b/tests/data-files/examples/1.0.0-RC3/collectionless-item.json new file mode 100644 index 000000000..c4b224d5e --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/collectionless-item.json @@ -0,0 +1,140 @@ +{ + "stac_version": "1.0.0-rc.3", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "CS3-20160503_132131_08", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.2880486, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "title": "Full Item", + "description": "A sample STAC Item demonstrates an Item that does not have a collection, which is not recommended, but allowed by the spec.", + "datetime": null, + "start_datetime": "2016-05-03T13:22:30Z", + "end_datetime": "2016-05-03T13:27:30Z", + "created": "2016-05-04T00:00:01Z", + "updated": "2017-01-01T00:30:55Z", + "license": "various", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.it" + } + ], + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "view:sun_elevation": 33.4, + "gsd": 0.512, + "cs:type": "scene", + "cs:anomalous_pixels": 0.14, + "cs:earth_sun_distance": 1.014156, + "cs:sat_id": "CS3", + "cs:product_level": "LV1B" + }, + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html" + }, + { + "rel": "license", + "type": "text/html", + "href": "http://remotedata.io/license.html" + } + ], + "assets": { + "analytic": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", + "title": "4-Band Analytic", + "eo:bands": [ + { + "name": "band1" + }, + { + "name": "band1" + }, + { + "name": "band2" + }, + { + "name": "band3" + } + ] + }, + "thumbnail": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "udm": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/UDM.tif", + "title": "Unusable Data Mask" + }, + "json-metadata": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/S3-20160503_132130_04.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/1.0.0-RC3/core-item.json b/tests/data-files/examples/1.0.0-RC3/core-item.json new file mode 100644 index 000000000..dc95ff807 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/core-item.json @@ -0,0 +1,122 @@ +{ + "stac_version": "1.0.0-rc.3", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat1", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/1.0.0-RC3/extended-item.json b/tests/data-files/examples/1.0.0-RC3/extended-item.json new file mode 100644 index 000000000..85b3f5987 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/extended-item.json @@ -0,0 +1,196 @@ +{ + "stac_version": "1.0.0-rc.3", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/remote-data/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Extended Item", + "description": "A sample STAC Item that includes a variety of examples from the stable extensions", + "datetime": "2020-12-14T18:02:31.437000Z", + "created": "2020-12-15T01:48:13.725Z", + "updated": "2020-12-15T01:48:13.725Z", + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v2" + ], + "gsd": 0.66, + "eo:cloud_cover": 1.2, + "proj:epsg": 32659, + "proj:shape": [ + 5558, + 9559 + ], + "proj:transform": [ + 0.5, + 0, + 712710, + 0, + -0.5, + 151406, + 0, + 0, + 1 + ], + "view:sun_elevation": 54.9, + "view:off_nadir": 3.8, + "view:sun_azimuth": 135.7, + "rd:type": "scene", + "rd:anomalous_pixels": 0.14, + "rd:earth_sun_distance": 1.014156, + "rd:sat_id": "cool_sat2", + "rd:product_level": "LV3A", + "sci:doi": "10.5061/dryad.s2v81.2/27.2" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ], + "eo:bands": [ + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band4", + "common_name": "nir", + "center_wavelength": 800, + "full_width_half_max": 152 + } + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ], + "eo:bands": [ + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + } + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/1.0.0-RC3/extensions-collection/collection.json b/tests/data-files/examples/1.0.0-RC3/extensions-collection/collection.json new file mode 100644 index 000000000..fa0fe85db --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/extensions-collection/collection.json @@ -0,0 +1,66 @@ +{ + "id": "extensions-collection", + "type": "Collection", + "stac_version": "1.0.0-rc.3", + "description": "A heterogenous collection containing deeper examples of various extensions", + "links": [ + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json" + }, + { + "rel": "item", + "href": "./proj-example/proj-example.json", + "title": "Proj extension example" + }, + { + "rel": "license", + "href": "https://remotedata.io/license.html", + "title": "Remote Data License Terms" + }, + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json" + } + ], + "stac_extensions": [], + "title": "Collection of Extension Items", + "keywords": [ + "examples", + "sar", + "projection" + ], + "providers": [ + { + "name": "Remote Data, Inc.", + "roles": [ + "producer", + "licensor" + ], + "url": "https://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2009-05-20T02:40:01.042784Z", + "2018-11-03T23:59:55.112875Z" + ] + ] + } + }, + "license": "PDDL-1.0" +} diff --git a/tests/data-files/examples/1.0.0-RC3/extensions-collection/proj-example/proj-example.json b/tests/data-files/examples/1.0.0-RC3/extensions-collection/proj-example/proj-example.json new file mode 100644 index 000000000..64a42dc14 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/extensions-collection/proj-example/proj-example.json @@ -0,0 +1,282 @@ +{ + "type": "Feature", + "stac_version": "1.0.0-rc.3", + "id": "proj-example", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "proj:epsg": 32614, + "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "proj:projjson": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 14N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "conversion": { + "name": "UTM zone 14N", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": -99, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 0.9996, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 500000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "area": "World - N hemisphere - 102°W to 96°W - by country", + "bbox": { + "south_latitude": 0, + "west_longitude": -102, + "north_latitude": 84, + "east_longitude": -96 + }, + "id": { + "authority": "EPSG", + "code": 32614 + } + }, + "proj:geometry": { + "coordinates": [ + [ + [ + 169200, + 3712800 + ], + [ + 403200, + 3712800 + ], + [ + 403200, + 3951000 + ], + [ + 169200, + 3951000 + ], + [ + 169200, + 3712800 + ] + ] + ], + "type": "Polygon" + }, + "proj:bbox": [ + 169200, + 3712800, + 403200, + 3951000 + ], + "proj:centroid": { + "lat": 34.595302781575604, + "lon": -101.34448382627504 + }, + "proj:shape": [ + 8391, + 8311 + ], + "proj:transform": [ + 30, + 0, + 224985, + 0, + -30, + 6790215, + 0, + 0, + 1 + ] + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 + ] + ] + ] + }, + "links": [ + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json" + } + ], + "assets": { + "B1": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + } + ] + }, + "B8": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 8 (panchromatic)", + "eo:bands": [ + { + "name": "B8", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + } + ], + "proj:shape": [ + 16781, + 16621 + ], + "proj:transform": [ + 15, + 0, + 224992.5, + 0, + -15, + 6790207.5, + 0, + 0, + 1 + ] + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" + ], + "collection": "landsat-8-l1" +} diff --git a/tests/data-files/examples/1.0.0-RC3/simple-item.json b/tests/data-files/examples/1.0.0-RC3/simple-item.json new file mode 100644 index 000000000..f74871fb3 --- /dev/null +++ b/tests/data-files/examples/1.0.0-RC3/simple-item.json @@ -0,0 +1,79 @@ +{ + "stac_version": "1.0.0-rc.3", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "datetime": "2020-12-11T22:38:32.125000Z" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json" + } + ], + "assets": { + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/jpeg", + "roles": [ + "thumbnail" + ] + } + } +} diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index 3b664cc23..b01726a7e 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -136,11 +136,11 @@ "sentinel-0.6.0/sentinel-2-l1c/catalog.json","COLLECTION","0.6.0","" "hand-0.9.0/collection.json","COLLECTION","0.9.0","" "hand-0.8.1/collection.json","COLLECTION","0.8.1","" -"1.0.0-RC1/catalog.json","CATALOG","1.0.0-rc.1","" -"1.0.0-RC1/collection-only/collection.json","COLLECTION","1.0.0-rc.1","" -"1.0.0-RC1/collection.json","COLLECTION","1.0.0-rc.1","" -"1.0.0-RC1/collectionless-item.json","COLLECTION","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" -"1.0.0-RC1/core-item.json","ITEM","1.0.0-rc.1","" -"1.0.0-RC1/extended-item.json","ITEM","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/scientific/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" -"1.0.0-RC1/extensions-collection/collection.json","COLLECTION","1.0.0-rc.1","" -"1.0.0-RC1/extensions-collection/proj-example/proj-example.json","ITEM","1.0.0-rc.1","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json" +"1.0.0-RC3/catalog.json","CATALOG","1.0.0-rc.3","" +"1.0.0-RC3/collection-only/collection.json","COLLECTION","1.0.0-rc.3","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" +"1.0.0-RC3/collection.json","COLLECTION","1.0.0-rc.3","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" +"1.0.0-RC3/collectionless-item.json","ITEM","1.0.0-rc.3","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" +"1.0.0-RC3/core-item.json","ITEM","1.0.0-rc.3","" +"1.0.0-RC3/extended-item.json","ITEM","1.0.0-rc.3","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/scientific/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json|https://stac-extensions.github.io/remote-data/v1.0.0/schema.json" +"1.0.0-RC3/extensions-collection/collection.json","COLLECTION","1.0.0-rc.3","" +"1.0.0-RC3/extensions-collection/proj-example/proj-example.json","ITEM","1.0.0-rc.3","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json" From f6ee84194cff92a53a98a6e6bebc4a7acf34a0c1 Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Tue, 4 May 2021 18:06:19 -0400 Subject: [PATCH 51/51] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d6d1eef6..3046532d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,26 @@ ### Added +- Added type annotations across the library ([#309](https://github.com/stac-utils/pystac/pull/309)) +- Added assets to collections ([#309](https://github.com/stac-utils/pystac/pull/309)) +- `item_assets` extension ([#309](https://github.com/stac-utils/pystac/pull/309)) +- `datacube` extension ([#309](https://github.com/stac-utils/pystac/pull/309)) +- Added specific errors: `ExtensionAlreadyExistsError`, `ExtensionTypeError`, and `RequiredPropertyMissing`; moved custom exceptions to `pystac.errors` ([#309](https://github.com/stac-utils/pystac/pull/309)) + ### Fixed ### Changed +- API change: The extension API changed significantly. See ([#309](https://github.com/stac-utils/pystac/pull/309)) for more details. +- API change: Refactored the global STAC_IO object to an instance-specific `StacIO` implementation. STAC_IO is deprecated and will be removed next release. ([#309](https://github.com/stac-utils/pystac/pull/309)) +- Asset.get_absolute_href returns None if no absolute href can be inferred (previously the relative href that was passed in was returned) ([#309](https://github.com/stac-utils/pystac/pull/309)) + ### Removed +- Removed `properties` from Collections ([#309](https://github.com/stac-utils/pystac/pull/309)) +- Removed `LinkMixin`, and implemented those methods on `STACObject` directly. STACObject was the only class using LinkMixin and this should not effect users ([#309](https://github.com/stac-utils/pystac/pull/309) +- Removed `single-file-stac` extension; this extension is being removed in favor of ItemCollection usage ([#309](https://github.com/stac-utils/pystac/pull/309) + ## [v0.5.6] ### Added