From f04cb28a768fb1eec7f49a3ebe2f95fee161f408 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Sat, 2 Jan 2021 15:50:25 +0100 Subject: [PATCH 01/61] Fix links --- core/README.md | 2 +- item-search/README.md | 2 +- ogcapi-features/README.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/README.md b/core/README.md index 488ff18f..ee2ebbea 100644 --- a/core/README.md +++ b/core/README.md @@ -1,6 +1,6 @@ # STAC API - Core Specification -- **Conformance URI:** ([rendered version](https://api.stacspec.org/spec/v1.0.0-beta.1/core)) +- **Conformance URI:** ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)) - **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Dependencies**: None diff --git a/item-search/README.md b/item-search/README.md index ce6037e7..295d799c 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -1,6 +1,6 @@ # STAC API - Item Search -- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/spec/v1.0.0-beta.1/item-search)) +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/item-search)) - **Conformance URI:** - **Dependencies**: [STAC API - Core](../core) - **Examples**: [examples.md](examples.md) diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 66f27ed2..357f9853 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -2,10 +2,10 @@ *based on [**OGC API - Features - Part 1: Core**](https://www.ogc.org/standards/ogcapi-features)* -- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/spec/v1.0.0-beta.1/ogcapi-features)) +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features)) uses all the OGC API - Features openapi fragments to describe returning STAC Items. - **Conformance URIs:** - - + - - - [Requirements Class Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#rc_core)) - - [Requirements Class OpenAPI 3.0](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#rc_oas30)) - - [Requirements Class GeoJSON](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_requirements_class_geojson)) From a77a0ff1e808e2f591c05d55b47b68235ba443ea Mon Sep 17 00:00:00 2001 From: philvarner Date: Tue, 16 Mar 2021 21:43:47 -0400 Subject: [PATCH 02/61] make explicit that / returns a Catalog; add 'type' field to Catalog definitions --- core/commons.yaml | 3 +++ core/openapi.yaml | 1 + ogcapi-features/README.md | 18 +++++++++--------- ogcapi-features/openapi.yaml | 2 ++ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/core/commons.yaml b/core/commons.yaml index 42f5835d..e64b6b5c 100644 --- a/core/commons.yaml +++ b/core/commons.yaml @@ -42,6 +42,7 @@ components: type: object required: - stac_version + - type - id - description - links @@ -50,6 +51,8 @@ components: $ref: '#/components/schemas/stac_version' stac_extensions: $ref: '#/components/schemas/stac_extensions' + type: + type: string id: type: string title: diff --git a/core/openapi.yaml b/core/openapi.yaml index ed82f2cc..688337b8 100644 --- a/core/openapi.yaml +++ b/core/openapi.yaml @@ -57,6 +57,7 @@ components: schema: $ref: '#/components/schemas/landingPage' example: + type: Catalog stac_version: 1.0.0-beta.1 id: sentinel title: Copernicus Sentinel Imagery diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 357f9853..7eac34b8 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -20,7 +20,7 @@ core. So the full conformance class list is in the following table. Note that implementing OGC API - Features does not actually depend on [STAC API - Core](../core), but we include it as a dependency since this extension discusses using it in the context of STAC. One could implement an OAFeat service, returning STAC -[Items](../stac-spec/item-spec/README.md) and [Collections](../stac-spec/collection-spec/README.md) from their endpoints, and it will work +[Item](../stac-spec/item-spec/README.md) and [Collection](../stac-spec/collection-spec/README.md) objects from their endpoints, and it will work with OAFeat clients. But specialized STAC clients will likely display results better, and depend on the STAC landing page. ## Endpoints @@ -30,12 +30,12 @@ The core OGC API - Features endpoints are shown below, with details provided in | Endpoint | Returns | Description | | ----------------------------------------------- | ---------------- | ----------- | -| `/` | JSON | Landing page, links to API capabilities | -| `/conformance` | JSON | Info about standards to which the API conforms | -| `/collections` | JSON | Object with a list of Collections contained in the catalog and links | -| `/collections/{collectionId}` | Collection | Returns single Collection JSON | +| `/` | [Catalog](../stac-spec/catalog-spec/README.md) | Landing page, links to API capabilities | +| `/conformance` | JSON | Info about standards to which the API conforms | +| `/collections` | JSON | Object containing an array of Collections within the Catalog and Links | +| `/collections/{collectionId}` | [Collection](../stac-spec/collection-spec/README.md) | Returns single Collection JSON | | `/collections/{collectionId}/items` | [ItemCollection](../fragments/itemcollection/README.md) | GeoJSON FeatureCollection-conformant entity of Items in collection | -| `/collections/{collectionId}/items/{featureId}` | Item | Returns single Item (GeoJSON Feature) | +| `/collections/{collectionId}/items/{featureId}` | [Item](../stac-spec/item-spec/README.md) | Returns single Item (GeoJSON Feature) | | `/api` | OpenAPI 3.0 JSON | Returns an OpenAPI description of the service from the `service-desc` link `rel` - not required to be `/api`, but the document is required | The OGC API - Features is a standard API that represents collections of geospatial data. It defines the RESTful interface @@ -72,9 +72,9 @@ protocol than STAC. ## Examples -Note that the OAFeat endpoints *only* supports HTTP GET. HTTP POST requests are not supported. If POST is required it is -recommended to use STAC Item Search, as it can be constrained to a single collection to act the same as an OAFeat `items` -endpoint. +Note that the OAFeat endpoints *only* allow HTTP GET. HTTP POST requests are not supported. If POST is required, +it is recommended to use STAC Item Search, as it can be constrained to a single collection to act the same as +an OAFeat `items` endpoint. Request all the data in `mycollection` that is in New Zealand: diff --git a/ogcapi-features/openapi.yaml b/ogcapi-features/openapi.yaml index cf70eb7f..c448b02a 100644 --- a/ogcapi-features/openapi.yaml +++ b/ogcapi-features/openapi.yaml @@ -37,8 +37,10 @@ paths: application/json: schema: $ref: '../core/openapi.yaml#/components/schemas/landingPage' + $ref: '../core/commons.yaml#/components/schemas/catalog' example: stac_version: 1.0.0-beta.1 + type: Catalog id: sentinel title: Copernicus Sentinel Imagery description: Catalog of Copernicus Sentinel 1 and 2 imagery. From 4173b784596ae3ead3009efbca5d88da049c9740 Mon Sep 17 00:00:00 2001 From: philvarner Date: Tue, 16 Mar 2021 21:50:47 -0400 Subject: [PATCH 03/61] update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b71b929..8c800d00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Catalog now has required field "type" + +### Changed + +### Deprecated + +### Removed + +### Fixed +- Updated text description of root ('/') endpoint (also called landing page) that the return type is a Catalog + ## [v1.0.0-beta.1] - 2020-12-10 ### Added From 088d637493948141e4a560ed68492e7b842c0b1d Mon Sep 17 00:00:00 2001 From: philvarner Date: Tue, 16 Mar 2021 21:53:31 -0400 Subject: [PATCH 04/61] fix openapi --- ogcapi-features/openapi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/ogcapi-features/openapi.yaml b/ogcapi-features/openapi.yaml index c448b02a..1c9ed6d1 100644 --- a/ogcapi-features/openapi.yaml +++ b/ogcapi-features/openapi.yaml @@ -37,7 +37,6 @@ paths: application/json: schema: $ref: '../core/openapi.yaml#/components/schemas/landingPage' - $ref: '../core/commons.yaml#/components/schemas/catalog' example: stac_version: 1.0.0-beta.1 type: Catalog From 4f78524c551524d0dc2a3c29ecf376f3b2221c66 Mon Sep 17 00:00:00 2001 From: philvarner Date: Wed, 17 Mar 2021 12:33:03 -0400 Subject: [PATCH 05/61] x --- README.md | 80 +------------------------------------------------------ 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/README.md b/README.md index 8aa4bd66..d6459e00 100644 --- a/README.md +++ b/README.md @@ -1,79 +1 @@ -stac-logo - -# STAC API - -## About - -The SpatioTemporal Asset Catalog (STAC) specification aims to standardize the way geospatial assets are exposed online and queried. -A 'spatiotemporal asset' is any file that represents information about the earth captured in a certain space and -time. The core STAC specification lives at [gitub.com/radiantearth/stac-spec](https://github.com/radiantearth/stac-spec). - -A STAC API is the dynamic version of a SpatioTemporal Asset Catalog. It returns a STAC [Catalog](stac-spec/catalog-spec/catalog-spec.md), -[Collection](stac-spec/collection-spec/collection-spec.md), [Item](stac-spec/item-spec/item-spec.md), -or a STAC API [ItemCollection](fragments/itemcollection/README.md), depending on the endpoint. -Catalogs and Collections are JSON, while Items and ItemCollections are GeoJSON-compliant entities with foreign members. -Typically, a Feature is used when returning a single Item, and FeatureCollection when multiple Items (rather than a JSON array of Item entities). - -The API can be implemented in compliance with the *[OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html)* standard -(we'll use OAFeat for shorthand). In this case STAC API can be thought of as a specialized Features API -to search STAC Catalogs, where the features returned are STAC [Items](stac-spec/item-spec/item-spec.md), -that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets. - -The specification for STAC API is provided as files that follow the [OpenAPI](http://openapis.org/) 3.0 specification, -rendered online into HTML at , in addition to human-readable documentation. - -## Stability Note - -This specification has evolved over the past couple years, and is used in production in a variety of deployments. It is -currently in a 'beta' state, with no major changes anticipated. For 1.0-beta we remain fully aligned with [OGC API - -Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) Version 1.0, and we are working to stay aligned -as the additional OGC API components mature. This may result in minor changes as things evolve. The STAC API specification - -follows [Semantic Versioning](https://semver.org/), so once 1.0.0 is reached any breaking change will require the spec to -go to 2.0.0. - -## Communication - -For any questions feel free to jump on our [gitter channel](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby) or email -our [google group](https://groups.google.com/forum/#!forum/stac-spec). The majority of communication about the evolution of -the specification takes place in the [issue tracker](https://github.com/radiantearth/stac-api-spec/issues) and in -[pull requests](https://github.com/radiantearth/stac-api-spec/pulls). - -## In this repository - -The **[Overview](overview.md)** document describes all the various parts of the STAC API and how they fit together. - -**STAC API - Core Specification:** -The *[core](core/)* folder describes the core STAC API specification that enables browsing catalogs and -retrieving the API capabilities. This includes the OpenAPI schemas for STAC items, catalogs and collections. - -**STAC API - Item Search Specification:** -The *[item-search](item-search)* folder contains the Item Search specification, which enables -cross-collection search of STAC Items at a `search` endpoint, as well as a number of extensions. - -**STAC API - Features:** -The *[ogcapi-features](ogcapi-features)* folder describes how a STAC API can fully implement [OGC API - -Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) to expose individual `items` endpoints for search of -each STAC collection. It also includes extensions that can be used to further enhance OAFeat. - -**Extensions:** -The *[extensions](extensions.md) document* describes how STAC incubates new functionality, and it links to the existing -extensions that can be added to enrich the functionality of a STAC API. Each has an OpenAPI yaml, but some of the yaml -documents live as fragments in the [fragments/](fragments/) folder. - -**Fragments:** -The *[fragments/](fragments/)* folder contains re-usable building blocks to be used in a STAC API, including common OpenAPI -schemas and parameters for behavior like sorting and filtering. Most all of them are compatible with -OGC API - Features, and the plan is to fully align the relevant functionality and have it be useful for all OAFeat implementations. -OpenAPI YAML documents are provided for each extension with additional documentation and examples provided in a README. - -**STAC Specification:** This repository includes a '[sub-module](https://git-scm.com/book/en/v2/Git-Tools-Submodules)', which -is a copy of the [STAC specification](stac-spec/) tagged at the latest stable version. -Sub-modules aren't checked out by default, so to get the directory populated -either use `git submodule update --init --recursive` if you've already cloned it, -or clone from the start with `git clone --recursive git@github.com:radiantearth/stac-api-spec.git`. - -## Contributing - -Anyone building software that catalogs imagery or other geospatial assets is welcome to collaborate. -Beforehand, please review our [guidelines for contributions and development process](CONTRIBUTING.md). +xxx From 7007eff0a27334dc9c2be224c683b58f8fc5569b Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Wed, 14 Apr 2021 17:53:47 +0200 Subject: [PATCH 06/61] Clarification on multiple bounding boxes in an extent. https://github.com/radiantearth/stac-spec/issues/1064 https://github.com/opengeospatial/ogcapi-features/pull/520 --- CHANGELOG.md | 3 +++ core/commons.yaml | 29 ++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b71b929..77cf5200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) + ## [v1.0.0-beta.1] - 2020-12-10 ### Added diff --git a/core/commons.yaml b/core/commons.yaml index 42f5835d..daa12149 100644 --- a/core/commons.yaml +++ b/core/commons.yaml @@ -246,6 +246,12 @@ components: The extent of the features in the collection. In the Core only spatial and temporal extents are specified. Extensions may add additional members to represent other extents, for example, thermal or pressure ranges. + + The first items in the arrays always describe the overall extent of + the data. All subsequent extents can be used to provide a more precise + description of the extent and identify clusters of data. + Clients only interested in the overall extent will only need to + access the first item in each array. required: - spatial - temporal @@ -260,9 +266,13 @@ components: bbox: description: |- One or more bounding boxes that describe the spatial extent of the dataset. - In the Core only a single bounding box is supported. Extensions may support - additional areas. If multiple areas are provided, the union of the bounding - boxes describes the spatial extent. + + The first bounding box always describes the overall spatial + extent of the data. All subsequent bounding boxes can be + used to provide a more precise description of the extent and + identify clusters of data. + Clients only interested in the overall spatial extent will + only need to access the first item in each array. type: array minItems: 1 items: @@ -324,10 +334,13 @@ components: interval: description: |- One or more time intervals that describe the temporal extent of the dataset. - The value `null` is supported and indicates an open time intervall. - In the Core only a single time interval is supported. Extensions may support - multiple intervals. If multiple intervals are provided, the union of the - intervals describes the temporal extent. + + The first time interval always describes the overall + temporal extent of the data. All subsequent time intervals + can be used to provide a more precise description of the + extent and identify clusters of data. + Clients only interested in the overall extent will only need + to access the first item in each array. type: array minItems: 1 items: @@ -335,6 +348,8 @@ components: Begin and end times of the time interval. The timestamps are in the coordinate reference system specified in `trs`. By default this is the Gregorian calendar. + + The value `null` is supported and indicates an open time interval. type: array minItems: 2 maxItems: 2 From 85ff067a14a840facc447d780b6ebec50f27e90e Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Thu, 15 Apr 2021 13:41:22 +0200 Subject: [PATCH 07/61] Updated to latest version of OGC API - Features PR. Thank you @philvarner --- core/commons.yaml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/core/commons.yaml b/core/commons.yaml index daa12149..d5480f07 100644 --- a/core/commons.yaml +++ b/core/commons.yaml @@ -247,9 +247,9 @@ components: extents are specified. Extensions may add additional members to represent other extents, for example, thermal or pressure ranges. - The first items in the arrays always describe the overall extent of - the data. All subsequent extents can be used to provide a more precise - description of the extent and identify clusters of data. + The first item in the array describes the overall extent of + the data. All subsequent items describe more precise extents, + e.g., to identify clusters of data. Clients only interested in the overall extent will only need to access the first item in each array. required: @@ -267,10 +267,9 @@ components: description: |- One or more bounding boxes that describe the spatial extent of the dataset. - The first bounding box always describes the overall spatial - extent of the data. All subsequent bounding boxes can be - used to provide a more precise description of the extent and - identify clusters of data. + The first bounding box describes the overall spatial + extent of the data. All subsequent bounding boxes describe + more precise bounding boxes, e.g., to identify clusters of data. Clients only interested in the overall spatial extent will only need to access the first item in each array. type: array @@ -335,10 +334,9 @@ components: description: |- One or more time intervals that describe the temporal extent of the dataset. - The first time interval always describes the overall - temporal extent of the data. All subsequent time intervals - can be used to provide a more precise description of the - extent and identify clusters of data. + The first time interval describes the overall + temporal extent of the data. All subsequent time intervals describe + more precise time intervals, e.g., to identify clusters of data. Clients only interested in the overall extent will only need to access the first item in each array. type: array From 4204a4862b2cff632a75ba3a7558aacde5ab6067 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Thu, 15 Apr 2021 13:51:49 +0200 Subject: [PATCH 08/61] Fix CI --- .circleci/rc.yaml | 5 ++--- README.md | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index fc1258d1..b0dd538c 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -21,15 +21,14 @@ plugins: - 'fenced' # Headings - remark-lint-heading-increment - - remark-lint-no-duplicate-headings - remark-lint-no-multiple-toplevel-headings - remark-lint-no-heading-punctuation - - - remark-lint-no-shortcut-reference-link - - false - - remark-lint-maximum-heading-length - 70 - - remark-lint-heading-style - atx + - - remark-lint-no-shortcut-reference-link + - false # Lists - remark-lint-list-item-bullet-indent - remark-lint-ordered-list-marker-style diff --git a/README.md b/README.md index 8aa4bd66..5e89be59 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + stac-logo # STAC API From 4b60da6546b204a7e919c8aaa473499ec44eaad7 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Thu, 15 Apr 2021 13:51:49 +0200 Subject: [PATCH 09/61] Fix CI --- .circleci/rc.yaml | 5 ++--- README.md | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index fc1258d1..b0dd538c 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -21,15 +21,14 @@ plugins: - 'fenced' # Headings - remark-lint-heading-increment - - remark-lint-no-duplicate-headings - remark-lint-no-multiple-toplevel-headings - remark-lint-no-heading-punctuation - - - remark-lint-no-shortcut-reference-link - - false - - remark-lint-maximum-heading-length - 70 - - remark-lint-heading-style - atx + - - remark-lint-no-shortcut-reference-link + - false # Lists - remark-lint-list-item-bullet-indent - remark-lint-ordered-list-marker-style diff --git a/README.md b/README.md index 8aa4bd66..5e89be59 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + stac-logo # STAC API From 7472474edc62ded650514a00feaa098b81fc2090 Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 20 Apr 2021 09:22:58 -0400 Subject: [PATCH 10/61] Remove stac-spec git submodule --- .gitmodules | 3 --- stac-spec | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 stac-spec diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b301bd95..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "stac-spec"] - path = stac-spec - url = https://github.com/radiantearth/stac-spec diff --git a/stac-spec b/stac-spec deleted file mode 160000 index 4988a40f..00000000 --- a/stac-spec +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4988a40f6071666d345e4a1c5adc9a6338973769 From b544f2dcf63bd067548b369b95fb9afa4535a0bc Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 20 Apr 2021 09:33:48 -0400 Subject: [PATCH 11/61] Squashed 'stac-spec/' content from commit ebd16753 git-subtree-dir: stac-spec git-subtree-split: ebd167536b17d3e311dd6a3fd963799db3f5fb28 --- .circleci/config.yml | 53 ++ .circleci/publish-schemas.js | 40 + .circleci/rc.yaml | 46 ++ .github/pull_request_template.md | 14 + .gitignore | 69 ++ .remarkignore | 1 + CHANGELOG.md | 420 +++++++++++ CODE_OF_CONDUCT.md | 76 ++ CONTRIBUTING.md | 81 ++ LICENSE | 201 +++++ README.md | 109 +++ STAC-UML.drawio | 1 + STAC-UML.pdf | Bin 0 -> 46890 bytes best-practices.md | 709 ++++++++++++++++++ catalog-spec/README.md | 34 + catalog-spec/catalog-spec.md | 127 ++++ catalog-spec/json-schema/catalog-core.json | 139 ++++ catalog-spec/json-schema/catalog.json | 29 + collection-spec/README.md | 43 ++ collection-spec/collection-spec.md | 260 +++++++ collection-spec/json-schema/collection.json | 170 +++++ examples/README.md | 89 +++ examples/catalog.json | 30 + examples/collection-only/collection.json | 228 ++++++ examples/collection.json | 93 +++ examples/collectionless-item.json | 147 ++++ examples/core-item.json | 117 +++ examples/extended-item.json | 191 +++++ .../extensions-collection/collection.json | 66 ++ .../proj-example/proj-example.json | 278 +++++++ examples/simple-item.json | 74 ++ extensions/README.md | 172 +++++ item-spec/README.md | 23 + item-spec/common-metadata.md | 186 +++++ item-spec/item-spec.md | 306 ++++++++ item-spec/json-schema/basics.json | 18 + item-spec/json-schema/datetime.json | 48 ++ item-spec/json-schema/instrument.json | 32 + item-spec/json-schema/item.json | 250 ++++++ item-spec/json-schema/licensing.json | 12 + item-spec/json-schema/provider.json | 47 ++ overview.md | 186 +++++ package.json | 26 + principles.md | 45 ++ process.md | 119 +++ schema.json | 0 46 files changed, 5405 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .circleci/publish-schemas.js create mode 100644 .circleci/rc.yaml create mode 100644 .github/pull_request_template.md create mode 100644 .gitignore create mode 100644 .remarkignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 STAC-UML.drawio create mode 100644 STAC-UML.pdf create mode 100644 best-practices.md create mode 100644 catalog-spec/README.md create mode 100644 catalog-spec/catalog-spec.md create mode 100644 catalog-spec/json-schema/catalog-core.json create mode 100644 catalog-spec/json-schema/catalog.json create mode 100644 collection-spec/README.md create mode 100644 collection-spec/collection-spec.md create mode 100644 collection-spec/json-schema/collection.json create mode 100644 examples/README.md create mode 100644 examples/catalog.json create mode 100644 examples/collection-only/collection.json create mode 100644 examples/collection.json create mode 100644 examples/collectionless-item.json create mode 100644 examples/core-item.json create mode 100644 examples/extended-item.json create mode 100644 examples/extensions-collection/collection.json create mode 100644 examples/extensions-collection/proj-example/proj-example.json create mode 100644 examples/simple-item.json create mode 100644 extensions/README.md create mode 100644 item-spec/README.md create mode 100644 item-spec/common-metadata.md create mode 100644 item-spec/item-spec.md create mode 100644 item-spec/json-schema/basics.json create mode 100644 item-spec/json-schema/datetime.json create mode 100644 item-spec/json-schema/instrument.json create mode 100644 item-spec/json-schema/item.json create mode 100644 item-spec/json-schema/licensing.json create mode 100644 item-spec/json-schema/provider.json create mode 100644 overview.md create mode 100644 package.json create mode 100644 principles.md create mode 100644 process.md create mode 100644 schema.json diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..7095d1c8 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,53 @@ +version: 2 +jobs: + test_examples: + working_directory: ~/stac + docker: + - image: circleci/node:12 + steps: + - checkout + - run: + name: install + command: npm install + - run: + name: validate + command: npm run check-examples + test_docs: + working_directory: ~/stac + docker: + - image: circleci/node:12 + steps: + - checkout + - run: + name: install + command: npm install + - run: + name: validate + command: npm run check-markdown + publish_schemas: + working_directory: ~/stac + docker: + - image: circleci/node:12 + steps: + - checkout + - run: + name: install + command: npm install + - add_ssh_keys: + fingerprints: + - "9b:0a:88:ff:12:d1:29:9a:ff:bb:72:ab:7d:81:df:59" + - run: + name: publish + command: npm run publish-schemas -- $CIRCLE_TAG +workflows: + version: 2 + ci: + jobs: + - test_examples + - test_docs + - publish_schemas: + filters: + tags: + only: /^v.*/ + branches: + ignore: /^((?!dev).)*$/ \ No newline at end of file diff --git a/.circleci/publish-schemas.js b/.circleci/publish-schemas.js new file mode 100644 index 00000000..824f88c2 --- /dev/null +++ b/.circleci/publish-schemas.js @@ -0,0 +1,40 @@ +const klaw = require('klaw-sync'); +const path = require('path'); +const fs = require('fs'); +const ghpages = require('gh-pages'); + +function filterFn (item) { + const basename = path.basename(item.path); + return basename === '.' || basename === 'node_modules' || basename[0] !== '.'; +} + +let args = process.argv.slice(2); +let tag = 'dev'; +if (args.length && args[0].trim().length > 0) { + tag = args[0]; +} + +var folder = '.'; +var jsonSchemaFolderPattern = path.sep + 'json-schema' + path.sep; +for (let file of klaw(folder, {filter: filterFn})) { + if (file.path.includes(jsonSchemaFolderPattern) && path.extname(file.path) === '.json') { + let source = file.path; + let target = 'schemas' + path.sep + tag + path.sep + path.relative(folder, file.path); + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.copyFileSync(source, target); + console.log(target); + } +} + +ghpages.publish('schemas/' + tag, { + src: '**', + dest: tag, + message: 'Publish JSON Schemas [ci skip]', + user: { + name: 'STAC CI', + email: 'ci@stacspec.org' + } +}, error => { + console.error(error ? error : 'Deployed to gh-pages'); + process.exit(error ? 1 : 0); +}); \ No newline at end of file diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml new file mode 100644 index 00000000..625a856a --- /dev/null +++ b/.circleci/rc.yaml @@ -0,0 +1,46 @@ +plugins: +# Check links + - validate-links +# Apply some recommended defaults for consistency + - remark-preset-lint-consistent + - remark-preset-lint-recommended +# No HTML for security - can't activate yet due to STAC logo in README.md +# - lint-no-html +# General formatting +# - - remark-lint-emphasis-marker +# - '*' + - remark-lint-hard-break-spaces + - remark-lint-blockquote-indentation + - remark-lint-no-consecutive-blank-lines +# Detect overly long lines - be liberal for now and don't restrict to 80 yet +# - - remark-lint-maximum-line-length +# - 150 +# Code + - remark-lint-fenced-code-flag + - remark-lint-fenced-code-marker + - remark-lint-no-shell-dollars + - - remark-lint-code-block-style + - 'fenced' +# Headings + - remark-lint-heading-increment + - remark-lint-no-duplicate-headings + - remark-lint-no-multiple-toplevel-headings + - remark-lint-no-heading-punctuation + - - remark-lint-maximum-heading-length + - 70 + - - remark-lint-heading-style + - atx +# Lists + - remark-lint-list-item-bullet-indent + - remark-lint-ordered-list-marker-style + - remark-lint-ordered-list-marker-value + - remark-lint-checkbox-character-style +# - - remark-lint-unordered-list-marker-style +# - '-' + - - remark-lint-list-item-indent + - space +# Tables + - remark-lint-table-pipes +# - remark-lint-table-pipe-alignment # Wait for https://github.com/remarkjs/remark-lint/issues/226 +# Urls + - remark-lint-no-literal-urls \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..e26fc2c9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +**Related Issue(s):** # + + +**Proposed Changes:** + +1. +2. + +**PR Checklist:** + +- [ ] This PR is made against the dev branch (all proposed changes except releases should be against dev, not master). +- [ ] This PR has **no** breaking changes. +- [ ] I have added my changes to the [CHANGELOG](https://github.com/radiantearth/stac-spec/blob/dev/CHANGELOG.md) **or** a CHANGELOG entry is not required. +- [ ] This PR affects the [STAC API spec](https://github.com/radiantearth/stac-api-spec), and I have opened issue/PR #XXX to track the change. diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ab3dafa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ +package-lock.json + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# IntelliJ IDEA files +.idea/ +*.iml + +# Folder created when CI puhlishes JSON Schemas +schemas/ diff --git a/.remarkignore b/.remarkignore new file mode 100644 index 00000000..32524460 --- /dev/null +++ b/.remarkignore @@ -0,0 +1 @@ +/CHANGELOG.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9a8027a1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,420 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [v1.0.0-rc.2] - 2021-03-30 + +### Changed + +- Required properties of type `string` require a minimum length of `1`. ([#1065](https://github.com/radiantearth/stac-spec/pull/1065)) +- `gsd` must be greater than 0. ([#1068](https://github.com/radiantearth/stac-spec/pull/1068)) + +### Removed + +- The remaining extensions in the spec (EO, Projection, Scientific Citation and View) have been moved out of the core specification, into their own repos in the [stac-extensions](https://github.com/stac-extensions/) GitHub organization. They must now be referred to by their schemas directly - the name shortcuts for them are no longer valid. The concept of the 'named shortcut' also goes away entirely. ([#1070](https://github.com/radiantearth/stac-spec/pull/1070)) + +### Fixed + +- Collection Assets were specified as required (only in written text, not in JSON Schema), but that was incorrectly copied over from the former `collection-assets` extension. Collection Assets are not required. +- Clarified that the values in summaries (both for ranges and sets of values) must follow the data type of the property they summarize. ([#1069](https://github.com/radiantearth/stac-spec/pull/1069)) + +## [v1.0.0-rc.1] - 2021-03-03 + +### Added + +- Catalog and Collection now require a `type` parameter, to be set to `Catalog` or `Collection` for clients to more easily distinguish them easily. ([#971](https://github.com/radiantearth/stac-spec/pull/971)) +- Collection specification adds Assets (previously needed Collections Asset extension to do that). ([#1008](https://github.com/radiantearth/stac-spec/pull/1008)) +- 'via' and 'canonical' rel types are now options in Items. ([#884](https://github.com/radiantearth/stac-spec/pull/884)) +- In Extensions list there is now reference to three new (non-core) extensions: [processing](https://github.com/stac-extensions/processing), [file info](https://github.com/stac-extensions/file) and [card4l](https://github.com/stac-extensions/card4l). These would have been added to the stac-spec repo, except all non-core extensions were moved to [stac-extensions](https://github.com/stac-extensions/) org. ([#1024](https://github.com/radiantearth/stac-spec/pull/1024)) +- 'summaries' are now available in the Catalog spec, so both catalogs and collections can make use of it. ([#903](https://github.com/radiantearth/stac-spec/issues/903)) +- There is a new recommendation to enable CORS. ([#940](https://github.com/radiantearth/stac-spec/pull/940)) +- A Best Practice section on 'requester pays' cloud buckets was added. ([#1021](https://github.com/radiantearth/stac-spec/pull/1021)) +- A new Best Practice section explains Asset Roles, plus some lists of potential roles for people to use (in best practices, sar and eo). ([#989](https://github.com/radiantearth/stac-spec/pull/989)) +- There is a new Best Practice recommendation to keep collections at consistent levels. ([#1009](https://github.com/radiantearth/stac-spec/pull/1009)) + +### Changed + +- The [Stats Object](collection-spec/collection-spec.md#stats-object) for Collection `summaries` changed `min` to `minimum` and `max` to `maximum` to align with JSON Schema. ([#967](https://github.com/radiantearth/stac-spec/pull/967)) +- URIs (usually found int properties like `href`, `url`) are now validated using the `iri-reference` format in JSON Schema (allows international characters in URIs) ([#953](https://github.com/radiantearth/stac-spec/pull/953)) +- Enhanced the way the spec talks about ID's to encourage more global uniqueness. ([#883](https://github.com/radiantearth/stac-spec/pull/883)) +- Clarified how collection-level asset object properties do not remove the need for item-level asset object properties in the `item-assets` extension ([#880](https://github.com/radiantearth/stac-spec/pull/880)) +- Made `summaries` to be *strongly recommended* - everyone should strive to implement them, as they are very useful. ([#985](https://github.com/radiantearth/stac-spec/pull/985)) +- Moved examples from individual directories into a single /examples folder at the root, and evolved them to be more representative. ([#955](https://github.com/radiantearth/stac-spec/pull/955)) +- Renamed "Scientific Extension" to "Scientific Citation Extension" ([#990](https://github.com/radiantearth/stac-spec/issues/990)) +- Relaxed the regular expression for DOIs in the scientific extension ([#910](https://github.com/radiantearth/stac-spec/issues/910)) +- `proj:geometry` allows all GeoJSON geometries instead of just a polygon. ([#995](https://github.com/radiantearth/stac-spec/pull/995)) + +### Removed + +- Checksum extension (field `checksum:multihash`). Use File Info extension (field `file:checksum`) instead for assets. There's no replacement for links. ([#934](https://github.com/radiantearth/stac-spec/pull/934)) +- Collection Assets extension, as the core construct of Assets in a Collection is now part of the core Collection spec. No change is required except removing `collection-assets` from the list of `stac_extensions`. ([#1008](https://github.com/radiantearth/stac-spec/pull/1008)) +- Numerous extensions (Data Cube, Item Assets, Point Cloud, SAR, Single File STAC, Tiled Assets, Timestamps & Versioning) have been moved out of the core specification, into their own repos in the [stac-extensions](https://github.com/stac-extensions/) GitHub organization. They must now be referred to by their schemas directly - the name shortcuts for them are no longer valid. ([#1024](https://github.com/radiantearth/stac-spec/pull/1024)) + +### Fixed + +- Fixed JSON Schema for `providers` (Collections and Items) to be an object and require a `name`. ([#924](https://github.com/radiantearth/stac-spec/pull/924)) + +## [v1.0.0-beta.2] - 2020-07-08 + +### Added +- JSON-schema file in the Point Cloud extension. + +### Changed +- Clarification on null geometries, making bbox not required if a null geometry is used. +- Multiple extents (bounding boxes / intervals) are allowed per Collection +- In the scientific extension, a link with the rel-type 'cite-as' SHOULD be used for the main publication of the dataset (the same as the one described in `sci:doi`), and not for the DOIs referenced in the `sci:publications` property. + +### Removed +- Validation instructions + +### Fixed +- Fixed several JSON Schemas +- Fixed examples + +## [v1.0.0-beta.1] - 2020-05-29 + +### Removed +- The API portion of STAC has been split off into a [new repository: stac-api-spec](https://github.com/radiantearth/stac-api-spec) and will start being versioned and released separately than the core STAC spec. +- proj4 string from proj extension +- Various warnings about how the spec is very early and likely will change. +- implementations.md (migrated to https://stacspec.org) and how-to-help.md (migrated to https://github.com/stac-utils/stac-ecosystem). +- `commons` extension completely removed: Items should contain all properties and not default to a common set at the Collection level +- ItemCollection removed from stac-spec core repo, will migrate to [stac-api-spec](https://github.com/radiantearth/stac-api-spec) as that is the only place it is used. + +### Added +- 'alternate' as a listed 'rel' type with recommended 'text/html' to communicate there is an html version. +- Added a code of conduct based on github's template. +- Overview document that gives a more explanatory discussion of the various parts of the spec and how they relate +- Several new sections to 'best practices' document. +- Added the ability to define Item properties under Assets (item-spec/item-spec.md) +- Add `proj:shape` and `proj:transform` to the projections extension. +- Collection-level assets extension +- Instructions on how to run check-markdown locally +- Timestamps extensions (adds fields `published`, `expires` and `unpublished`) +- `created` and `updated` can be used in the assets to specify the creation / update times of the assets. +- [Tiled Assets extension](extensions/tiled-assets/README.md), for representing data that has been split into tiles + +### Changed +- [Label extension](extensions/label/README.md) types were clarified and types in README and JSON schema were brought into alignment +- Moved item recommendations to best practices, and added a bit more in item spec about 'search' +- Moved `eo:gsd` from `eo` extension to core `gsd` field in Item common metadata +- `asset` extension renamed to `item-assets` and renamed `assets` field in Collections to `item_assets` +- `item-assets` extension only requires any two fields to be available, not the two specific fields `title` and `type` +- `datetime` allows `null` as value, but requires `start_datetime` and `end_datetime` then +- Extensions `sat`, `scientific` and `view`: At least one field is required to be specified. +- [Single File STAC extension](extensions/single-file-stac/README.md) changed to be a complete STAC catalog + GeoJSON FeatureCollection that contains collections and items. +- Improved several JSON Schemas + +### Fixed +- Datacube extension: `cube:dimensions` was not flagged as required. + +## [v0.9.0] - 2020-02-26 + +### Added +- ItemCollection requires `stac_version` field, `stac_extensions` has also been added +- A `description` field has been added to Item assets (also Asset definitions extension) +- Field `mission` to [Common Metadata fields](item-spec/common-metadata.md) +- Extensions: + - [Version Indicators extension](extensions/version/README.md), new `version` and `deprecated` fields in STAC Items and Collections + - Data Cube extension can be used in Collections, added new field `description` + - [Asset Extension](extensions/asset/README.md): new `description` and `roles` fields + - New [Projection Extension](extensions/projection/README.md) to describe Items with Assets that have an associated geospatial projection + - New [View Geometry Extension](extensions/view/README.md) +- STAC API: + - Added the [Item and Collection API Version extension](https://github.com/radiantearth/stac-api-spec/tree/master/extensions/version/README.md) to support versioning in the API specification + - Run `npm run serve` or `npm run serve-ext` to quickly render development versions of the OpenAPI spec in the browser +- [Basics](item-spec/common-metadata.md#basics) added to Common Metadata definitions with new `description` field for +Item properties +- New fields to the `link` object to facilitate [pagination support for POST requests](https://github.com/radiantearth/stac-api-spec/tree/master/api-spec.md#paging-extension) +- `data` role, as a suggestion for a common role for data files to be used in case data providers don't come up with their own names and semantics +- Clarification text on HTTP verbs in STAC API + +### Changed +- Support for [CommonMark 0.29 instead of CommonMark 0.28](https://spec.commonmark.org/0.29/changes.html) +- Collection field `property` and the merge ability moved to a new extension 'Commons' +- Added field `roles` to Item assets (also Asset definitions extension), to be used similarly to Link `rel` +- Updated API yaml to clarify bbox filter should be implemented without brackets. Example: `bbox=160.6,-55.95,-170,-25.89` +- Collection `summaries` merge array fields now +- Several fields have been moved from extensions or item fields to the [Common Metadata fields](item-spec/common-metadata.md): + - `eo:platform` / `sar:platform` => `platform` + - `eo:instrument` / `sar:instrument` => `instruments`, also changed from string to array of strings + - `eo:constellation` / `sar:constellation` => `constellation` + - `dtr:start_datetime` => `start_datetime` + - `dtr:end_datetime` => `end_datetime` +- Moved angle definitions from extensions `eo` and new `view` extension + - `eo:off_nadir` -> `view:off_nadir` + - `eo:azimuth` -> `view:azimuth` + - `eo:incidence_angle` -> `view:incidence_angle` + - `eo:sun_azimuth` -> `view:sun_azimuth` + - `eo:sun_elevation` -> `view:sun_elevation` +- Extensions: + - Data Cube extension: Changed allowed formats (removed PROJ string, added PROJJSON / WKT2) for reference systems + - [Checksum extension](extensions/checksum/README.md) is now using self-identifiable hashes ([Multihash](https://github.com/multiformats/multihash)) + - Changed `sar:type` to `sar:product_type` and `sar:polarization` to `sar:polarizations` in the [SAR extension](extensions/sar/README.md) +- STAC API: + - The endpoint `/stac` has been merged with `/` + - The endpoint `/stac/search` is now called `/search` + - Sort Extension - added non-JSON query/form parameter format + - Fields extension has a simplified format for GET parameters + - `search` extension renamed to `context` extension. JSON object renamed from `search:metadata` to `context` + - Removed "next" from the search metadata and query parameter, added POST body and headers to the links for paging support + - Query Extension - type restrictions on query predicates are more accurate, which may require additional implementation support +- Item `title` definition moved from core Item fields to [Common Metadata Basics](item-spec/common-metadata.md#basics) +fields. No change is required for STAC Items. +- `putFeature` can return a `PreconditionFailed` to provide more explicit information when the resource has changed in the server +- [Sort extension](https://github.com/radiantearth/stac-api-spec/tree/master/extensions/sort) now uses "+" and "-" prefixes for GET requests to denote sort order. +- Clarified how `/search` links must be added to `/` and changed that links to both GET and POST must be provided now that the method can be specified in links + +### Removed +- `version` field in STAC Collections. Use [Version Extension](extensions/version/README.md) instead +- `summaries` field from Catalogs. Use Collections instead +- Asset Types (pre-defined values for the keys of individual assets, *not* media types) in Items. Use the asset's `roles` instead +- `license` field doesn't allow SPDX expressions any longer. Use `various` and links instead +- Extensions: + - `eo:platform`, `eo:instrument`, `eo:constellation` from EO extension, and `sar:platform`, `sar:instrument`, `sar:constellation` from the [SAR extension](extensions/sar/README.md) + - Removed from EO extension field `eo:epsg` in favor of `proj:epsg` + - `gsd` and `accuracy` from `eo:bands` in the [EO extension](extensions/eo/README.md) + - `sar:absolute_orbit` and `sar:center_wavelength` fields from the [SAR extension](extensions/sar/README.md) + - `data_type` and `unit` from the `sar:bands` object in the [SAR extension](extensions/sar/README.md) + - Datetime Range (`dtr`) extension. Use the [Common Metadata fields](item-spec/common-metadata.md) instead +- STAC API: + - `next` from the search metadata and query parameter +- In API, removed any mention of using media type `multipart/form-data` and `x-www-form-urlencoded` + +### Fixed + +- The `license` field in Item and Collection spec explicitly mentions that the value `proprietary` without a link means that the data is private +- Clarified how to fill `stac_extensions` +- More clarifications; typos fixed +- Fixed Item JSON Schema now `allOf` optional Common Metadata properties are evaluated +- Clarified usage of optional Common Metadata fields for STAC Items +- Clarified usage of paging options, especially in relation to what OGC API - Features offers +- Allow Commonmark in asset description, as it's allowed everywhere else +- Put asset description in the API +- Fixed API spec regarding license expressions +- Added missing schema in the API Version extension +- Fixed links in the Landsat example in the collection-spec + +## [v0.8.1] - 2019-11-01 + +### Changed +- Updated specification to base on OGC API - Features - Part 1: Core, v1.0.0 instead of OGC API - Features - Part 1: Core, v1.0.0-draft.2 (fka WFS3 draft 2). + +### Fixed +- Numerous typos, clarifications and fixes for documentation and examples. +- Fixed STAC API definition to include STAC-related fields and examples in *OGC API - Features*-derived endpoints. +- Fixed JSON schemas for extensions: `$id` field matches file name. + +## [v0.8.0] - 2019-10-11 + +### Changed +- Updated specification to base on WFS3 draft 2 (OGC API - Features - Part 1: Core, v1.0.0-draft.2). This leads to many changes in the API and one change in STAC collections, notably: + - The structure of the field `extent` in STAC and WFS Collections changed. + - Query parameter `time` was renamed to `datetime` and accepts slightly different values. + - WFS links have additional fields `hreflang` and `length`. + - WFS Collections have additional fields `crs` and `itemType`. + - `time` API parameter changed to `datetime` +- The API intersects parameter now accepts a GeoJSON Geometry (any type) *instead* of a GeoJSON Feature. +- API: Clarification on `include` and `exclude` parameters in the field extension and notes on default values. +- API: queries should contain either `bbox` or `intersects`. +- API: Core API now has reserved parameters to prevent overlap with extensions +- Updated bbox definitions in API, Item, and Collection specs to include support for optional elevation values. +- Moved Single Item Extension to core (`license` and `providers` properties for Items). +- Allow `various` for the `license` fields. +- Clarified meaning of SAR and EO platform, constellation, and instrument +- Numerous typos, clarification and general word-smithing +- Changed GeoTIFF media type from `image/vnd.stac.geotiff` to `image/tiff; application=geotiff`, changed Cloud-optimized GeoTiff media type from `image/vnd.stac.geotiff; cloud-optimized=true` to `image/tiff; application=geotiff; profile=cloud-optimized`. + +### Added +- **stac_version**: Each Item must specify the STAC version. +- **stac_extensions**: Introduced this field for Items, Catalogs and Collections. +- Property `summaries` have been added to catalogs and collections. +- API Transaction extension supports optimistic locking through use of the ETag header. +- Asset Definition Extension added to Collections to allow specifying details about Assets that may appear in member Items. +- [Single File Catalog extension](extensions/single-file-stac/README.md) added as a format to have a set of Collections and Items in a single file. +- [Label extension](extensions/label/README.md) added with additional fields for describing labeled data, such as used for training data or from the output of a classification +- Timestamp fields added to `Item`: `created` and `updated` to refer to the datetime the metadata file was created or updated. +- Added Search Metadata API extension which adds fields to a response from a STAC API such as the number of items found and how many were returned. +- ItemCollection class added to spec that is a GeoJSON FeatureCollection of Items, such as what would be returned from a search. Located in item directory. +- `in` operator added to the query extension (to check if value is in a list of values) +- New bands added to the [common band names](extensions/eo/README.md#common-band-names) for the EO extension: yellow, rededge, and 2 narrow NIR bands +- [Scientific extension](extensions/scientific/README.md) can be used in Collections. + +### Fixed +- Updated language, fixed typos and examples. +- Renamed `pc:schema` to `pc:schemas` in the Point Cloud extension. + +### Changes since 0.8.0rc1 +- [Label extension](extensions/label/README.md): + - moved label:classes to be a list of Class Objects from a single Class Object in spec markdown and json schema (matching previous example JSON). + - moved label:overview to be a list of Overview Objects from a single Overview Object in spec markdown and json schema (matching previous example JSON). + - Renamed fields to use plural forms (`label:property` -> `label:properties`, `label:task` -> `label:tasks`, `label:method` -> `label:methods` and `label:overview` -> `label:overviews`) + +## [v0.7.0] - 2019-05-06 + +### Fixed +- Updated language / fixed typos +- Moved static vs dynamic discussion text to catalog best practices document +- Moved hosting of interactive api docs from swaggerhub to [stacspec.org](http://stacspec.org) +- JSON Schemas are now all following the latest JSON Schema version, draft 07. + +### Changed +- No longer require an absolute self link for Items, Collections and Catalogs. +- Reorganized api-spec, added extensions folder to hold API extensions +- Change the fields parameter in the API to allow filtering on any property. +- Refinements to SAR extension, changed several fields from a single array-based field (`sar:absolute_orbit`, `sar:resolution`, `sar:pixel_spacing`, `sar:looks`) to multiple fields with exactly one value. +- Commons extension ability to 'merge' properties is now in the core specification + +### Added +- Catalog best practices document, including recommendations for catalog layouts, html, and self-contained catalogs. +- `page` parameter for STAC API +- Optional `collection` property field in Items (previously part of the Commons extension) +- It is now required to link to `/stac/search/` from `/stac/` +- Added new fields to SAR extension: `sar:incidence_angle`, `sar:relative_orbit`, `sar:observation_direction` +- Added new filter parameters `ids` and `collections` to `/stac/search/` + +### Removed +- Removed the field `sar:off_nadir` from the SAR extension +- JavaScript-based validation + +## [v0.6.2] - 2019-03-01 + +### Fixed +- Fixed several examples and typos, improved descriptions +- Strictly checking the STAC version numbers in the JSON schemas +- Added missing required fields in Item JSON schema +- Changed `id` to `$id` in JSON schemas (draft-06 compatibility) + +### Changed +- Extensions require examples and a JSON schema to get to the maturity level 'Pilot' +- Updated ISERV implementation + +### Added +- Checksum extension +- Data Cube extension +- Point Cloud extension +- SAR extension + +## [v0.6.1] - 2019-01-25 + +### Fixed +- Added `null` as potential data type to `eo:epsg` in the EO extension. +- Fixed several examples and typos. +- Updated JSON Schema versions for uniformity +- Added OpenEO GEE implementation +- Clarified 'intersects' behavior for STAC API + +## [v0.6.0] - 2018-11-06 + +### Fixed +- Reorganized and cleaned up repository. +- Fixed examples throughout. + +### Added +- **Changelog**: This changelog added. +- **Collections added**: Collections are a type of Catalog with additional fields, such as provider and license. Items must belong to a single Collection. +- **Extension maturity**: Protocol for providing maturity classification for extensions based on stability and implementations. +- **Commons extension**: The previous 'Collections' extension is now the 'Commons' extension and allows an Item to inherit properties from its Collection. +- **Datetime-range extension**: Extension for providing start and end datetimes. +- **Scientific extension**: Extension for providings links to scientific publications relating to the data. +- **rel types**: A list of supported `rel` types are provided for use when specifying links, `derived_from` and `license` types added. +- **eo:constellation**: A new field in the EO specification to specify a grouping of platforms. +- **stac_version**: The `stac_version` field is required on all Catalogs (and Collections). +- **JSON schemas**: Added JSON schemas where they were missing. +- **Single Item extension**: Extension to supply License and Providers for single Items when no collection is used. +- **UML Diagram**: See STAC-060-uml.pdf. +- **Development Process**: See process.md for information on the STAC development process. + +### Changed +- **API**: Main catalog endpoint at `/stac`, search endpoint now at `/stac/search`. +- **eo:bands**: The `eo:bands` field is now an array rather than a dictionary, and has been moved inside of `properties` in a STAC Item. +- **Catalog fields**: Catalogs have a smaller number of basic fields: `id`, `stac_version`, `title` (optional), `description`, and `links`. The new Collections type contains additional fields. +- **links**: The links fields are now an array rather than a dictionary. +- **properties**: Fields with the data type array or objects are allowed inside the `properties` in a STAC Item. +- **description**: Description fields now allow formatting with CommonMark. +- **assets**: Fields changed names: `name` to `title` and `mime_type` to `type`. + +### Removed: +* **provider**: Provider field in Items got removed. Use Collections or the Single Item extension instead. +* **license**: License field in Items got removed. Use Collections or the Single Item extension instead. + +## [v0.5.2] - 2018-07-12 + +Minor bug fixes on 0.5.1 for the schema files. Thanks @francbartoli + +## [v0.5.1] - 2018-07-06 + +Minor bug fixes from 0.5.1 release + +* [Update openapi / swagger specs for new 'links'](https://github.com/radiantearth/stac-spec/commit/480d4fb02b4a7e880c7ca01320fe2773260ba595) +* [minor fixes on collection extension](https://github.com/radiantearth/stac-spec/pull/124) - thanks @m-mohr +* [minor cbers example updates](https://github.com/radiantearth/stac-spec/pull/123) - thanks @fredliporace + +## [v0.5.0] - 2018-07-01 + +The 0.5.0 release of the STAC spec is an iteration forward on the spec, with a number of core improvements. Highlights include: + +* **Links is now a dictionary** - This is the most core change done. It aligns the structure with the 'asset' change in 0.5.0, making it easier for clients to look up the link that they want more easily. The schema is updated to this (and actually checks assets better now, thanks @mojodna ) + +* **Transactions Extension** - There is now a transaction extension for the STAC API, thanks to @hgs-msmith and @hgs-trutherford + +* **Collections iterations** @matthewhanson has evolved the collections extension, adding in some namespace type hints on it, and explaining it more clearly. + +* **eo:crs to eo:epsg** In the EO profile @matthewhanson brought in a change to use EPSG code, instead of full Well Known Text, to make it easy to reference. + +Full list of issues and pull requests at + +## [v0.4.1] - 2018-04-24 + +A few minor improvements on the release. ([issues](https://github.com/radiantearth/stac-spec/issues?utf8=%E2%9C%93&q=milestone%3A0.4.1+)) + +* @hgs-msmith got a swagger version of the spec, and made some minor improvements to the openapi version #103 and #102 +* @francbartoli and @m-mohr pointed out some inconsistencies with landsat, so got the openapi updated #106 +* @m-mohr pointed out some issues with landsat example, so updated those #105 +* @hgs-trutherford pointed out that the planet example was a bit confusing, so updated it to the EO profile. + +## [v0.4.0] - 2018-04-06 + +The 0.4.0 is the first 'official' release of the SpatioTemporal Asset Catalog (STAC) specification! + +It is the result of the [ft. collins sprint](https://github.com/radiantearth/community-sprints/tree/master/03072018-ft-collins-co), the second in person meeting of the STAC community. But it also includes +a number of improvements from remote contributors. + +Highlights include: + +* Updates to the core **`Item` JSON specification**, including simplifying to a single datetime, moving thumbnails from 'links' to 'assets', making assets a dictionary for easier lookup and requiring `self` links to be absolute links. + +* Alignment of **STAC API** with the new [WFS3](https://github.com/opengeospatial/WFS_FES/) specification + +* Cleanup of the **static catalog** specification for greater clarity around the catalog + +* A first cut of an **Earth Observation Profile**, as well as a new collections extension to support it. + +* Numerous small improvements and bug fixes. + +See the [milestone 0.4.0 in the issue tracker](https://github.com/radiantearth/stac-spec/milestone/3) for the complete lists of improvements. + +Thanks @hgs-msmith, @matthewhanson, @hgs-trutherford, @rouault, @joshfix, @alkamin, @hemphillda, @jeffnaus and @fredliporace for contributing to the spec directly, and to [everyone](https://github.com/opengeospatial/wfs3hackathon/blob/master/notes/introductions.md#participants) who participated in the [Ft Collins sprint](https://github.com/radiantearth/community-sprints/tree/master/03072018-ft-collins-co) and brought great ideas. + + +[Unreleased]: +[v1.0.0-rc.2]: +[v1.0.0-rc.1]: +[v1.0.0-beta.2]: +[v1.0.0-beta.1]: +[v0.9.0]: +[v0.8.1]: +[v0.8.0]: +[v0.7.0]: +[v0.6.2]: +[v0.6.1]: +[v0.6.0]: +[v0.5.2]: +[v0.5.1]: +[v0.5.0]: +[v0.4.1]: +[v0.4.0]: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b3776240 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [stac-spec-admins@googlegroups.com](mailto:stac-spec-admins@googlegroups.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at + +[homepage]: + +For answers to common questions about this code of conduct, see + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6fd9042d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +## Contributing + +Pull Requests are the primary method of contributing to the spec itself, and everyone is welcome to submit +changes. Suggestions for changes to the core will be taken with higher priority if a clear implementation +of STAC has been built that can highlight the problem. If the changes can be done as an [extension](extensions/) +instead of modifying the core files then that route is recommended first, and once there is uptake for the +extension it will be considered for core. + +We consider everyone using the specification to catalog their data to be a 'contributor', as STAC is +really about the end result of more interoperable data, not just creating a spec for the sake of it. +So if you want to help then the best thing you can do is make new catalogs or build software that uses +the spec. And please do give us feedback. The best place to do so is on our +[gitter channel](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby). If you are interested in helping +but aren't sure where to, then see the [stac-ecosystem](https://github.com/stac-utils/stac-ecosystem) repo +for ideas on projects to advance STAC. The next phase of STAC's evolution will be mostly focused on +this broader ecosystem, while keeping the core spec as stable as we can. + +### Submitting Pull Requests + +Any proposed changes to the specification should be done as pull requests. Please make these +requests against the [dev](https://github.com/radiantearth/stac-spec/tree/dev) branch (this will +require you to switch from the default of 'master', which we keep so it displays first). + +Creating a Pull Request will show our PR template, which includes checkbox reminders for a number +of things. + +* Adding an entry the [CHANGELOG](CHANGELOG.md). If the change is more editorial and minor then this +is not required, but any change to the actual specification should definitely have one. +* Base the PR against dev, as mentioned above - even if the branch was made off of dev this reminds +you to change the base in GitHub's PR creation page. +* Make a ticket in the STAC API repo if anything here affects there. +* Highlight if the PR makes breaking changes to the specification (in beta there can still be +select breaking changes, but after 1.0 this will change) + +All pull requests should submit clean markdown, which is checked by the continuous integration +system. Please use `npm run check` locally, as described in the [next section](#check-files), +to ensure that the checks on the pull request succeed. If it does not then you can look at the +mistakes online, which are the same as running `npm run check` locally would surface. + +All pull requests that modify or create JSON schema files or examples should use [JSON formatter](https://jsonformatter.org/) to keep files consistent across the repo. + +All pull requests additionally require a review of two STAC core team members. Releases are cut +from dev to master (and require 3 approvals), see the [process](process.md) document for more details. + +### Check files + +The same check-markdown and check-examples programs that runs as a check on PR's is part of the repo and can be run locally. +To install you'll need npm, which is a standard part of any [node.js installation](https://nodejs.org/en/download/). Alternatively, you can also use [yarn](https://yarnpkg.com/) instead of npm. In this case replace all occurrences of `npm` with `yarn` below. + +First you'll need to install everything with npm once. Just navigate to the root of the stac-spec repo and on +your command line run: + +```bash +npm install +``` + +Then to do the check for markdown and examples you run: + +```bash +npm run check +``` + +This will spit out the same texts that you see online, and you can then go and fix your markdown or examples. + +To just check the markdown run: + +```bash +npm run check-markdown +``` + +To just check the examples run: + +```bash +npm run check-examples +``` + +To automatically format / pretty-print the examples run: + +```bash +npm run format-examples +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..cbe1c3e3 --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +stac-logo + +[![CircleCI](https://circleci.com/gh/radiantearth/stac-spec.svg?style=svg)](https://circleci.com/gh/radiantearth/stac-spec) + +## About + +The SpatioTemporal Asset Catalog (STAC) family of specifications aim to +standardize the way geospatial asset metadata is structured and queried. +A "spatiotemporal asset" is any file that represents information about +the Earth at a certain place and time. The original focus was on scenes +of satellite imagery, but the specifications now cover a broad variety of uses, +including sources such as aircraft and drone and data such as hyperspectral optical, +synthetic aperture radar (SAR), video, point clouds, lidar, digital elevation +models (DEM), vector, machine learning labels, and composites like NDVI and +mosaics. STAC is intentionally designed with a minimal core and flexible +extension mechanism to support a broad set of use cases. + +This is advantageous to providers of geospatial data, as they can simply use a +well-designed, standard format and API without needing to design their own proprietary one. +This is advantageous to consumers of geospatial data, as they can use existing libraries +and tools to access metadata, instead of needing to write new code to interact +with each data provider's proprietary formats and APIs. + +The STAC specifications define related JSON object types connected by link +relations to support a [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS)-style traversable +interface and a [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) API +providing additional browse and search interfaces. +Typically, several STAC specifications are composed together to create an implementation. +The **Item**, **Catalog**, and **Collection** specifications define a minimal core +of the most frequently used JSON object types. Because of the hierarchical structure +between these objects, a STAC catalog can be implemented in a completely 'static' +manner as a group of hyperlinked Catalog, Collection, and Item URLs, enabling data +publishers to expose their data as a browsable set of files. If more complex query +abilities are desired, such as spatial or temporal predicates, the +**STAC API** [specification](https://github.com/radiantearth/stac-api-spec/) can be +implemented as a web service interface to query over a group of STAC objects, usually +held in a database. + +To the greatest extent possible, STAC uses and extends existing specifications. +The most important object in STAC is an **Item**, which is simply a [GeoJSON](http://geojson.org) **Feature** +with a well-defined set of additional attributes ("foreign members"). The **STAC API** +extends the **[OGC API - Features - Part 1: Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html)** +with additional web service endpoints and object attributes. + +## Stability Note + +This specification has matured over the past several years, and is used in +[numerous production deployments](https://stacindex.org/catalogs). +With the 1.0.0 release, implementors should expect that most definitions will remain +stable. Our goal +is to maintain backwards-compatiblity within the core for a long time. +The STAC specification follows [Semantic Versioning](https://semver.org/), so once +1.0.0 is reached, any breaking change will require the spec to go to 2.0.0. + +## Current version and branches + +The [master branch](https://github.com/radiantearth/stac-spec/tree/master) is the 'stable' +version of the spec. It is currently version **1.0.0** of the specification. The +[dev](https://github.com/radiantearth/stac-spec/tree/dev) branch is where active development takes place, +and may have inconsistent examples. Whenever dev stabilizes, a release is cut and we +merge `dev` in to `master`. So `master` should be stable at any given time. +More information on how the STAC development process works can be found in +[process.md](process.md). + +## Communication + +Our [gitter channel](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby) is +the best place to ask questions or provide feedback. The majority of communication about the evolution of +the specification takes place in the [issue tracker](https://github.com/radiantearth/stac-spec/issues) and in +[pull requests](https://github.com/radiantearth/stac-spec/pulls). + +## In this Repository + +This repository contains the core object type specifications, [examples](examples/), +validation schemas, and documentation about the context and plans for the evolution of the +specification. Each folder contains a README explaining the layout of the folder, +the main specification document, examples, and validating schemas. + +Additionally, the [STAC API specification](https://github.com/radiantearth/stac-api-spec/) +provides API endpoints, based on the [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) standard, +that enable clients to search for Item objects that match their filtering criteria. + +The **Item**, **Catalog**, **Collection**, and **STAC API** specifications are intended to be +used together, but are designed so each piece is small, self-contained, and reusable in other contexts. + +* **[Overview](overview.md)** describes the three core object type specifications and how they relate to one another. +* **[Item Specification](item-spec/)** defines a STAC **Item**, which is a [GeoJSON](http://geojson.org) **Feature** +with additional fields ("foreign members") for attributes like time and links to related entities and assets +(including thumbnails). This is the core entity that describes the data to be discovered. +* **[Catalog Specification](catalog-spec/)** specifies a structure to link various STAC Items together to be crawled or browsed. It is a +simple, flexible JSON file of links to Items, Catalogs or Collections that can be used in a variety of ways. +* **[Collection Specification](collection-spec/)** provides additional information about a spatio-temporal collection of data. +In the context of STAC it is most likely a related group of STAC Items that is made available by a data provider. +It includes things like the spatial and temporal extent of the data, the license, keywords, etc. +It enables discovery at a higher level than individual Item objects, providing a simple way to describe sets of data. +* **[Examples](examples/):** The *[examples/](examples/)* folder contains examples for all three specifications, linked together to form two +complete examples. Each spec and extension links in to highlight particular files that demonstrate key concepts. +* **[Extensions](extensions/README.md)** describe how STAC can use extensions that extend the functionality of the core spec or +add fields for specific domains. Extensions can be published anywhere, although the preferred location for public extensions is in the [GitHub `stac-extensions` organization](https://github.com/stac-extensions). +* **Additional documents:** The supporting documents include a complementary [best practices](best-practices.md) +document, and information on contributing (links in the next section). We also maintain a [changelog](CHANGELOG.md) of +what was modified in each version. + +## Contributing + +Anyone building software that catalogs imagery or other geospatial assets is welcome to collaborate. +Beforehand, please review our [guidelines for contributions](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md). +You may also be interested in our overall [process](process.md), and the [principles](principles.md) that guide our +collaboration diff --git a/STAC-UML.drawio b/STAC-UML.drawio new file mode 100644 index 00000000..fca34d35 --- /dev/null +++ b/STAC-UML.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/STAC-UML.pdf b/STAC-UML.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c3e02556007c08643fd135354dab0dc088fd2f07 GIT binary patch literal 46890 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 state-level) and +then year, month, day. Or do the opposite - date and then location. Making a huge unordered list is technically allowed, +but not helpful for discovery of data. Thus it is generally considered a best practice to make use of sub-catalogs to +keep the size of each sub-catalog under a megabyte. If your sub-catalog lists tens of thousands of child items then you +should consider an additional way to break it up. + +We encourage people to explore new structures of linking data, but the following list is what a number of implementors +ended up doing. Following these recommendations makes for more legible catalogs, and many tools operate more efficiently +if you follow these recommendations. + +1. Root documents (Catalogs / Collections) should be at the root of a directory tree containing the static catalog. +2. Catalogs should be named `catalog.json` and Collections should be named `collection.json`. +3. Items should be named `.json`. +4. Sub-Catalogs should be stored in subdirectories of their parent (and only 1 subdirectory deeper than a document's parent) (e.g. `.../sample/sub1/catalog.json`). +5. Items should be stored in subdirectories of their parent Catalog. +This means that each Item and its assets are contained in a unique subdirectory. +6. Limit the number of Items in a Catalog or sub-Catalog, grouping / partitioning as relevant to the dataset. +7. Use structural elements (Catalog and Collection) consistently across each 'level' of your hierarchy. For example, if levels 2 and 4 of the hierarchy only contain Collections, +don't add a Catalog at levels 2 and 4. + +#### Dynamic Catalog Layout + +While these recommendations were primarily written for [static catalogs](#static-catalogs), they apply +equally well to [dynamic catalogs](#dynamic-catalogs). Subdirectories of course would just be URL paths +generated dynamically, but the structure would be the same as is recommended. + +One benefit of a dynamic catalog is that it can generate various 'views' of the catalog, exposing the same Items in +different sub-catalog organization structures. For example one catalog could divide sub-catalogs by date and another +by providers, and users could browse down to both. The leaf Items should just be linked to in a single canonical location +(or at least use a rel link that indicates the location of the canonical one). It is recommended that dynamic catalogs +provide multiple 'views' to allow users to navigate in a way that makes sense to them, providing multiple 'sub-catalogs' +from the root Catalog that enable different paths to browse (country/state, date/time, constellation/satellite, etc). But the +canonical 'rel' link should be used to designate the primary location of the Item to search engine crawlers. + +#### Mixing STAC Versions + +Although it is allowed to mix STAC versions, it should be used carefully as clients may not support all versions so that +the catalog could be of limited use to users. A Catalog or Collection linking to differently versioned Sub-Catalogs or Sub-Collections +is a common use case when multiple data source are combined. Client developers should be aware of this use case. Nevertheless, it +is strongly recommended that Catalogs don't contain differently versioned Items so that users/clients can at least use and/or download +consistent (Sub-)Catalogs containing either all or no data. Collections that are referenced from Items should always use the same +STAC version. Otherwise some behaviour of functionality may be unpredictable (e.g. merging common fields into Items or reading summaries). + +### Using Summaries in Collections + +One of the strongest recommendations for STAC is to always provide [summaries](collection-spec/collection-spec.md#summaries) in +your Collections. The core team decided to not require them, in case there are future situations where providing a summary +is too difficult. The idea behind them is not to exhaustively summarize every single field in the Collection, but to provide +a bit of a 'curated' view. + +Some general thinking on what to summarize is as follows: + +* Any field that is a range of data (like numbers or dates) is a great candidate to summarize, to give people a sense what values +the data might be. For example in overhead imagery, a +[`view:off_nadir`](https://github.com/stac-extensions/view/blob/main/README.md#item-properties-and-item-asset-fields) with a range of 0 to 3 would tell people this +imagery is all pretty much straight down, while a value of 15 to 40 would tell them that it's oblique imagery, or 0 to 60 that it's +a Collection with lots of different look angles. + +* Fields that have only one or a handful of values are also great to summarize. Collections with a single satellite may +use a single [`gsd`](item-spec/common-metadata.md#instrument) field in the summary, and it's quite useful for users to know +that all data is going to be the same resolution. Similarly it's useful to know the names of all the +[`platform` values](item-spec/common-metadata.md#instrument) that are used in the Collection. + +* It is less useful to summarize fields that have numerous different discrete values that can't easily be represented +in a range. These will mostly be string values, when there aren't just a handful of options. For example if you had a +'location' field that gave 3 levels of administrative region (like 'San Francisco, California, United States') to help people +understand more intuitively where a shot was taken. If your Collection has millions of Items, or even hundreds, you don't want +to include all the different location string values in a summary. + +* Fields that consist of arrays are more of a judgement call. For example [`instruments`](item-spec/common-metadata.md#instrument) +is straightforward and recommended, as the elements of the array are a discrete set of options. On the other hand +[`proj:transform`](https://github.com/stac-extensions/projection/blob/main/README.md#projtransform) makes no sense to summarize, as the union of all the values +in the array are meaningless, as each Item is describing its transform, so combining them would just be a bunch of random numbers. +So if the values contained in the array are independently meaningful (not interconnected) and there aren't hundreds of potential +values then it is likely a good candidate to summarize. + +We do highly recommend including an [`eo:bands`](https://github.com/stac-extensions/eo/blob/main/README.md#eobands) summary if your Items implement `eo:bands`, +especially if it represents just one satellite or constellation. This should be a union of all the potential bands that you +have in assets. It is ok to only add the summary at the Collection level without putting an explicit `eo:bands` summary at the +`properties` level of an Item, since that is optional. This gives users of the Collection a sense of the sensor capabilities without +having to examine specific Items or aggregate across every Item. + +Note that the ranges of summaries don't have to be exact. If you are publishing a catalog that is constantly updating with +data from a high agility satellite you can put the `view:off_nadir` range to be the expected values, based on the satellite +design, instead of having it only represent the off nadir angles that are Items for assets already captured in the catalog. +We don't want growing catalogs to have to constantly check and recalculate their summaries whenever new data comes in - its +just meant to give users a sense of what types of values they could expect. + +### Use of links + +The STAC specifications allow both relative and absolute links, and says that `self` links are not required, but are +strongly recommended. This is what the spec must say to enable the various use cases, but there is more subtlety for when it +is essential to use different link types. The best practice is to use one of the below catalog types, applying the link +recommendations consistently, instead of just haphazardly applying relative links in some places and absolute ones in other places. + +#### Self-contained Catalogs + +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. Or a tool that creates catalogs may need to work +without knowing the final location that it will live at online, so it isn't possible to set absolute 'self' URL's. These use +cases should utilize a catalog that follows the listed principles: + +* **Only relative href's in structural `links`**: The full catalog structure of links down to sub-catalogs and Items, and their +links back to their parents and roots, should be done with relative URL's. The structural rel types include `root`, `parent`, +`child`, `item`, and `collection`. Other links can be absolute, especially if they describe a resource that makes less sense in +the catalog, like [sci:doi](https://github.com/stac-extensions/scientific/blob/main/README.md#item-and-collection-fields), +`derived_from` or even `license` (it can be nice to include the license in the catalog, but some licenses live at a canonical +online location which makes more sense to refer to directly). This enables the full catalog to be downloaded or +copied to another location and to still be valid. This also implies no `self` link, as that link must be absolute. + +* **Use Asset `href` links consistently**: The links to the actual assets are allowed to be either relative or absolute. There +are two types of 'self-contained catalogs'. + +#### Self-contained Metadata Only + +These consist of just the STAC metadata (Collection, Catalog and Item files), and uses absolute href +links to refer to the online locations of the assets. + +#### Self-contained with Assets + +These use relative href links for the assets, and includes them in the folder structure. +This enables offline use of a catalog, by including all the actual data, referenced locally. + +Self-contained catalogs tend to be used more as static catalogs, where they can be easily passed around. But often they will +be generated by a more dynamic STAC service, enabling a subset of a catalog or a portion of a search criteria to be downloaded +and used in other contexts. That catalog could be used offline, or even published in another location. + +Self-contained catalogs are not just for offline use, however - they are designed to be able to be published online and to live +on the cloud in object storage. They just aim to ease the burden of publishing, by not requiring lots of updating of links. +Adding a single `self` link at the root is recommended for online catalogs, turning it into a 'relative published catalog', as detailed below. This anchors it in an online location and enables provenance tracking. + +#### Published Catalogs + +While STAC is useful as a portable format to move between systems, the goal is really to enable search. While any combination +of absolute and relative links is technically allowed by the specification, it is strongly recommended to follow one of the +patterns described below when publishing online. Many clients will not properly handle arbitrary mixes of absolute and relative +href's. + +We refer to a 'published catalog' as one that lives online in a stable location, and uses `self` links to establish its location and +enable easy provenance tracking. There are two types of published catalogs: + +#### Absolute Published Catalog + +This is a catalog that uses absolute links for everything, both in the `links` objects and in the +`asset` hrefs. It includes `self` links for every Item. Generally these are implemented by dynamic catalogs, as it is quite +easy for them to generate the proper links dynamically. But a static catalog that knows its published location could easily +implement it. + +#### Relative Published Catalog + +This is a self-contained catalog as described above, except it includes an absolute `self` link at +the root catalog, to identify its online location. This is designed so that a self-contained catalog (of either type, with its +assets or just metadata) can be 'published' online +by just adding one field (the self link) to its root catalog. All the other links should remain the same. The resulting catalog +is no longer compliant with the self-contained catalog recommendations, but instead transforms into a 'relative published catalog'. +With this, a client may resolve Item and sub-catalog self links by traversing parent and root links, but requires reading +multiple sources to achieve this. + +So if you are writing a STAC client it is recommended to start with just supporting these two types of published catalogs. In +turn, if your data is published online publicly or for use on an intranet then following these recommendations will ensure +that a wider range of clients will work with it. + +### Using Relation Types + +Implementors of STAC are highly recommended to be quite liberal with their `links`, and to use the `rel` field (in conjunction +with the `type` field) to communicate the structure and content of related entities. While each STAC spec describes some of the +'custom' relations STAC has set, the ideal is to reuse official [IANA Link Relation +Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) as much as possible. The following table describes +a number of the common official relations that are used in production STAC implementations. + +| Type | Description | +| ------------ | ------------------------------------------------------------ | +| alternate | It is recommended that STAC Items are also available as HTML, and should use this rel with `"type" : "text/html"` to tell clients where they can get a version of the Item or Collection to view in a browser. See [STAC on the Web in Best Practices](#stac-on-the-web) for more information. | +| canonical | The URL of the [canonical](https://en.wikipedia.org/wiki/Canonical_link_element) version of the Item or Collection. API responses and copies of catalogs should use this to inform users that they are direct copy of another STAC Item, using the canonical rel to refer back to the primary location. | +| via | The URL of the source metadata that this STAC Item or Collection is created from. Used similarly to canonical, but refers back to a non-STAC record (Landsat MTL, Sentinel tileInfo.json, etc) | +| prev | Indicates that the link's context is a part of a series, and that the previous in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs. | +| next | Indicates that the link's context is a part of a series, and that the next in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs. | + +### Versioning for Catalogs + +In the Item and Collection STAC JSON, versions and deprecation can be indicated with the [Versioning Indicators Extension](https://github.com/stac-extensions/version). + +The [Items and Collections API Version Extension](https://github.com/stac-extensions/version/) provides endpoints and +semantics for keeping and accessing previous versions of Collections and Items. The same semantics can be used in static +catalogs to preserve previous versions of the documents and link them together. + +In order to achieve this, the static catalog must make sure that for every record created, a copy of the record is also +created in a separate location and it is named with the version id adopted by the catalog. See +[here](https://github.com/stac-extensions/version/blob/main/README.md#version-id) for recommendations on versioning schema. + +The main record should also provide a link to the versioned record following the linking patterns described +[here](https://github.com/stac-extensions/version/blob/main/README.md#relation-types). For every update to the record, the same +cycle is repeated: + +1. Add link from the updated record to the previous version +2. Create a copy of the updated record and name it correctly + +#### Example + +When the record `my_item.json` is created, a copy of it is also created. `my_item.json` includes `permalink` to `my_item_01.json`. The version suffix of the file name is taken from the version field of the record when it is available. + +- `root / collections / example_collection / items / my_item / my_item.json` +- `root / collections / example_collection / items / my_item / my_item_01.json` + +When `my_item.json` is updated, the new `my_item.json` includes a link to `my_item_01.json` and is also copied to `my_item_02.json`. This ensures that `my_item_02.json` includes a link to `my_item_01.json` + +- `root / collections / example_collection / items / my_item / my_item.json` +- `root / collections / example_collection / items / my_item / my_item_01.json` +- `root / collections / example_collection / items / my_item / my_item_02.json` + +### Static to Dynamic best practices + +Many implementors are using static catalogs to be the reliable core of their dynamic services, or layering their STAC API +on top of any static catalog that is published. These are some recommendations on how to handle this: + +#### Ingestion and links + +Implementors have found that it's best to 'ingest' a static STAC into an internal datastore (often elasticsearch, but a +traditional database could work fine too) and then generate the full STAC API responses from that internal representation. +There are instances that have the API refer directly to the static STAC Items, but this only works well if the static STAC +catalog is an 'absolute published catalog'. So the recommendation is to always use absolute links - either in the static +published catalog, or to create new absolute links for the STAC search/ endpoint +responses, with the API's location at the base url. The `/` endpoint with the catalog could either link directly +to the static catalog, or can follow the 'dynamic catalog layout' recommendations above with a new set of URL's. + +Ideally each Item would use its `links` to provide a reference back to the static location. The location of the static +Item should be treated as the canonical location, as the generated API is more likely to move or be temporarily down. The +spec provides the `derived_from` rel field, which fits well enough, but `canonical` is likely the more appropriate one +as everything but the links should be the same. + +#### Keep catalogs in sync with cloud notification and queue services + +There is a set of emerging practices to use services like Amazon's Simple Queue Service (SQS) and Simple Notification Service +(SNS) to keep catalogs in sync. There is a great [blog post on the CBERS STAC implementation on AWS](https://aws.amazon.com/blogs/publicsector/keeping-a-spatiotemporal-asset-catalog-stac-up-to-date-with-sns-sqs/). The core +idea is that a static catalog should emit a notification whenever it changes. The recommendation for SNS is to use the STAC +Item JSON as the message body, with some fields such as a scene’s datetime and geographic bounding box that allows +basic geographic filtering from listeners. + +The dynamic STAC API would then listen to the notifications and update its internal datastore whenever new data comes into +the static catalog. Implementors have had success using AWS Lambda to do a full 'serverless' updating of the elasticsearch +database, but it could just as easily be a server-based process. + +## How to Differentiate STAC Files + +Any tool that crawls a STAC implementation or encounters a STAC file in the wild needs a clear way to determine if it is an Item, +Collection, Catalog or [ItemCollection](https://github.com/radiantearth/stac-api-spec/tree/v1.0.0-beta.1/fragments/itemcollection) +(part of the [STAC API spec](https://github.com/radiantearth/stac-api-spec/tree/v1.0.0-beta.1)). As of 1.0.0 this is done primarily +with the `type` field, and secondarily in Items with `stac_version`, or optionally the `rel` of the link to it. + +```shell +if type is 'Collection' + => Collection +else if type is 'Catalog' + => Catalog +else if type is 'Feature' and stac_version is defined + => Item +else if type is 'FeatureCollection' and stac_version is defined + => ItemCollection +else + => Invalid (JSON) +``` + +When crawling a STAC implementation, one can also make use of the [relation type](catalog-spec/catalog-spec.md#relation-types +) (`rel` field) when following a link. If it is an `item` rel type then the file must be a STAC Item. If it is `child`, `parent` or +`root` then it must be a Catalog or a Collection, though the final determination between the two requires looking at the the `type` field +in the Catalog or Collection JSON that is linked to. Note that there is also a `type` field in STAC Link and Asset objects, but that +is for the Media Type, but there are not specific media types for Catalog and Collection. See the sections on [STAC media +types](catalog-spec/catalog-spec.md#media-types), and [Asset media types](item-spec/item-spec.md#asset-media-type) for more information. + +In versions of STAC prior to 1.0 the process was a bit more complicated, as there was no `type` field for catalogs and collections. +See [this issue comment](https://github.com/radiantearth/stac-spec/issues/889#issuecomment-684529444) for a heuristic that works +for older STAC versions. diff --git a/catalog-spec/README.md b/catalog-spec/README.md new file mode 100644 index 00000000..a2626d22 --- /dev/null +++ b/catalog-spec/README.md @@ -0,0 +1,34 @@ +# STAC Catalog Specification + +A STAC [Catalog](catalog-spec.md) is a top-level object that logically groups other Catalog, Collection, +and [Item](../item-spec/item-spec.md) objects. A Catalog contains an array of Link objects to these other +objects and can include additional metadata to describe the objects contained therein. It is defined in full +in the [STAC Catalog Specification](catalog-spec.md). + +For more information on how the parts of STAC fit together see the [overview](../overview.md) document. + +A Catalog is typically the "entry point" into a STAC object hierarchy. For example, the root endpoint ("landing page") of a STAC API implementation is a Catalog. For many static STAC catalogs (e.g., those defined only by a set of files on disk or in a cloud object store), the root URL points to a Catalog that acts as the starting point to traverse the entire catalog of Catalog, Collection, and Item objects. + +While STAC Catalogs mostly describe a structure of links and Items, a key related specification is the [STAC Collection Specification](../collection-spec/collection-spec.md), +which contains fields that further describe the group of Items in a Catalog. + +A STAC Catalog requires a subset of the fields required by a Collection. These are distinguished from one another by the `type` field, which will have the value `Catalog` or `Collection`. This means that a Collection can be changed to a Catalog simply by changing this `type` field. The parent-child relationships among Catalogs and Collections are for objects of these types, as there is no subtyping relationship between the Collection and Catalog types, even through they share field names. + +Catalogs are designed so that a simple file server on the web or object store like Amazon S3 can store JSON that defines a +full Catalog. More dynamic services can also return a Catalog structure, and the [STAC API](https://github.com/radiantearth/stac-api-spec) +specification contains an OpenAPI definition of the standard way to do this, at the `/` endpoint. + +## In this directory + +**Specification:** The main Catalog specification is in *[catalog-spec.md](catalog-spec.md)*. +It includes in depth explanation of the structures and fields. + +**Schemas:** The schemas to validate the core Catalog definition are found in the *[json-schema/](json-schema/)* folder. +The primary one is *[catalog.json](json-schema/catalog.json)*. + +## Catalog Evolution + +The Catalog specification is maturing, but it is still relatively early days. The core of Catalog has been defined very +narrowly, to just describe a structure that can be followed by people or machines, so most additional functionality will +be defined in additional specifications and extensions. The only anticipated changes to the core of Catalog are to add in +additional extension mechanisms for others to use. diff --git a/catalog-spec/catalog-spec.md b/catalog-spec/catalog-spec.md new file mode 100644 index 00000000..032d85a3 --- /dev/null +++ b/catalog-spec/catalog-spec.md @@ -0,0 +1,127 @@ +# STAC Catalog Specification + +- [Catalog fields](#catalog-fields) + - [Additional Field Information](#additional-field-information) + - [stac_version](#stac_version) + - [stac_extensions](#stac_extensions) + - [Link Object](#link-object) + - [Relation types](#relation-types) +- [Media Types](#media-types) + - [Media Type for STAC Catalogs](#media-type-for-stac-catalogs) + - [STAC Media Types](#stac-media-types) +- [Extensions](#extensions) + +This document explains the structure and content of a STAC **Catalog** object. A STAC Catalog object +represents a logical group of other Catalog, +[Collection](../collection-spec/collection-spec.md), and [Item](../item-spec/item-spec.md) objects. +These Items can be linked to directly from a Catalog, or the Catalog can link to other Catalogs (often called +sub-catalogs) that contain links to Collections and Items. The division of sub-catalogs is up to the implementor, +but is generally done to aid the ease of online browsing by people. + +A Catalog object will typically be the entry point into a STAC catalog. Their +purpose is discovery: to be browsed by people or be crawled +by clients to build a searchable index. + +Any JSON object that contains all the required fields is a valid STAC Catalog object. + +- [Examples](../examples/) + - See an example [catalog.json](../examples/catalog.json). The [collection.json](../examples/collection.json) is also a valid + Catalog file, demonstrating linking to items (it is also a Collection, so has additional fields) +- [JSON Schema](json-schema/catalog.json) + +The [Catalog section of the Overview](../overview.md#catalog-overview) document provides background information on +the structure of Catalogs as well as links to best practices. This specification lays out the requirements +and fields to be compliant. + +This Catalog specification primarily defines a structure for information to be discoverable. Any use +that is publishing a set of related spatiotemporal assets is strongly recommended to also use the +STAC Collection specification to provide additional information about the set of Items +contained in a Catalog, in order to give contextual information to aid in discovery. Every STAC Collection is +also a valid STAC Catalog. + +## Catalog fields + +| Element | Type | Description | +| --------------- | ------------- | ------------------------------------------------------------ | +| type | string | **REQUIRED.** Set to `Catalog` if this Catalog only implements the Catalog spec. | +| stac_version | string | **REQUIRED.** The STAC version the Catalog implements. | +| stac_extensions | \[string] | A list of extension identifiers the Catalog implements. | +| id | string | **REQUIRED.** Identifier for the Catalog. | +| title | string | A short descriptive one-line title for the Catalog. | +| description | string | **REQUIRED.** Detailed multi-line description to fully explain the Catalog. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| summaries | Map | A map of property summaries, either a set of values or statistics such as a range. More info in the [Collection spec](../collection-spec/collection-spec.md#summaries). | +| links | [[Link Object](#link-object)] | **REQUIRED.** A list of references to other documents. | + +### Additional Field Information + +#### stac_version + +In general, STAC versions can be mixed, but please keep the [recommended best practices](../best-practices.md#mixing-stac-versions) in mind. + +#### stac_extensions + +A list of extensions the Catalog implements. +This list must only contain extensions that extend the Catalog itself, see the the 'Scope' column in the list of +extensions. This does NOT declare the extensions of child Catalog, Collection, or Item +objects. The list contains URLs to the JSON Schema files it can be validated against. + +### Link Object + +This object describes a relationship with another entity. Data providers are advised to be liberal +with links. + +| Field Name | Type | Description | +| ---------- | ------ | ----------- | +| href | string | **REQUIRED.** The actual link in the format of an URL. Relative and absolute links are both allowed. | +| rel | string | **REQUIRED.** Relationship between the current document and the linked document. See chapter ["Relation types"](#relation-types) for more information. | +| type | string | [Media type](#media-types) of the referenced entity. | +| title | string | A human readable title to be used in rendered displays of the link. | + +For a full discussion of the situations where relative and absolute links are recommended see the +['Use of links'](../best-practices.md#use-of-links) section of the STAC best practices. + +#### Relation types + +The following types are commonly used as `rel` types in the Link Object of a STAC Catalog: + +| Type | Description | +| ------- | ----------- | +| self | STRONGLY RECOMMENDED. *Absolute* URL to the location that the Catalog file can be found online, if available. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | +| root | STRONGLY RECOMMENDED. URL to the root STAC Catalog or [Collection](../collection-spec/README.md). Catalogs should include a link to their root, even if it's the root and points to itself. | +| parent | URL to the parent STAC Catalog or Collection. Non-root Catalogs should include a link to their parent. | +| child | URL to a child STAC Catalog or Collection. | +| item | URL to a STAC Item. | + +**Note:** A link to at least one `item` or `child` Catalog is **REQUIRED**. + +There are additional `rel` types in the [Using Relation Types](../best-practices.md#using-relation-types) best practice, but as +they are more typically used in Collections, as Catalogs tend to just be used to structure STAC organization, so tend to just use +the ones above. + +## Media Types + +One of the best ways to help inform web clients about the content in a link is to use a common [Media +Type](https://en.wikipedia.org/wiki/Media_type) in the `type` field. In STAC the `type` field is used in both the +'[Link](#link-object)'' and '[Asset](../item-spec/item-spec.md#asset-object)' Objects. It is quite useful for STAC browsers to better determine +what to render and display to users searching and browsing the Catalog. Media types are often referred to by the +now deprecated term "MIME types". + +### Media Type for STAC Catalogs + +A STAC Catalog is a JSON file ([RFC 8259](https://tools.ietf.org/html/rfc8259)), and thus should use the +[`application/json`](https://tools.ietf.org/html/rfc8259#section-11) as the [Media Type](https://en.wikipedia.org/wiki/Media_type) +(previously known as the MIME Type). + +### STAC Media Types + +The following table lists the Media Types to use for STAC structures. + +| Media Type | Description | +| ---------------------- | ---------------------------------------------------------- | +| `application/geo+json` | A STAC [Item](../item-spec/item-spec.md) | +| `application/json` | A STAC Catalog | +| `application/json` | A STAC [Collection](../collection-spec/collection-spec.md) | + +## Extensions + +The [extensions page](../extensions/) gives an overview about relevant extensions for STAC Catalogs. diff --git a/catalog-spec/json-schema/catalog-core.json b/catalog-spec/json-schema/catalog-core.json new file mode 100644 index 00000000..503d7a66 --- /dev/null +++ b/catalog-spec/json-schema/catalog-core.json @@ -0,0 +1,139 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog-core.json#", + "title": "Common STAC Catalog + Collection Fields", + "description": "This object represents the common fields shared by Catalogs and Collections", + "allOf": [ + { + "$ref": "#/definitions/catalogCore" + } + ], + "definitions": { + "catalogCore": { + "title": "Catalog Core Fields", + "type": "object", + "required": [ + "stac_version", + "id", + "description", + "links" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0-rc.2" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "anyOf": [ + { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + }, + { + "title": "Reference to a core extension", + "type": "string" + } + ] + } + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "title": { + "title": "Title", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "minLength": 1 + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "summaries": { + "$ref": "#/definitions/summaries" + } + } + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + }, + "summaries": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "title": "Stats", + "type": "object", + "required": [ + "minimum", + "maximum" + ], + "properties": { + "minimum": { + "title": "Minimum value", + "type": [ + "number", + "string" + ] + }, + "maximum": { + "title": "Maximum value", + "type": [ + "number", + "string" + ] + } + } + }, + { + "title": "Set of values", + "type": "array", + "minItems": 1, + "items": { + "description": "Any data type could occur." + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/catalog-spec/json-schema/catalog.json b/catalog-spec/json-schema/catalog.json new file mode 100644 index 00000000..0f2c110f --- /dev/null +++ b/catalog-spec/json-schema/catalog.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog.json#", + "title": "STAC Catalog Specification", + "description": "This object represents Catalogs in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "catalog-core.json" + }, + { + "$ref": "#/definitions/catalog" + } + ], + "definitions": { + "catalog": { + "title": "Catalog", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "title": "Type of STAC entity", + "const": "Catalog" + } + } + } + } +} diff --git a/collection-spec/README.md b/collection-spec/README.md new file mode 100644 index 00000000..6b5a0233 --- /dev/null +++ b/collection-spec/README.md @@ -0,0 +1,43 @@ +# STAC Collection Specification + +A STAC [Collection](collection-spec.md) object is used to describe a group of related +Items. It builds on fields defined for a [Catalog](../catalog-spec/catalog-spec.md) object +by further defining and explaining logical groups of [Items](../item-spec/item-spec.md). A +Collection can have parent Catalog and Collection objects, as well as child Item, Catalog, +and Collection objects. These parent-child relationships among objects of these types, as there is no +subtyping relationship between the Collection and Catalog types, even through they share field names. + +A Collection provides a flexible mechanism to provide additional metadata about a set of Items. +Generally, is used to describe a set of assets that +are defined with the same properties and share higher-level metadata. There is no +standardized name for this sort of logical grouping, but other places it is called a " +dataset series" (ESA, ISO 19115), "collection" (CNES, NASA), "dataset" (JAXA), or "product" +(JAXA). In GIS terms, the Items are +'[features](https://en.wikipedia.org/wiki/Simple_Features)' (that link to assets) and +a Collection is the 'layer'. STAC uses the same terms as the +[OGC Features API](https://ogcapi.ogc.org/features/). A STAC Collection is a valid +[Feature API Collection](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#example_4), +extending it with additional fields. + +Thus, the additional fields in a Collection detail the type of information a user would want to +know about the group of Items it contains. Items are required to provide a link back to their +collection definition. But the Collection is independent of STAC Items and STAC Catalogs, and thus +other parties can also use this specification standalone, as a way to describe collections in a +lightweight way. For more details on how the STAC specs fit together see the [overview](../overview.md) +document. + +## In this directory + +**Specification:** The main STAC Collection specification is in *[collection-spec.md](collection-spec.md)*. It includes an overview and in depth explanation of the +structures and fields. + +**Schemas:** The schemas to validate the STAC Collection definition are found in the +*[json-schema/](json-schema/)* folder. The primary one is *[collection.json](json-schema/collection.json)*. + +## Collection Flexibility + +STAC Collections are defined for flexibility. They only require a handful of fields, and +implementors are free to add most any JSON field or object that they want via extensions. This flexibility and extensibility is a design goal, so that it is quite easy to implement a collection and be able to adapt it to most any data model. + +Implementors are encouraged to do what makes sense for them, and to check out the [examples](../examples/) and +[other implementations](https://stacindex.org/catalogs) for current best practices. diff --git a/collection-spec/collection-spec.md b/collection-spec/collection-spec.md new file mode 100644 index 00000000..f781bb91 --- /dev/null +++ b/collection-spec/collection-spec.md @@ -0,0 +1,260 @@ +# STAC Collection Specification + +- [Overview](#overview) +- [Collection fields](#collection-fields) + - [Additional Field Information](#additional-field-information) + - [stac_version](#stac_version) + - [stac_extensions](#stac_extensions) + - [id](#id) + - [license](#license) + - [summaries](#summaries) + - [assets](#assets) + - [Extent Object](#extent-object) + - [Spatial Extent Object](#spatial-extent-object) + - [Temporal Extent Object](#temporal-extent-object) + - [Provider Object](#provider-object) + - [Link Object](#link-object) + - [Relation types](#relation-types) + - [Asset Object](#asset-object) + - [Stats Object](#stats-object) +- [Media Type for STAC Collections](#media-type-for-stac-collections) +- [Standalone Collections](#standalone-collections) + +## Overview + +The STAC Collection Specification defines a set of common fields to describe a group of Items that share properties and metadata. The +Collection Specification shares all fields with the STAC [Catalog Specification](../catalog-spec/catalog-spec.md) (with different allowed +values for `type` and `stac_extensions`) and adds fields to describe the whole dataset and the included set of Items. Collections +can have both parent Catalogs and Collections and child Items, Catalogs and Collections. + +A STAC Collection is represented in JSON format. Any JSON object that contains all the required fields is a valid STAC Collection and also a valid STAC Catalog. + +STAC Collections are compatible with the [Collection](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#example_4) JSON +specified in [*OGC API - Features*](https://ogcapi.ogc.org/features/), but they are extended with additional fields. + +* [Examples](../examples/): + * Sentinel 2: A basic standalone example of a [Collection](../examples/collection-only/collection.json) without Items. + * Simple Example: A [Collection](../examples/collection.json) that links to 3 example Items. + * Extension Collection: An additional [Collection](../examples/extensions-collection/collection.json), which is used to highlight + various [extension](../extensions) functionality, but serves as another example. +* [JSON Schema](json-schema/collection.json) + +## Collection fields + +| Element | Type | Description | +| --------------- | ------------------------------------------------ | ------------------------------------------------------------ | +| type | string | **REQUIRED.** Must be set to `Collection` to be a valid Collection. | +| stac_version | string | **REQUIRED.** The STAC version the Collection implements. | +| stac_extensions | \[string] | A list of extension identifiers the Collection implements. | +| id | string | **REQUIRED.** Identifier for the Collection that is unique across the provider. | +| title | string | A short descriptive one-line title for the Collection. | +| description | string | **REQUIRED.** Detailed multi-line description to fully explain the Collection. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| keywords | \[string] | List of keywords describing the Collection. | +| license | string | **REQUIRED.** Collection's license(s), either a SPDX [License identifier](https://spdx.org/licenses/), `various` if multiple licenses apply or `proprietary` for all other cases. | +| providers | \[[Provider Object](#provider-object)] | A list of providers, which may include all organizations capturing or processing the data or the hosting provider. Providers should be listed in chronological order with the most recent provider being the last element of the list. | +| extent | [Extent Object](#extent-object) | **REQUIRED.** Spatial and temporal extents. | +| summaries | Map | STRONGLY RECOMMENDED. A map of property summaries, either a set of values or statistics such as a range. | +| links | \[[Link Object](#link-object)] | **REQUIRED.** A list of references to other documents. | +| assets | Map | Dictionary of asset objects that can be downloaded, each with a unique key. | + +### Additional Field Information + +#### stac_version + +In general, STAC versions can be mixed, but please keep the [recommended best practices](../best-practices.md#mixing-stac-versions) in mind. + +#### stac_extensions + +A list of extensions the Collection implements. +This list must only contain extensions that extend the Collection itself, see the the 'Scope' column in the list of +extensions. This does NOT declare the extensions of child Collection or Item +objects. The list contains URLs to the JSON Schema files it can be validated against. + +If an extension has influence on multiple parts +of the whole STAC structure, it must be listed in all affected parts (e.g. Collection and Item for the `datacube` extension). +If a structure, such as the summaries extension, provides fields in their JSON structure, these extensions must not be listed +here as they don't extend the Collection itself. For example, if a Collection includes the field `sat:platform` in the +summaries, the Collection should not list the `sat` extension in the `stac_extensions` field. + +#### id + +It is important that Collection identifiers are unique across the provider. And providers should strive as much as possible to make +their Collection ids 'globally' unique, prefixing any common information with a unique string. This could be the provider's name if +it is a fairly unique name, or their name combined with the domain they operate in. + +#### license + +Collection's license(s) as a SPDX [License identifier](https://spdx.org/licenses/). Alternatively, use `proprietary` (see below) if the license is not on the SPDX license list or `various` if multiple licenses apply. In all cases links to the license texts SHOULD be added, see the `license` link relation type. If no link to a license is included and the `license` field is set to `proprietary`, the Collection is private, and consumers have not been granted any explicit right to use the data. + +#### summaries + +Collections are *strongly recommended* to provide summaries of the values of fields that they can expect from the `properties` +of STAC Items contained in this Collection. This enables users to get a good sense of what the ranges and potential values of +different fields in the Collection are, without to inspect a number of Items (or crawl them exhaustively to get a definitive answer). +Summaries help to fully define Collections, especially if they don't link to any Items. They also give clients enough information to +build tailored user interfaces for querying the data, by presenting the potential values that are available. Summaries should summarize all values in every Item underneath the collection, including in any nested sub-Catalogs. + +A summary for a field can be specified in two ways: + +1. A set of all distinct values in an array: The set of values must contain at least one element and it is strongly recommended to list all values. If the field summarizes an array (e.g. [`instruments`](../item-spec/common-metadata.md#instrument)), the field's array elements of each Item must be merged to a single array with unique elements. +2. Statistics in a [Stats Object](#stats-object): Statistics by default only specify the range (minimum and maximum values), but can optionally be accompanied by additional statistical values. The range specified by the `minimum` and `maximum` properties can specify the potential range of values, but it is recommended to be as precise as possible. + +All values must follow the schema of the property they summarize. So the values in the array or the values given for `minimum` and `maxmimum` must comply to the original data type and any further restrictions that apply for the property they summarize. For example, the `minimum` for `gsd` can't be lower than zero and the summaries for `platform` and `instruments` must each be an array of strings (or alternatively minimum and maximum values, but that's not very meaningful). + +It is recommended to list as many properties as reasonable so that consumers get a full overview about the properties included in the Items. Nevertheless, it is not very useful to list all potential `title` values of the Items. Also, a range for the `datetime` property may be better suited to be included in the STAC Collection's `extent` field. In general, properties that are covered by the Collection specification should not be repeated in the summaries. + +See the [examples folder](../examples) for Collections with summaries to get a sense of how to use them. + +#### assets + +This provides an optional mechanism to expose assets that don't make sense at the Item level. +It is a dictionary of [Asset Objects](#asset-object) associated with the Collection that can be +downloaded or streamed, each with a unique key. +In general, the keys don't have any meaning and are considered to be non-descriptive unique identifiers. +Providers may assign any meaning to the keys for their respective use cases, but must not expect that clients understand them. +To communicate the purpose of an asset better use the `roles` field in the [Asset Object](#asset-object). +The definition provided here, at the Collection level, is the same as the +[Asset Object in Items](../item-spec/item-spec.md#asset-object). + +There are a few guidelines for using the asset construct at the Collection level: + +* Collection-level assets SHOULD NOT list any files also available in Items. +* If possible, item-level assets are always the preferable way to expose assets. + +Collection-level assets can be useful in some scenarios, for example: +1. Exposing additional data that applies Collection-wide and you don't want to expose it in each Item. This can be Collection-level metadata or a thumbnail for visualization purposes. +2. Individual Items can't properly be distinguished for some data structures, e.g. [Zarr](https://zarr.readthedocs.io/) as it's a data structure not contained in single files. +3. Exposing assets for "[Standalone Collections](https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md#standalone-collections)". + +Oftentimes it is possible to model data and assets with either a Collection or an Item. In those scenarios we *recommend* to use +Items as much as is feasible, as they designed for assets. Using Collection-level assets should only be used if there is not another +option. + +### Extent Object + +The object describes the spatio-temporal extents of the Collection. Both spatial and temporal extents are required to be specified. + +| Element | Type | Description | +| -------- | ------------------------------------------------- | --------------------------------------------------------------------- | +| spatial | [Spatial Extent Object](#spatial-extent-object) | **REQUIRED.** Potential *spatial extents* covered by the Collection. | +| temporal | [Temporal Extent Object](#temporal-extent-object) | **REQUIRED.** Potential *temporal extents* covered by the Collection. | + +#### Spatial Extent Object + +The object describes the spatial extents of the Collection. + +| Element | Type | Description | +| ------- | ------------ | -------------------------------------------------------------------- | +| bbox | \[\[number]] | **REQUIRED.** Potential *spatial extents* covered by the Collection. | + +**bbox**: Bounding Boxes of the assets represented by this Collection using either 2D or 3D geometries. Each outer array element can be a separate bounding box, but it is recommended to only use multiple bounding boxes if a union of them would then include a large uncovered area (e.g. the union of Germany and Chile). + +The length of the inner array must be 2*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters and the elevation of the northeasterly most extent is the maximum. + +The coordinate reference system of the values is WGS 84 longitude/latitude. Example that covers the whole Earth: `[[-180.0, -90.0, 180.0, 90.0]]`. Example that covers the whole earth with a depth of 100 meters to a height of 150 meters: `[[-180.0, -90.0, -100.0, 180.0, 90.0, 150.0]]`. + +#### Temporal Extent Object + +The object describes the temporal extents of the Collection. + +| Element | Type | Description | +| -------- | ------------------ | --------------------------------------------------------------------- | +| interval | \[\[string\|null]] | **REQUIRED.** Potential *temporal extents* covered by the Collection. | + +**interval**: Each outer array element can be a separate temporal extent, but it is recommended to only use multiple temporal extents if a union of them would then include a large uncovered time span (e.g. only having data for the years 2000, 2010 and 2020). + +Each inner array consists of exactly two dates and times. Each date and time MUST be formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). The temporal reference system is the Gregorian calendar. + +Open date ranges are supported by setting either the start or the end time to `null`. Example for data from the beginning of 2019 until now: `[["2009-01-01T00:00:00Z", null]]`. + +### Provider Object + +The object provides information about a provider. A provider is any of the organizations that captures or processes the content of the Collection and therefore influences the data offered by this Collection. May also include information about the final storage provider hosting the data. + +| Field Name | Type | Description | +| ----------- | --------- | ------------------------------------------------------------ | +| name | string | **REQUIRED.** The name of the organization or the individual. | +| description | string | Multi-line description to add further provider information such as processing details for processors and producers, hosting details for hosts or basic contact information. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| roles | \[string] | Roles of the provider. Any of `licensor`, `producer`, `processor` or `host`. | +| url | string | Homepage on which the provider describes the dataset and publishes contact information. | + +**roles**: The provider's role(s) can be one or more of the following elements: + +* *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. +* *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. +* *processor*: A processor is any provider who processed data to a derived product. +* *host*: The host is the actual provider offering the data on their storage. There should be no more than one host, specified as last element of the list. + +### Link Object + +This object describes a relationship with another entity. Data providers are advised to be liberal with links. + +| Field Name | Type | Description | +| ---------- | ------ | ------------------------------------------------------------ | +| href | string | **REQUIRED.** The actual link in the format of an URL. Relative and absolute links are both allowed. | +| rel | string | **REQUIRED.** Relationship between the current document and the linked document. See chapter "[Relation types](#relation-types)" for more information. | +| type | string | [Media type](../catalog-spec/catalog-spec.md#media-types) of the referenced entity. | +| title | string | A human readable title to be used in rendered displays of the link. | + +For a full discussion of the situations where relative and absolute links are recommended see the +['Use of links'](../best-practices.md#use-of-links) section of the STAC best practices. + +#### Relation types + +STAC Collections use a variety of `rel` types in the link object, to describe the exact nature of the link between this Collection and the entity it is linking to. +It is recommended to use the official [IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. +The following table explains places where custom STAC `rel` types are used for ollections. +This is done where there is not a clear official option, or where STAC uses an official type but adds additional meaning for the STAC context. + +| Type | Description | +| ------- | ------------------------------------------------------------ | +| self | STRONGLY RECOMMENDED. *Absolute* URL to the location that the Collection file can be found online, if available. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | +| root | URL to the root STAC Catalog or Collection. Collections should include a link to their root, even if it's the root and points to itself. | +| parent | URL to the parent STAC Catalog or Collection. Non-root Collections should include a link to their parent. | +| child | URL to a child STAC Catalog or Collection. | +| item | URL to a STAC Item. All Items linked from a Collection MUST refer back to its Collection with the [`collection` relation type](../item-spec/item-spec.md#relation-types). | +| license | The license URL(s) for the Collection SHOULD be specified if the `license` field is set to `proprietary` or `various`. If there is no public license URL available, it is RECOMMENDED to put the license text in a separate file and link to this file. | +| derived_from | URL to a STAC Collection that was used as input data in the creation of this Collection. See the note in [STAC Item](../item-spec/item-spec.md#derived_from) for more info. | + +A more complete list of possible `rel` types and their meaning in STAC can be found in the [Using Relation Types](../best-practices.md#using-relation-types) best practice. + +**Note:** The STAC Catalog specification requires a link to at least one `item` or `child` Catalog. This is *not* a requirement for Collections, but *recommended*. In contrast to Catalogs, it is **REQUIRED** that Items linked from a Collection MUST refer back to its Collection with the [`collection` relation type](../item-spec/item-spec.md#relation-types). + +### Asset Object + +An Asset is an object that contains a URI to data associated with the Collection that can be downloaded +or streamed. The definition provided here, at the Collection level, is the same as the +[Asset Object in Items](../item-spec/item-spec.md#asset-object). It is allowed to add additional fields. + +| Field Name | Type | Description | +| ----------- | --------- | ----------- | +| href | string | **REQUIRED.** URI to the asset object. Relative and absolute URI are both allowed. | +| title | string | The displayed title for clients and users. | +| description | string | A description of the Asset providing additional details, such as how it was processed or created. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| type | string | [Media type](../item-spec/item-spec.md#asset-media-type) of the asset. See the [common media types](../best-practices.md#common-media-types-in-stac) in the best practice doc for commonly used asset types. | +| roles | \[string] | The [semantic roles](../item-spec/item-spec.md#asset-role-types) of the asset, similar to the use of `rel` in links. | + +### Stats Object + +For a good understanding of the summarized field, statistics can be added. By default, only ranges with a minimum and a maximum value can be specified. +Ranges can be specified for [ordinal](https://en.wikipedia.org/wiki/Level_of_measurement#Ordinal_scale) values only, which means they need to have a rank order. +Therefore, ranges can only be specified for numbers and some special types of strings. Examples: grades (A to F), dates or times. +Implementors are free to add other derived statistical values to the object, for example `mean` or `stddev`. + +| Field Name | Type | Description | +| ---------- | -------------- | ----------- | +| minimum | number\|string | **REQUIRED.** Minimum value. | +| maximum | number\|string | **REQUIRED.** Maximum value. | + +## Media Type for STAC Collections + +A STAC Collection is a JSON file ([RFC 8259](https://tools.ietf.org/html/rfc8259)), and thus should use the +[`application/json`](https://tools.ietf.org/html/rfc8259#section-11) as the [Media Type](https://en.wikipedia.org/wiki/Media_type) +(previously known as the MIME Type). + +## Standalone Collections + +STAC Collections which don't link to any Item are called **standalone Collections**. +To describe them with more fields than the Collection fields has to offer, it is allowed to re-use the metadata fields defined by extensions for Items in the `summaries` field. +This makes much sense for fields such as `platform` or `proj:epsg`, which are often the same for a whole Collection, but doesn't make much sense for `eo:cloud_cover`, which usually varies heavily across a Collection. +The data provider is free to decide, which fields are reasonable to be used. diff --git a/collection-spec/json-schema/collection.json b/collection-spec/json-schema/collection.json new file mode 100644 index 00000000..422385f2 --- /dev/null +++ b/collection-spec/json-schema/collection.json @@ -0,0 +1,170 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/collection-spec/json-schema/collection.json#", + "title": "STAC Collection Specification", + "description": "This object represents Collections in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "../../catalog-spec/json-schema/catalog-core.json" + }, + { + "$ref": "#/definitions/collection" + } + ], + "definitions": { + "collection": { + "title": "STAC Collection", + "description": "These are the fields specific to a STAC Collection. All other fields are inherited from STAC Catalog.", + "type": "object", + "required": [ + "type", + "license", + "extent" + ], + "properties": { + "type": { + "title": "Type of STAC entity", + "const": "Collection" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "anyOf": [ + { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + }, + { + "title": "Reference to a core extension", + "type": "string" + } + ] + } + }, + "keywords": { + "title": "Keywords", + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "Collection License Name", + "type": "string", + "pattern": "^[\\w\\-\\.\\+]+$" + }, + "providers": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "Organization name", + "type": "string" + }, + "description": { + "title": "Organization description", + "type": "string" + }, + "roles": { + "title": "Organization roles", + "type": "array", + "items": { + "type": "string", + "enum": [ + "producer", + "licensor", + "processor", + "host" + ] + } + }, + "url": { + "title": "Organization homepage", + "type": "string", + "format": "iri" + } + } + } + }, + "extent": { + "title": "Extents", + "type": "object", + "required": [ + "spatial", + "temporal" + ], + "properties": { + "spatial": { + "title": "Spatial extent object", + "type": "object", + "required": [ + "bbox" + ], + "properties": { + "bbox": { + "title": "Spatial extents", + "type": "array", + "minItems": 1, + "items": { + "title": "Spatial extent", + "type": "array", + "oneOf": [ + { + "minItems":4, + "maxItems":4 + }, + { + "minItems":6, + "maxItems":6 + } + ], + "items": { + "type": "number" + } + } + } + } + }, + "temporal": { + "title": "Temporal extent object", + "type": "object", + "required": [ + "interval" + ], + "properties": { + "interval": { + "title": "Temporal extents", + "type": "array", + "minItems": 1, + "items": { + "title": "Temporal extent", + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + } + } + } + } + }, + "assets": { + "$ref": "../../item-spec/json-schema/item.json#/definitions/assets" + } + } + } + } +} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..85b5f5a1 --- /dev/null +++ b/examples/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/examples/catalog.json b/examples/catalog.json new file mode 100644 index 00000000..1bac09f9 --- /dev/null +++ b/examples/catalog.json @@ -0,0 +1,30 @@ +{ + "id": "examples", + "type": "Catalog", + "stac_version": "1.0.0-rc.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.2/examples/catalog.json", + "type": "application/json" + } + ] +} diff --git a/examples/collection-only/collection.json b/examples/collection-only/collection.json new file mode 100644 index 00000000..9a8da7c3 --- /dev/null +++ b/examples/collection-only/collection.json @@ -0,0 +1,228 @@ +{ + "type": "Collection", + "stac_version": "1.0.0-rc.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/examples/collection.json b/examples/collection.json new file mode 100644 index 00000000..4fadb641 --- /dev/null +++ b/examples/collection.json @@ -0,0 +1,93 @@ +{ + "id": "simple-collection", + "type": "Collection", + "stac_version": "1.0.0-rc.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.2/examples/collection.json", + "type": "application/json" + } + ] +} diff --git a/examples/collectionless-item.json b/examples/collectionless-item.json new file mode 100644 index 00000000..250bb704 --- /dev/null +++ b/examples/collectionless-item.json @@ -0,0 +1,147 @@ +{ + "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_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" + } + } +} diff --git a/examples/core-item.json b/examples/core-item.json new file mode 100644 index 00000000..0b453dd4 --- /dev/null +++ b/examples/core-item.json @@ -0,0 +1,117 @@ +{ + "stac_version": "1.0.0-rc.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" + } + } +} diff --git a/examples/extended-item.json b/examples/extended-item.json new file mode 100644 index 00000000..4012cb4a --- /dev/null +++ b/examples/extended-item.json @@ -0,0 +1,191 @@ +{ + "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", + "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-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" + } + } +} diff --git a/examples/extensions-collection/collection.json b/examples/extensions-collection/collection.json new file mode 100644 index 00000000..a79fabe3 --- /dev/null +++ b/examples/extensions-collection/collection.json @@ -0,0 +1,66 @@ +{ + "id": "extensions-collection", + "type": "Collection", + "stac_version": "1.0.0-rc.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" +} diff --git a/examples/extensions-collection/proj-example/proj-example.json b/examples/extensions-collection/proj-example/proj-example.json new file mode 100644 index 00000000..91fa8130 --- /dev/null +++ b/examples/extensions-collection/proj-example/proj-example.json @@ -0,0 +1,278 @@ +{ + "type": "Feature", + "stac_version": "1.0.0-rc.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": [ + "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/examples/simple-item.json b/examples/simple-item.json new file mode 100644 index 00000000..ad4d526d --- /dev/null +++ b/examples/simple-item.json @@ -0,0 +1,74 @@ +{ + "stac_version": "1.0.0-rc.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" + ] + } + } +} diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 00000000..e864f3ce --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,172 @@ +# Extensions + +- [Overview](#overview) +- [General Conventions](#general-conventions) +- [Stable STAC Extensions](#stable-stac-extensions) +- [Community Extensions](#community-extensions) + - [Extension Maturity](#extension-maturity) + - [Proposed extensions](#proposed-extensions) +- [Extending STAC](#extending-stac) + - [Proposing new extensions](#proposing-new-extensions) + - [Prefixes](#prefixes) + - [Use of arrays and objects](#use-of-arrays-and-objects) + +## Overview + +One of the most important aspects of the SpatioTemporal Asset Catalog specification is its extensibility. The core +STAC specification defines only a minimal core, but is designed for extension. It is expected that most real-world +implementations will use several 'extensions' to fully describe their data. This document describes how extensions +work, and links to the 'core' extensions included in this repo, as well as to a variety of 'community' extensions. + +**For the complete list of available extensions see the [STAC extensions overview page](https://stac-extensions.github.io/).** + +Extensions to the core STAC specification provide additional JSON fields that can be used to better describe +the data. Most tend to be about describing a particular domain or type of data, but some imply +functionality. + +Extensions include a JSON Schema precisely describing the structure, a natural language description of the fields, and thorough examples. + +Anybody can create an extension for their data, and data providers often work together to share +fields between them to create a shared community extensions. See the section below on '[Extending STAC](#extending-stac)') +for information on how to get started. And everyone is encouraged to link to the extension in the table below, so others +can be aware of it. + +Each extension has at least one *owner*. You can find extension owners in each extension's README. + +## General Conventions + +1. Additional attributes relating to an [Item](../item-spec/item-spec.md) should be added into the Item Properties object, rather than directly in the Item object. +2. In general, additional attributes that apply to an Item Asset should also be allowed in Item Properties and vice-versa. +For example, the `eo:bands` attribute may be used in Item Properties to describe the aggregation of all bands available in +the Item Asset objects contained in the Item, but may also be used in an individual Item Asset to describe only the bands available in that asset. +3. Additional attributes relating to a [Catalog](../catalog-spec/catalog-spec.md) or [Collection](../collection-spec/collection-spec.md) should be added to the root of the object. + +## Stable STAC Extensions + +These extensions are considered stable and are widely used in many production implementations. As additional extensions advance +through the [Extension Maturity](#extension-maturity) classification they, will be added here. + +| Extension Title | Description | +|-----------------------------------------------------------------------|--------------------------------| +| [Electro-Optical](https://github.com/stac-extensions/eo/) | Covers electro-optical data that represents a snapshot of the Earth for a single date and time. It could consist of multiple spectral bands, for example visible bands, infrared bands, red edge bands and panchromatic bands. The extension provides common fields like bands, cloud cover, gsd and more. | +| [Projection](https://github.com/stac-extensions/projection/) | Provides a way to describe Items whose assets are in a geospatial projection. | +| [Scientific Citation](https://github.com/stac-extensions/scientific/) | Metadata that indicate from which publication data originates and how the data itself should be cited or referenced. | +| [View Geometry](https://github.com/stac-extensions/view/) | View Geometry adds metadata related to angles of sensors and other radiance angles that affect the view of resulting data | + +## Community Extensions + +There are many more extensions that are part of the broader STAC ecosystem. The center of activity for these is the +[stac-extensions GitHub organization](https://github.com/stac-extensions), which has a number of extension repositories. For +an overview of all extensions with their [Extension Maturity](#extension-maturity) classification see the +[STAC extensions overview page](https://stac-extensions.github.io/). + +### Proposed extensions + +Beyond the community extensions there have been a number of extensions that people have proposed to the STAC community. These +can be found in the STAC [Issue Tracker](https://github.com/radiantearth/stac-spec/issues) under the +[new extension](https://github.com/radiantearth/stac-spec/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+extension%22) label. +These are ideas that others would likely use and potentially collaborate on. Anyone is free to add new +ideas there, and see the section below on [proposing new extensions](#proposing-new-extensions) for the +workflow to advance ideas into full-fledged community extensions. + +## Extending STAC + +Anyone is welcome to create an extension. There are several types of extensions, some just add additional fields, +some change the behavior of STAC and some introduce completely new functionality. New extensions should try to align +with existing extensions as well as possible and may even re-use fields and their definitions until they may get split +into a new extension that combines commonly used fields across multiple extensions. +Best practices for extension proposals are still emerging in this section. + +### Proposing new extensions + +Extensions can be hosted anywhere, but should use the [extension template](https://github.com/stac-extensions/stac-extensions.github.io#using-the-stac-extensions-template) +as a starting point. If you'd like to add a repository to the [stac-extensions](https://github.com/stac-extensions) +GitHub organization, just ask on [Gitter](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby)! This is fine for +work-in-progress extensions. You can also host the extension repository in your own GitHub account, and optionally +transfer it to the stac-extensions org later. + +For new extensions that require community discussion, we recommend the following workflow: + +- Use the stac-extensions template to sketch out your proposed extension +- Open an issue on this repository with the prefix "New Extension: " and describe the extension. Include a link to the extension repository. +- Discussion can occur on that issue, or discussion can move to issues/pull requests on the extension repository directly. +- Once the extension has an initial release, the issue on stac-spec will be closed. + +### Extension Maturity + +There are many extensions being built with STAC, but they have varying degrees of maturity. All community extensions +listed here included must include a maturity classification, so that STAC spec users can easily get a sense of how +much they can count on the extension. Extension creators are encouraged to list their extensions here, even if it is just +an rough proposal, so others can potentially collaborate. + +| Maturity Classification | Min Impl # | Description | Stability | +| ----------------------- | ----------- | ----------- | --------- | +| Proposal | 0 | An idea put forward by a community member to gather feedback | Not stable - breaking changes almost guaranteed as implementers try out the idea. | +| Pilot | 1 | Idea is fleshed out, with examples and a JSON schema, and implemented in one or more catalogs. Additional implementations encouraged to help give feedback | Approaching stability - breaking changes are not anticipated but can easily come from additional feedback | +| Candidate | 3 | A number of implementers are using it and are standing behind it as a solid extension. Can generally count on an extension at this maturity level | Mostly stable, breaking changes require a new version and minor changes are unlikely. The extension has a code owner, designated in its README. | +| Stable | 6 | Highest current level of maturity. The community of extension maintainers commits to a STAC review process for any changes, which are not made lightly. | Completely stable, all changes require a new version number and review process. | +| Deprecated | N/A | A previous extension that has likely been superseded by a newer one or did not work out for some reason. | DO NOT USE, is not supported | + +Maturity mostly comes through diverse implementations, so the minimum number of implementations +column is the main gating function for an extension to mature. But extension authors can also +choose to hold back the maturity advancement if they don't feel they are yet ready to commit to +the less breaking changes of the next level. + +A 'mature' classification level will likely be added once there are extensions that have been +stable for over a year and are used in twenty or more implementations. + +### Prefixes + +A STAC Item can combine schema information from several different sources - the core STAC Item information, +an earth observation community extension, and a vendor specific provider. It can be difficult to distinguish exactly where each definition +came from, and to pull out the most relevant information, especially when vendors often will dump in all the metadata they have in to the +STAC definition. + +So one idea is to have prefixes to differentiate specific vendors (like `dg:` for DigitalGlobe), and for communities of practice +(like `eo:` for Electro-Optical). These wouldn't be full namespacing, though an extension for like JSON-LD could potentially +evolve to make fully resolved namespacing an option. + +An example of this can be seen in a Landsat example: + +```js + "properties": { + "datetime":"2018-01-01T13:21:30Z", + + "start_datetime":"2018-01-01T13:21:30Z", + "end_datetime":"2018-01-01T13:31:30Z", + + "view:off_nadir": -0.001, + "eo:cloud_cover": 10.31, + "view:sun_azimuth": 149.01607154, + "view:sun_elevation": 59.21424700, + "gsd": 30, + + "l8:data_type": "L1T", + "l8:wrs_path": 153, + "l8:wrs_row": 25, + "l8:earth_sun_distance": 1.0141560, + "l8:ground_control_points_verify": 114, + "l8:geometric_rmse_model": 7.562, + "l8:image_quality_tirs": 9, + "l8:ground_control_points_model": 313, + "l8:geometric_rmse_model_x": 5.96, + "l8:geometric_rmse_model_y": 4.654, + "l8:geometric_rmse_verify": 5.364, + "l8:image_quality_oli": 9 + } +``` + +### Use of arrays and objects + +For extensions, it is recommended to + +1. Use arrays only as enumerations/lists (possibly sorted), without implying additional meaning (such as order) +2. To avoid using nested objects, in favor of multiple attributes with a similar naming scheme. + +For example, if one would like to define an extension to contain a start and a end date, there are multiple options (tl;dr: option **3** is recommended): + +1. Define an object, for example: `"date_range": {"start": "2018-01-01", "end": "2018-01-31"}`. This is **discouraged** as it is more complex to search in objects. +2. Define an two-element array where the first element is the start date and the second element is the end date, for example `"date_range": ["2018-01-01", "2018-01-31"]`. This is **discouraged** as it would conflict with Collection `summaries`, which always considers arrays as true (potentially sorted) enumeration without any additional meaning. +3. Define two separate fields, e.g. `"date_range_start": "2018-01-01", "date_range_end": "2018-01-31"`. This is **recommended** as it avoids the conflicts above and is usually better displayed in software that only understands GeoJSON but has no clue about STAC. This is due to the fact that most legacy software can not display arrays or objects GeoJSON `properties` properly. + +This rules only applies to the fields defined directly for the Item's `properties`. For fields and structures defined on other levels (e.g. in the root of an Item or in an array), extension authors can freely define the structure. So an array of objects such as the `eo:bands` are fine to use, but keep in mind that the drawbacks mentioned above usually still apply. diff --git a/item-spec/README.md b/item-spec/README.md new file mode 100644 index 00000000..cd45a670 --- /dev/null +++ b/item-spec/README.md @@ -0,0 +1,23 @@ +# STAC Item Specification + +The [STAC Item](item-spec.md) object is the most important object in a STAC system. An +**Item** is the entity that contains metadata for a scene and links to the assets. + +Item objects are the leaf nodes for a graph of [Catalog](../catalog-spec/catalog-spec.md) +and [Collection](../collection-spec/collection-spec.md) objects. See the +[overview](../overview.md) document for more information about how these objects relate +to each other. + +## In this directory + +**Specification:** The STAC Item specification is in +*[item-spec.md](item-spec.md)*. It includes an overview and an in-depth explanation of the fields. + +**Schemas:** The OpenAPI specification in *[item.json](json-schema/item.json)* +defines an **Item** object. The [basics](json-schema/basics.json), +[datetime](json-schema/datetime.json), [instrument](json-schema/instrument.json), +[licensing](json-schema/licensing.json), and [provider](json-schema/provider.json) +schemas validate additional fields defined in *[Common Metadata](common-metadata.md)*. + +**Common Metadata:** A set of commonly-used fields for STAC Items is listed in +*[common-metadata.md](common-metadata.md)*. diff --git a/item-spec/common-metadata.md b/item-spec/common-metadata.md new file mode 100644 index 00000000..2a0506ca --- /dev/null +++ b/item-spec/common-metadata.md @@ -0,0 +1,186 @@ +# STAC Common Metadata + +This document outlines commonly used fields in STAC. +They are often used in [STAC Item properties](item-spec.md#properties-object), +but can also be used in other places, e.g. an [Item Asset](item-spec.md#asset-object) +or [Collection Asset](../collection-spec/collection-spec.md#asset-object). + +- [STAC Common Metadata](#stac-common-metadata) + - [Basics](#basics) + - [Date and Time](#date-and-time) + - [Date and Time Range](#date-and-time-range) + - [Licensing](#licensing) + - [Relation types](#relation-types) + - [Provider](#provider) + - [Provider Object](#provider-object) + - [Instrument](#instrument) + +Various *examples* are available in the folder [`examples`](../examples/). +*JSON Schemas* can be found in the folder [`json-schema`](json-schema/). + +By default, these fields are only included and validated against in the core [Item schema](json-schema/item.json). +Implementation of any of the fields is not required, +if the specifications allowing these fields to be used don't say differently. +For example, `datetime` is required in STAC Items. + +## Basics + +Descriptive fields to give a basic overview of a STAC Item. + +- [JSON Schema](json-schema/basics.json) + +| Field Name | Type | Description | +| ----------- | ------ | ------------------------------------------------------------ | +| title | string | A human readable title describing the Item. | +| description | string | Detailed multi-line description to fully explain the Item. [CommonMark 0.29](https://commonmark.org/) syntax MAY be used for rich text representation. | + +## Date and Time + +- [JSON Schema](json-schema/datetime.json) + +Fields to provide additional temporal information such as ranges with a start and an end datetime stamp. + +| Field Name | Type | Description | +| ---------- | ------------ | ----------- | +| datetime | string\|null | See the [Item Spec Fields](item-spec.md#properties-object) for more information. | +| created | string | Creation date and time of the corresponding data (see below). | +| updated | string | Date and time the corresponding data (see below) was updated last. | + +All timestamps MUST be formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). + +**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. + +*NOTE: There are more date and time related fields available in the [Timestamps +extension](https://github.com/stac-extensions/timestamps), which is not an official extension*. + +### Date and Time Range + +While a STAC Item can have a nominal datetime describing the capture, these properties allow an Item to have a range +of capture dates and times. An example of this is the [MODIS 16 day vegetation index product.](https://lpdaac.usgs.gov/products/mod13q1v006/). +The datetime property in a STAC Item and these fields are not mutually exclusive. + +**Important:** Using one of the fields REQUIRES to include the other field as well to enable a user to search STAC records by the provided times. So if you use `start_datetime` you need to add `end_datetime` and vice-versa. Both fields are also REQUIRED if the `datetime` field is set to `null`. + +| Field Name | Type | Description | +| -------------- | ------ | ------------------------------------------------------------ | +| start_datetime | string | The first or start date and time for the Item, in UTC. It is formatted as `date-time` according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). | +| end_datetime | string | The last or end date and time for the Item, in UTC. It is formatted as `date-time` according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). | + +## Licensing + +Information about the license(s) of the data, which is not necessarily the same license that applies to the metadata. +**Licensing information should be defined at the [Collection](../collection-spec/collection-spec.md) level if possible.** + +- [JSON Schema](json-schema/licensing.json) + +| Field Name | Type | Description | +| ---------- | ------ | ----------- | +| license | string | Item's license(s), either a SPDX [License identifier](https://spdx.org/licenses/), `various` if multiple licenses apply or `proprietary` for all other cases. Should be defined at the Collection level if possible. | + +**license**: Data license(s) as a SPDX [License identifier](https://spdx.org/licenses/). Alternatively, use +`proprietary` (see below) if the license is not on the SPDX license list or `various` if multiple licenses apply. +In all cases links to the license texts SHOULD be added, see the [`license` link relation type](#relation-types). +If no link to a license is included and the `license` field is set to `proprietary`, the Collection is private, +and consumers have not been granted any explicit right to use the data. + +### Relation types + +| Type | Description | +| ------------ | ------------------------------------------------------------ | +| license | The license URL(s) for the Item SHOULD be specified if the `license` field is set to `proprietary` or `various`. If there is no public license URL available, it is RECOMMENDED to supplement the STAC Item with the license text in a separate file and link to this file. | + +## Provider + +Information about the organizations capturing, producing, processing, hosting or publishing this data. +**Provider information should be defined at the Collection level if possible.** + +- [JSON Schema](json-schema/provider.json) + +| Field Name | Type | Description | +| ---------- | ------ | ----------- | +| providers | [[Provider Object](#provider-object)] | A list of providers, which may include all organizations capturing or processing the data or the hosting provider. Providers should be listed in chronological order with the most recent provider being the last element of the list. | + +### Provider Object + +The object provides information about a provider. A provider is any of the organizations that captures or processes the content of the assets and therefore influences the data offered by the STAC implementation. May also include information about the final storage provider hosting the data. + +| Field Name | Type | Description | +| ----------- | --------- | ------------------------------------------------------------ | +| name | string | **REQUIRED.** The name of the organization or the individual. | +| description | string | Multi-line description to add further provider information such as processing details for processors and producers, hosting details for hosts or basic contact information. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| roles | \[string] | Roles of the provider. Any of `licensor`, `producer`, `processor` or `host`. | +| url | string | Homepage on which the provider describes the dataset and publishes contact information. | + +#### roles + +The provider's role(s) can be one or more of the following elements: + +* *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. +* *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. +* *processor*: A processor is any provider who processed data to a derived product. +* *host*: The host is the actual provider offering the data on their storage. There should be no more than one host, specified as last element of the list. + +## Instrument + +Adds metadata specifying a platform and instrument used in a data collection mission. These fields will often be combined +with domain-specific extensions that describe the actual data, such as the `eo` or `sar` extensions. + +- [JSON Schema](json-schema/instrument.json) + +| Field Name | Type | Description | +| ------------- | --------- | ----------- | +| platform | string | Unique name of the specific platform to which the instrument is attached. | +| instruments | \[string] | Name of instrument or sensor used (e.g., MODIS, ASTER, OLI, Canon F-1). | +| constellation | string | Name of the constellation to which the platform belongs. | +| mission | string | Name of the mission for which data is collected. | +| gsd | number | Ground Sample Distance at the sensor, in meters (m), must be greater than 0. | + +### Additional Field Information + +#### platform + +The unique name of the specific platform the instrument is attached to. For satellites this would +be the name of the satellite, whereas for drones this would be a unique name for the drone. Examples include +`landsat-8` (Landsat-8), `sentinel-2a` and `sentinel-2b` (Sentinel-2), `terra` and `aqua` (part of NASA EOS, +carrying the MODIS instruments), `mycorp-uav-034` (hypothetical drone name), and `worldview02` +(Maxar/DigitalGlobe WorldView-2). + +#### instruments + +An array of all the sensors used in the creation of the data. For example, data from the Landsat-8 +platform is collected with the OLI sensor as well as the TIRS sensor, but the data is distributed together so would be +specified as `['oli', 'tirs']`. Other instrument examples include `msi` (Sentinel-2), `aster` (Terra), and `modis` +(Terra and Aqua), `c-sar` (Sentinel-1) and `asar` (Envisat). + +#### constellation + +The name of a logical collection of one or more platforms that have similar payloads and have +their orbits arranged in a way to increase the temporal resolution of acquisitions of data with similar geometric and +radiometric characteristics. This field allows users to search for related data sets without the need to specify which +specific platform the data came from, for example, from either of the Sentinel-2 satellites. Examples include `landsat-8` +(Landsat-8, a constellation consisting of a single platform), `sentinel-2` +([Sentinel-2](https://www.esa.int/Our_Activities/Observing_the_Earth/Copernicus/Sentinel-2/Satellite_constellation)), +`rapideye` (operated by Planet Labs), and `modis` (NASA EOS satellites Aqua and Terra). In the case of `modis`, this +is technically referring to a pair of sensors on two different satellites, whose data is combined into a series of +related products. Additionally, the Aqua satellite is technically part of the A-Train constellation and Terra is not +part of a constellation, but these are combined to form the logical collection referred to as MODIS. + +#### mission + +The name of the mission or campaign for collecting data. This could be a discrete set of data collections +over a period of time (such as collecting drone imagery), or could be a set of tasks of related tasks from a satellite +data collection. + +#### gsd + +The nominal Ground Sample Distance for the data, as measured in meters on the ground. There are many +definitions of GSD. The value of this field should be related to the spatial resolution at the sensor, rather +than the pixel size of images after orthorectification, pansharpening, or scaling. +The GSD of a sensor can vary depending on off-nadir and wavelength, so it is at the discretion of the implementer +to decide which value most accurately represents the GSD. For example, Landsat8 optical and short-wave IR bands +are all 30 meters, but the panchromatic band is 15 meters. The +`gsd` should be 30 meters in this case because that is nominal spatial resolution at the sensor. The Planet +PlanetScope Ortho Tile Product has an `gsd` of 3.7 (or 4 if rounding), even though the pixel size of the images is 3.125. For example, one might choose for WorldView-2 the +Multispectral 20° off-nadir value of 2.07 and for WorldView-3 the Multispectral 20° off-nadir value of 1.38. diff --git a/item-spec/item-spec.md b/item-spec/item-spec.md new file mode 100644 index 00000000..84209294 --- /dev/null +++ b/item-spec/item-spec.md @@ -0,0 +1,306 @@ +# STAC Item Specification + +- [Overview](#overview) +- [Item fields](#item-fields) + - [Additional Field Information](#additional-field-information) + - [stac_version](#stac_version) + - [stac_extensions](#stac_extensions) + - [id](#id) + - [assets](#assets) + - [bbox](#bbox) + - [Properties Object](#properties-object) + - [datetime](#datetime) + - [Additional Fields](#additional-fields) + - [Link Object](#link-object) + - [Relation types](#relation-types) + - [derived_from](#derived_from) + - [Collections](#collections) + - [Asset Object](#asset-object) + - [Asset Media Type](#asset-media-type) + - [Asset Roles](#asset-roles) + - [Asset Role Types](#asset-role-types) + - [Additional Fields for Assets](#additional-fields-for-assets) +- [Media Type for STAC Item](#media-type-for-stac-item) +- [Extensions](#extensions) + +## Overview + +This document explains the structure and content of a SpatioTemporal Asset Catalog (STAC) Item. An **Item** is a +[GeoJSON](http://geojson.org/) [Feature](https://tools.ietf.org/html/rfc7946#section-3.2) augmented with +[foreign members](https://tools.ietf.org/html/rfc7946#section-6) relevant to a STAC object. +These include fields that identify the time range and assets of the Item. An Item is the core +object in a STAC Catalog, containing the core metadata that enables any client to search or crawl +online catalogs of spatial 'assets' (e.g., satellite imagery, derived data, DEMs). + +The same Item definition is used in both [STAC Catalogs](../catalog-spec/README.md) and +the [Item-related API endpoints](https://github.com/radiantearth/stac-api-spec/blob/master/api-spec.md#ogc-api---features-endpoints). +Catalogs are simply sets of Items that are linked online, generally served by simple web servers +and used for crawling data. The search endpoint enables dynamic queries, for example selecting all +Items in Hawaii on June 3, 2015, but the results they return are FeatureCollections of Items. + +Items are represented in JSON format and are very flexible. Any JSON object that contains all the +required fields is a valid STAC Item. + +- Examples: + - See the [minimal example](../examples/simple-item.json), as well as a [more fleshed example](../examples/core-item.json) that contains a number of + current best practices. + - Real world [implementations](https://stacindex.org/catalogs) are also available. +- [JSON Schema](json-schema/item.json) + +## Item fields + +This object describes a STAC Item. The fields `id`, `type`, `bbox`, `geometry` and `properties` are +inherited from GeoJSON. + +| Field Name | Type | Description | +| ---------- | -------------------------------------------------------------------------- | ----------- | +| type | string | **REQUIRED.** Type of the GeoJSON Object. MUST be set to `Feature`. | +| stac_version | string | **REQUIRED.** The STAC version the Item implements. | +| stac_extensions | \[string] | A list of extensions the Item implements. | +| id | string | **REQUIRED.** Provider identifier. The ID should be unique within the [Collection](../collection-spec/collection-spec.md) that contains the Item. | +| geometry | [GeoJSON Geometry Object](https://tools.ietf.org/html/rfc7946#section-3.1) \| [null](https://tools.ietf.org/html/rfc7946#section-3.2) | **REQUIRED.** Defines the full footprint of the asset represented by this item, formatted according to [RFC 7946, section 3.1](https://tools.ietf.org/html/rfc7946#section-3.1). The footprint should be the default GeoJSON geometry, though additional geometries can be included. Coordinates are specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). | +| bbox | \[number] | **REQUIRED if `geometry` is not `null`.** Bounding Box of the asset represented by this Item, formatted according to [RFC 7946, section 5](https://tools.ietf.org/html/rfc7946#section-5). | +| properties | [Properties Object](#properties-object) | **REQUIRED.** A dictionary of additional metadata for the Item. | +| links | \[[Link Object](#link-object)] | **REQUIRED.** List of link objects to resources and related URLs. A link with the `rel` set to `self` is strongly recommended. | +| assets | Map | **REQUIRED.** Dictionary of asset objects that can be downloaded, each with a unique key. | +| collection | string | The `id` of the STAC Collection this Item references to (see [`collection` relation type](#relation-types)). This field is *required* if such a relation type is present. This field provides an easy way for a user to search for any Items that belong in a specified Collection. Must be a non-empty string. | + +### Additional Field Information + +#### stac_version + +In general, STAC versions can be mixed, but please keep the [recommended best practices](../best-practices.md#mixing-stac-versions) in mind. + +#### stac_extensions + +A list of extensions the Item implements. +This list must only contain extensions that extend the Item itself, see the the 'Scope' column in the list of +extensions. The list contains URLs to the JSON Schema files it can be validated against. +If an extension such as the `tiled-assets` extension has influence on multiple parts of the whole catalog +structure, it must be listed in all affected parts (e.g. Catalog, Collection and Item for the `tiled-assets` extension). + +#### id + +It is important that an Item identifier is unique within a Collection, and that the +[Collection identifier](../collection-spec/collection-spec.md#id) in turn is unique globally. Then the two can be combined to +give a globally unique identifier. Items are *[strongly recommended](#collections)* to have Collections, and not having one makes +it more difficult to be used in the wider STAC ecosystem. If an Item does not have a Collection, then the Item identifier should be unique within its root Catalog. + +As most geospatial assets are already uniquely defined by some +identification scheme from the data provider it is recommended to simply use that ID. Data providers are advised to include sufficient information to make their +IDs globally unique, including things like unique satellite IDs. See the [id section of best practices](../best-practices.md#field-and-id-formatting) for +additional recommendations. + +#### assets + +This is a dictionary of [Asset Objects](#asset-object) data associated with the Item that can be +downloaded or streamed, each with a unique key. +In general, the keys don't have any meaning and are considered to be non-descriptive unique identifiers. +Providers may assign any meaning to the keys for their respective use cases, but must not expect that clients understand them. +To communicate the purpose of an asset better use the `roles` field in the [Asset Object](#asset-object). + +Assets should include the main asset, as well as any 'sidecar' files that are related and help a +client make sense of the data. Examples of this include extended metadata (in XML, JSON, etc.), +unusable data masks, satellite ephemeris data, etc. Some assets (like Landsat data) are represented +by multiple files - all should be linked to. It is generally recommended that different processing +levels or formats are not exhaustively listed in an Item, but instead are represented by related +Items that are linked to, but the best practices around this are still emerging. + +#### bbox + +Bounding Box of the asset represented by this Item using either 2D or 3D geometries, formatted according to [RFC 7946, section 5](https://tools.ietf.org/html/rfc7946#section-5). The length of the array must be 2\*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters and the elevation of the northeasterly most extent is the maximum. This field enables more naive clients to easily index and search geospatially. STAC compliant APIs are required to compute intersection operations with the Item's geometry field, not its bbox. + +### Properties Object + +Additional metadata fields can be added to the GeoJSON Object Properties. The only required field +is `datetime` but it is recommended to add more fields, see [Additional Fields](#additional-fields) +resources below. + +| Field Name | Type | Description | +| ---------- | ------------ | ------------------------------------------------------------ | +| datetime | string\|null | **REQUIRED.** The searchable date and time of the assets, in UTC. It is formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). `null` is allowed, but requires `start_datetime` and `end_datetime` from [common metadata](common-metadata.md#date-and-time-range) to be set. | + +#### datetime + +This is likely the acquisition (in the case of single camera type captures) or the 'nominal' +or representative time in the case of assets that are combined together. Though time can be a +complex thing to capture, for this purpose keep in mind the STAC spec is primarily searching for +data, so use whatever single date and time is most useful for a user to search for. STAC content +extensions may further specify the meaning of the main `datetime` field, and many will also add more +datetime fields. +If there's clearly no meaningful single 'nominal' time, it is allowed to use `null` instead. +In this case it is **required** to specify a temporal interval with the fields `start_datetime` +and `end_datetime` from [common metadata](common-metadata.md#date-and-time-range). For example, if +your data is a time-series that covers 100 years, it's not very meaningful to set the datetime to a +single timestamp as it would not be found in most searches that searches for a decade of data in that +period although the Item actually covers the decade. See [datetime selection](../best-practices.md#datetime-selection) +in the best practices document for more information. + +#### Additional Fields + +Providers should include metadata fields that are relevant for users of STAC, but it is recommended +to [select only those necessary for search](../best-practices.md#field-selection-and-metadata-linking). +Where possible metadata fields should be mapped to the STAC Common Metadata and widely used extensions, +to enable cross-catalog search on known fields. + +* [STAC Common Metadata](common-metadata.md#stac-common-metadata) - A list of fields commonly used +throughout all domains. These optional fields are included for STAC Items by default. +* [Extensions](../extensions/README.md) - Additional fields that are more specific, +such as [EO](https://github.com/stac-extensions/eo), [View](https://github.com/stac-extensions/view). +* [Custom Extensions](../extensions/README.md#extending-stac) - It is generally allowed to add custom +fields but it is recommended to add multiple fields for related values instead of a nested object, +e.g., two fields `view:azimuth` and `view:off_nadir` instead of a field `view` with an object +value containing the two fields. The convention (as used within Extensions) is for related fields +to use a common prefix on the field names to group them, e.g. `view`. A nested data structure should +only be used when the data itself is nested, as with `eo:bands`. + +### Link Object + +This object describes a relationship with another entity. Data providers are advised to be liberal +with the links section, to describe things like the Catalog an Item is in, related Items, parent or +child Items (modeled in different ways, like an 'acquisition' or derived data). +It is allowed to add additional fields such as a `title` and `type`. + +| Field Name | Type | Description | +| ---------- | ------ | ----------- | +| href | string | **REQUIRED.** The actual link in the format of an URL. Relative and absolute links are both allowed. | +| rel | string | **REQUIRED.** Relationship between the current document and the linked document. See chapter "Relation types" for more information. | +| type | string | [Media type](../catalog-spec/catalog-spec.md#media-types) of the referenced entity. | +| title | string | A human readable title to be used in rendered displays of the link. | + +For a full discussion of the situations where relative and absolute links are recommended see the +['Use of links'](../best-practices.md#use-of-links) section of the STAC best practices. + +#### Relation types + +STAC Items use a variety of `rel` types in the link object, to describe the exact nature of the link between this Item and the entity it is linking to. +It is recommended to use the official [IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. +The following table explains places where STAC use custom `rel` types are used with Items. +This happens where there is not a clear official option, or where STAC uses an official type but adds additional meaning for the STAC context. + +| Type | Description | +| ------------ | ------------------------------------------------------------ | +| self | STRONGLY RECOMMENDED. *Absolute* URL to the Item if it is available at a public URL. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | +| root | URL to the root STAC Catalog or Collection. | +| parent | URL to the parent STAC Catalog or Collection. | +| collection | STRONGLY RECOMMENDED. URL to a Collection. *Absolute* URLs should be used whenever possible. The referenced Collection is STRONGLY RECOMMENDED to implement the same STAC version as the Item. | +| derived_from | URL to a STAC Item that was used as input data in the creation of this Item. | + +A more complete list of potential `rel` types and their meaning in STAC can be found in the [Using Relation +Types](../best-practices.md#using-relation-types) best practice. + +##### derived_from + +*Note regarding the type `derived_from`: A full provenance model is far beyond the scope of STAC, and the goal is to align with any good independent spec +that comes along for that. But the derived_from field is seen as a way to encourage fuller specs and at least start a linking +structure that can be used as a jumping off point for more experiments in provenance tracking* + +#### Collections + +Items are *strongly recommended* to provide a link to a STAC Collection definition. It is important as Collections provide additional information about a set of items, for example the license, provider and other information +giving context on the overall set of data that an individual Item is a part of. + +If Items are part of a STAC Collection, the [STAC Collection spec *requires* Items to link back to the Collection](../collection-spec/collection-spec.md#relation-types). +Linking back must happen in two places: + +1. The field `collection` in an Item must be filled (see section 'Item fields'). It is the `id` of a STAC Collection. +2. An Item must also provide a link to the STAC Collection using the [`collection` relation type](#relation-types): + ```js + "links": [ + { "rel": "collection", "href": "link/to/collection/record.json" } + ] + ``` + +### Asset Object + +An Asset is an object that contains a URI to data associated with the Item that can be downloaded +or streamed. It is allowed to add additional fields. + +| Field Name | Type | Description | +| ----------- | --------- | ----------- | +| href | string | **REQUIRED.** URI to the asset object. Relative and absolute URI are both allowed. | +| title | string | The displayed title for clients and users. | +| description | string | A description of the Asset providing additional details, such as how it was processed or created. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | +| type | string | [Media type](#asset-media-type) of the asset. See the [common media types](../best-practices.md#common-media-types-in-stac) in the best practice doc for commonly used asset types. | +| roles | \[string] | The [semantic roles](#asset-roles) of the asset, similar to the use of `rel` in links. | + +[Additional fields](#additional-fields) *may* be added to the assets, though this +is recommended only in special cases. See [Additional Fields for Assets](#additional-fields-for-assets)) for more information. + +#### Asset Media Type + +Any media type can be used in an Item's asset `type` field, and [registered](https://www.iana.org/assignments/media-types/media-types.xhtml) +Media Types are preferred. STAC Items that have sidecar metadata files associated with a data asset (e.g, `.tfw`, Landsat 8 MTL files) +should use media types appropriate for the the metadata file. For example, if it is a plain text file, then `text/plain` +would be appropriate; if it is an XML, then `text/xml` is appropriate. For more information on media types as well as a +list of [common media types](../best-practices.md#common-media-types-in-stac) used in STAC see the [best practice on +working with media types](../best-practices.md#working-with-media-types). + +#### Asset Roles + +The `roles` field is used to describe the purpose of each asset. It is recommended to include one for every asset, to give users +a sense of why they might want to make use of the asset. There are some emerging standards that enable clients to take particular +action when they encounter particular roles, listed below. But implementors are encouraged to come up with their own terms to +describe the role. + +##### Asset Role Types + +Like the Link `rel` field, the `roles` field can be given any value, however here are a few standardized role names. + +| Role Name | Description | +| --------- | ------------------------------------------------------------------------------------- | +| thumbnail | An asset that represents a thumbnail of the Item, typically a true color image (for Items with assets in the visible wavelengths), lower-resolution (typically smaller 600x600 pixels), and typically a JPEG or PNG (suitable for display in a web browser). Multiple assets may have this purpose, but it recommended that the `type` and `roles` be unique tuples. For example, Sentinel-2 L2A provides thumbnail images in both JPEG and JPEG2000 formats, and would be distinguished by their media types. | +| overview | An asset that represents a possibly larger view than the thumbnail of the Item, for example, a true color composite of multi-band data. | +| data | The data itself. This is a suggestion for a common role for data files to be used in case data providers don't come up with their own names and semantics. | +| metadata | A metadata sidecar file describing the data in this Item, for example the Landsat-8 MTL file. | + +It is STRONGLY RECOMMENDED to add to each STAC Item +* a thumbnail with the role `thumbnail` for preview purposes +* one or more data file although it doesn't need to use the suggested role `data` + +Note that multiple roles per asset are encouraged: pick all the ones that apply. So many should have the 'data' role, and then +another role to describe how the data is used. For more information on how to use roles see the [Asset +Roles](../best-practices.md#asset-roles) section of the Best Practices document. It includes a [list of asset +roles](../best-practices.md#list-of-asset-roles) that include many more ideas on roles to use. As they reach more widespread +adoption we will include them here. + +#### Additional Fields for Assets + +As detailed above, Items contain properties, which are the main source of metadata for searching across Items. Many content +extensions can add further property fields as well. Any property that can be specified for an Item can also be specified for +a specific asset. This can be used to override a property defined in the Item, or to specify fields for which there is no +single value for all assets. + +**It is important to note that the STAC API does not facilitate searching across Asset properties in this way, and this +should be used sparingly.** It is primarily used to define properties at the Asset level that may be used during use of +the data instead of for searching. + +For example, `gsd` defined for an Item represents the best Ground Sample Distance (resolution) for the data within the Item. +However, some assets may be lower resolution and thus have a higher `gsd`. The `eo:bands` field from the EO extension defines +an array of spectral bands. However, it may be useful instead to specify the bands that are used in a particular asset. + +For an example see the [sentinel2-sample](https://github.com/stac-utils/stac-examples/blob/main/sentinel2/sentinel2-sample.json). The Sentinel-2 overall `gsd` is 10m, because this is +the best spatial resolution among all the bands and is defined in Item properties so it can be searched on. In the example +Band 5 and others have a `gsd` of 20m, so that asset specifies the `gsd` as well, which overrides the Item `gsd` for this +one asset. The example also includes reduced resolution versions of files included as assets, using `gsd` to represent +the proper resolution. + +For `eo:bands`, it could be put in Item properties as an array of all the bands, but in this case it's not. Instead, +the assets each define an array containing the spectral band information for that asset (in the order the bands appear +in the file). + +For examples of fields that this construct is recommended for, see the [section of STAC Best Practices](../best-practices.md#common-use-cases-of-additional-fields-for-assets) +that talks about common use cases of additional fields for assets. + +## Media Type for STAC Item + +A STAC Item is a GeoJSON file ([RFC 7946](https://tools.ietf.org/html/rfc7946)), and thus should use the +[`application/geo+json`](https://tools.ietf.org/html/rfc7946#section-12) as the [Media Type](https://en.wikipedia.org/wiki/Media_type) +(previously known as the MIME Type). + +## Extensions + +There are emerging best practices, which in time will evolve in to specification extensions for +particular domains or uses. + +The [extensions page](../extensions/README.md) gives an overview about relevant extensions for STAC Items. diff --git a/item-spec/json-schema/basics.json b/item-spec/json-schema/basics.json new file mode 100644 index 00000000..a5b26a3d --- /dev/null +++ b/item-spec/json-schema/basics.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/basics.json#", + "title": "Basic Descriptive Fields", + "type": "object", + "properties": { + "title": { + "title": "Item Title", + "description": "A human-readable title describing the Item.", + "type": "string" + }, + "description": { + "title": "Item Description", + "description": "Detailed multi-line description to fully explain the Item.", + "type": "string" + } + } +} \ No newline at end of file diff --git a/item-spec/json-schema/datetime.json b/item-spec/json-schema/datetime.json new file mode 100644 index 00000000..3a1480db --- /dev/null +++ b/item-spec/json-schema/datetime.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/datetime.json#", + "title": "Date and Time Fields", + "type": "object", + "dependencies": { + "start_datetime": { + "required": [ + "end_datetime" + ] + }, + "end_datetime": { + "required": [ + "start_datetime" + ] + } + }, + "properties": { + "datetime": { + "title": "Date and Time", + "description": "The searchable date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": ["string", "null"], + "format": "date-time" + }, + "start_datetime": { + "title": "Start Date and Time", + "description": "The searchable start date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time" + }, + "end_datetime": { + "title": "End Date and Time", + "description": "The searchable end date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time" + }, + "created": { + "title": "Creation Time", + "type": "string", + "format": "date-time" + }, + "updated": { + "title": "Last Update Time", + "type": "string", + "format": "date-time" + } + } +} \ No newline at end of file diff --git a/item-spec/json-schema/instrument.json b/item-spec/json-schema/instrument.json new file mode 100644 index 00000000..78d836ba --- /dev/null +++ b/item-spec/json-schema/instrument.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/instrument.json#", + "title": "Instrument Fields", + "type": "object", + "properties": { + "platform": { + "title": "Platform", + "type": "string" + }, + "instruments": { + "title": "Instruments", + "type": "array", + "items": { + "type": "string" + } + }, + "constellation": { + "title": "Constellation", + "type": "string" + }, + "mission": { + "title": "Mission", + "type": "string" + }, + "gsd": { + "title": "Ground Sample Distance", + "type": "number", + "exclusiveMinimum": 0 + } + } +} \ No newline at end of file diff --git a/item-spec/json-schema/item.json b/item-spec/json-schema/item.json new file mode 100644 index 00000000..34d351f3 --- /dev/null +++ b/item-spec/json-schema/item.json @@ -0,0 +1,250 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/item.json#", + "title": "STAC Item", + "type": "object", + "description": "This object represents the metadata for an item in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/core" + } + ], + "definitions": { + "common_metadata": { + "allOf": [ + { + "$ref": "basics.json" + }, + { + "$ref": "datetime.json" + }, + { + "$ref": "instrument.json" + }, + { + "$ref": "licensing.json" + }, + { + "$ref": "provider.json" + } + ] + }, + "core": { + "allOf": [ + { + "$ref": "https://geojson.org/schema/Feature.json" + }, + { + "oneOf": [ + { + "type": "object", + "required": [ + "geometry", + "bbox" + ], + "properties": { + "geometry": { + "$ref": "https://geojson.org/schema/Geometry.json" + }, + "bbox": { + "type": "array", + "oneOf": [ + { + "minItems": 4, + "maxItems": 4 + }, + { + "minItems": 6, + "maxItems": 6 + } + ], + "items": { + "type": "number" + } + } + } + }, + { + "type": "object", + "required": [ + "geometry" + ], + "properties": { + "geometry": { + "type": "null" + }, + "bbox": { + "not": {} + } + } + } + ] + }, + { + "type": "object", + "required": [ + "stac_version", + "id", + "links", + "assets", + "properties" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0-rc.2" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "anyOf": [ + { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + }, + { + "title": "Reference to a core extension", + "type": "string" + } + ] + } + }, + "id": { + "title": "Provider ID", + "description": "Provider item ID", + "type": "string", + "minLength": 1 + }, + "links": { + "title": "Item links", + "description": "Links to item relations", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "assets": { + "$ref": "#/definitions/assets" + }, + "properties": { + "allOf": [ + { + "$ref": "#/definitions/common_metadata" + }, + { + "anyOf": [ + { + "required": [ + "datetime" + ], + "properties": { + "datetime": { + "not": { + "type": "null" + } + } + } + }, + { + "required": [ + "datetime", + "start_datetime", + "end_datetime" + ] + } + ] + } + ] + }, + "collection": { + "title": "Collection ID", + "description": "The ID of the STAC Collection this Item references to.", + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + }, + "assets": { + "title": "Asset links", + "description": "Links to assets", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/asset" + } + }, + "asset": { + "allOf": [ + { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "title": "Asset reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "title": { + "title": "Asset title", + "type": "string" + }, + "description": { + "title": "Asset description", + "type": "string" + }, + "type": { + "title": "Asset type", + "type": "string" + }, + "roles": { + "title": "Asset roles", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "$ref": "#/definitions/common_metadata" + } + ] + } + } +} diff --git a/item-spec/json-schema/licensing.json b/item-spec/json-schema/licensing.json new file mode 100644 index 00000000..a20dde60 --- /dev/null +++ b/item-spec/json-schema/licensing.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/licensing.json#", + "title": "Licensing Fields", + "type": "object", + "properties": { + "license": { + "type": "string", + "pattern": "^[\\w\\-\\.\\+]+$" + } + } +} \ No newline at end of file diff --git a/item-spec/json-schema/provider.json b/item-spec/json-schema/provider.json new file mode 100644 index 00000000..b80c734e --- /dev/null +++ b/item-spec/json-schema/provider.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/provider.json#", + "title": "Provider Fields", + "type": "object", + "properties": { + "providers": { + "title": "Providers", + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "Organization name", + "type": "string", + "minLength": 1 + }, + "description": { + "title": "Organization description", + "type": "string" + }, + "roles": { + "title": "Organization roles", + "type": "array", + "items": { + "type": "string", + "enum": [ + "producer", + "licensor", + "processor", + "host" + ] + } + }, + "url": { + "title": "Organization homepage", + "type": "string", + "format": "iri" + } + } + } + } + } +} \ No newline at end of file diff --git a/overview.md b/overview.md new file mode 100644 index 00000000..3d752aae --- /dev/null +++ b/overview.md @@ -0,0 +1,186 @@ +# STAC Overview + +There are three component specifications that together make up the core SpatioTemporal Asset Catalog specification. +Each can be used alone, but they work best in concert with one another. The [STAC API specification](https://github.com/radiantearth/stac-api-spec) +builds on top of that core, but is out of scope for this overview. An [Item](item-spec/item-spec.md) represents a +single [spatiotemporal asset](#what-is-a-spatiotemporal-asset) as GeoJSON so it can be searched. +The [Catalog](catalog-spec/catalog-spec.md) specification provides structural elements, to group Items +and [Collections](collection-spec/collection-spec.md). Collections *are* catalogs, that add more required metadata and +describe a group of related Items. For more on the differences see the [section below](#catalogs-vs-collections). + +A [UML diagram](https://en.wikipedia.org/wiki/Unified_Modeling_Language) of the [STAC model](STAC-UML.pdf) is also +provided to help with navigating the specification. + +## Item Overview + +Fundamental to any SpatioTemporal Asset Catalog, an [Item](item-spec/item-spec.md) object represents a unit of +data and metadata, typically representing a single scene of data at one place and time. A STAC Item is a +[GeoJSON](http://geojson.org/) [Feature](https://tools.ietf.org/html/rfc7946#section-3.2) +and can be easily read by any modern GIS or geospatial library, and it describes a +[SpatioTemporal Asset](#what-is-a-spatiotemporal-asset). +The STAC Item JSON specification uses the GeoJSON geometry to describe the location of the asset, and +then includes additional information: + +* the time the asset represents; +* a thumbnail for quick browsing; +* asset links, to enable direct download or streaming access of the asset; +* relationship links, allowing users to traverse other related resources and STAC Items. + +A STAC Item can contain additional fields and JSON structures to communicate more information about the +asset, so it can be easily searched. STAC provides a core set of +[Common Metadata](item-spec/common-metadata.md) +and there is a wider community working on a variety of [STAC Extensions](extensions/) that provide shared metadata for +more specific domains. Both aim to describe data with well known, well +defined terms to enable consistent publishing and better search. For more recommendations on selecting fields +for an Item see [this section](best-practices.md#field-selection-and-metadata-linking) of the best practices document. + +### What is a SpatioTemporal Asset + +A 'spatiotemporal asset' is any file that represents information about the earth captured in a certain +space and time. Examples include Imagery (from satellites, planes and drones), SAR, Point Clouds (from +LiDAR, Structure from Motion, etc), Data Cubes, Full Motion Video, and data derived from any of those. +The key is that the GeoJSON is not the actual 'thing', but instead references files and serves as an +index to the 'assets'. It is [not recommended](best-practices.md#representing-vector-layers-in-stac) +to use STAC to refer to traditional vector data layers (shapefile, geopackage) as assets, as they +don't quite fit conceptually. + +## Catalogs vs Collections + +Before we go deep into the Catalogs and Collections, it is worth explaining the relationship +between the two and when you might want to use one or the other. + +A Catalog is a very simple construct - it just provides links to Items or to other Catalogs. +The closest analog is a folder in a file structure, it is the container for Items, but it can +also hold other containers (folders / catalogs). + +The Collection specification shares some fields with the catalog spec but has a number of additional fields: +license, extent (spatial and temporal), providers, keywords and summaries. Every Item in a Collection links +back to their Collection, so clients can easily find fields like the license. Thus every Item implicitly +shares the fields described in their parent Collection. + +But what *should* go in a Collection, versus just in a Catalog? A Collection will generally consist of +a set of assets that are defined with the same properties and share higher level metadata. In the +satellite world these would typically all come from the same sensor or constellation. It corresponds +directly to what others call a "dataset series" (ESA, ISO 19115), "collection" (CNES, NASA), and +"dataset" (JAXA, DCAT). So if all your Items have the same properties, they probably belong in +the same Collection. But the construct is deliberately flexible, as there may be good reasons +to break the recommendation. + +Catalogs in turn are used for two main things: + +* Split overly large collections into groups +* Group collections into a catalog of Collections (e.g. as entry point for navigation to several Collections). + +The first case allows users to browse down into the Items of large collections. A collection like +Landsat usually would start with path and row Catalogs to group by geography, and then year, +month and day groups to enable deeper grouping. [Dynamic catalogs](best-practices.md#dynamic-catalogs) can +provide multiple grouping paths, serving as a sort of faceted search. + +The second case is used when one wants to represent diverse data in a single place. If an organization +has an internal catalog with Landsat 8, Sentinel 2, NAIP data and several commercial imagery providers +then they'd have a root catalog that would link to a number of different Collections. + +So in conclusion it's best to use Collections for what you want user to find as starting point, and then +catalogs are just for structuring and grouping the data. Future work includes a mechanism to actually +search Collection-level data, hopefully in concert with other specifications. + +## Catalog Overview + +There are two required element types of a Catalog: Catalog and Item. A STAC Catalog +points to [STAC Items](item-spec/README.md), or to other STAC catalogs. It provides a simple +linking structure that can be used recursively so that many Items can be included in +a single Catalog, organized however the implementor desires. + +STAC makes no formal distinction between a "root" catalog and the "child" catalogs. A root catalog +is simply the top-most catalog -- it has no parent. A nested catalog structure is useful (and +recommended) for breaking up massive numbers of catalog Items into logical groupings. For example, +it might make sense to organize a catalog by date (year, month, day), or geography (continent, +country, state/prov). See the [Catalog Layout](best-practices.md#catalog-layout) best practices +section for more. + +A simple Catalog structure might look like this: + +- catalog (root) + - catalog + - catalog + - item + - asset + - item + - asset + - item + - asset + - asset + +This example might be considered a somewhat "typical" structure. However, Catalogs and Items can +describe a number of different relationships. The following shows various relationships between +catalogs and items: + +- `Catalog` -> `Item` (this is a common structure for a catalog to list links to Items) +- `Catalog` -> `Catalog` (this is a common tree structure to group sets of Items. Each catalog in + this relationship may also include Item links as well as catalog links) + +The relationships are all described by a common `links` object structure, making use of +the `rel` field to further describe the relationship. + +There are a few types of catalogs that implementors occasionally refer to. These get defined by the `links` structure. + +- A **sub-catalog** is a Catalog that is linked to from another Catalog that is used to better organize data. For example a Landsat collection + might have sub-catalogs for each Path and Row, so as to create a nice tree structure for users to follow. +- A **root catalog** is a Catalog that only links to sub-catalogs. These are typically entry points for browsing data. Often + they will contain the [STAC Collection](collection-spec/) definition, but in implementations that publish diverse information it may + contain sub-catalogs that provide a variety of Collections. +- A **parent catalog** is the Catalog that sits directly above a sub-catalog. Following parent catalog links continuously + will naturally end up at a root catalog definition. + +It should be noted that a Catalog does not have to link back to all the other Catalogs that point to it. Thus a published +root catalog might be a sub-catalog of someone else's structure. The goal is for data providers to publish all the +information and links they want to, while also encouraging a natural web of information to arise as Catalogs and Items are +linked to across the web. + +### Static and Dynamic Catalogs + +The Catalog specification is designed so it can be implemented as easily as possibly. This can be as simple as +simply putting linked json files on a file server or an object storage service (like [AWS S3](https://aws.amazon.com/s3/)), +or it can be generated on the fly by a live server. The first type of implementation is often called a 'static catalog', +and any catalog that is not just files is called a 'dynamic catalog'. You can read more about the two types along with +recommendations in [this section](best-practices.md#static-and-dynamic-catalogs) of the best practices document, +along with how to keep a [dynamic catalog in sync](best-practices.md#static-to-dynamic-best-practices) with a static one. + +### Catalog Best Practices + +In addition to information about different catalog types, the [best practices document](best-practices.md) has +a number of suggestions on how to organize and implement good catalogs. The [catalog specification](catalog-spec/catalog-spec.md) +is designed for maximum flexbility, so none of these are required, but they provide guidance for implementors who +want to follow what most of the STAC community is doing. + +- [Catalog Layout](best-practices.md#catalog-layout) is likely the most important section, as following its +recommendations will enable catalogs to work better with client tooling that optimizes for known layouts. +- [Use of Links](best-practices.md#use-of-links) articulates practices for making catalogs that are portable (with +relative links through out) and ones that are published in stable locations (with absolute self links). +- [Versioning for Catalogs](best-practices.md#versioning-for-catalogs) explains how to use STAC's structure to +keep a history of changes made to Items and catalogs. +- [STAC on the Web](best-practices.md#stac-on-the-web) explains how catalogs should have html versions for +each Item and Catalog, as well as ways to achieve that. + +## Collection Overview + +A STAC Collection extends the core fields of the Catalog construct to provide additional metadata to describe the set of Items it +contains. The required fields are fairly +minimal - it includes the 4 required Catalog fields (id, description, stac_version and links), and adds license +and extents. But there are a number of other common fields defined in the spec, and more common fields are also +defined in [STAC extensions](extensions/). These serve as basic metadata, and ideally Collections also link to +fuller metadata (ISO 19115, etc) when it is available. + +As Collections contain all of Catalogs' core fields, they can be used just as flexibly. They can have both parent Catalogs and Collections +as well as child Items, Catalogs and Collections. Items are strongly recommended to have a link to the Collection +they are a part of. Items can only belong to one Collection, so if an Item is in a Collection that is the child of +another Collection, then it must pick which one to refer to. Generally the 'closer' Collection, the more specific +one, should be the one linked to. + +The Collection specification is used standalone quite easily - it is used to describe an aggregation of data, +and doesn't require links down to sub-catalogs and Items. This is most often used when the software +does operations at the layer / coverage level, letting users manipulate a whole collection of assets at once. They often +have an optimized internal format that doesn't make sense to expose as Items. [OpenEO](https://openeo.org/) and +[Google Earth Engine](https://earthengine.google.com/) are two examples that only use STAC collections, and +both would be hardpressed to expose individual Items due to their architectures. For others implementing STAC +Collections can also be a nice way to start and achieve some level of interoperability. diff --git a/package.json b/package.json new file mode 100644 index 00000000..3fb2aeee --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "stac-spec", + "version": "1.0.0-rc.2", + "description": "STAC spec helpers to check the spec.", + "repository": "https://github.com/radiantearth/stac-spec", + "license": "Apache-2.0", + "scripts": { + "check": "npm run check-markdown && npm run check-examples", + "check-markdown": "remark . -f -r .circleci/rc.yaml", + "check-examples": "stac-node-validator . --lint --verbose --schemas .", + "format-examples": "stac-node-validator . --format --schemas .", + "publish-schemas": "node .circleci/publish-schemas.js" + }, + "dependencies": { + "gh-pages": "^3.0.0", + "klaw-sync": "^6.0.0", + "remark-cli": "^8.0.0", + "remark-lint": "^7.0.0", + "remark-lint-no-html": "^2.0.0", + "remark-preset-lint-consistent": "^3.0.0", + "remark-preset-lint-markdown-style-guide": "^3.0.0", + "remark-preset-lint-recommended": "^4.0.0", + "remark-validate-links": "^10.0.0", + "stac-node-validator": "^0.4.6" + } +} diff --git a/principles.md b/principles.md new file mode 100644 index 00000000..03306596 --- /dev/null +++ b/principles.md @@ -0,0 +1,45 @@ +This project will be governed by a set of informal core principles. These principles are not set in stone, +and indeed they should evolve in the same manner that all specifications worked on should - proposed and +reviewed in pull requests, approved by consensus. The goal of the principles is to help avoid +[bikeshedding](http://bikeshed.org/) - lay down some meta-rules so we can focus on creating useful +core geospatial standards. + +* **Creation and evolution of specs in Github**, using Open Source principles +(please read [Producing OSS](http://producingoss.com/) if that phrase doesn't immediately make sense to you). +The collaboration facilities of Github should be used to their full extent. All proposed improvements and +changes should come in the form of pull requests, using code review functionality to discuss changes. + +* **JSON + REST + HTTP at the core.** JSON has won over XML, and REST over SOAP. We embrace them and +are not considering legacy options. Forward looking protocols can be considered as extensions, +but the default specifications should be in JSON, following best REST practices. HTTP caching and +error codes should be leveraged at the core. GeoJSON has already defined the core geospatial JSON response, +so it should also be core. [JSON API](http://jsonapi.org/) should be used as basis of decisions where possible. + +* **Small Reusable Pieces Loosely Coupled** - Each specification should be as focused as possible, +defining one core concept and refraining from describing lots of options. Additional options can be made +as separate specifications that build on the core. But the core specs should be small and easily understandble, +with clear defaults for any choice. Handling complex cases should be possible by combining discrete pieces. +Implementors should not be forced to implement lots of options just for basic compliance - they should be +able to pick and choose which pieces are relevant to the problems they are trying to solve. + +* **Focus on the developer**. Specifications should aim for implementability - any explanation or design choice +should be considered with a developer audience. And specifications should be accessible to developers who do not +have geospatial background. A developer should not need to understand 'projections' to implement a simple feature +access service. But we should think through the spec extensions they could use in the future when their client asks +for data in a different projection. + +* **Working code required.** Proposed changes should be accompanied by working code +(ideally with a link to an online service running the code). A reference implementation should be available +online to power the interactive documentation. Fully accepted specifications should have at least 3 implementations +that cover the entire specification. Extensions have their own [Extension Maturity](./extensions/README.md#extension-maturity) model. + +* **Design for scale.** The design should work great with more data than can be imagined right now. +Ideally implementations are built with large test data sets to validate that they will work. +Everything should be compatible with content distribution network (CDN) caching. + +## Resources + +* Open Source Principles - [Producing Open Source Software](http://producingoss.org) by Karl Fogel. +* Best Practices JSON API Design - [JSON API](http://jsonapi.org/) best practices for making API's with JSON +* Pragmatic REST - [Web API Design: Crafting interfaces that developers love](https://pages.apigee.com/rs/apigee/images/api-design-ebook-2012-03.pdf) +* Open API Initiative - [OpenAPIs.org](https://openapis.org/) diff --git a/process.md b/process.md new file mode 100644 index 00000000..2250e844 --- /dev/null +++ b/process.md @@ -0,0 +1,119 @@ +## STAC Development & Release Process + +### Development Process + +The SpatioTemporal Asset Catalog specification is under active development. The goal is to get to a small, flexible stable +release that can be extended in a variety of ways. The core development team aims to release early and often, which generally +has meant a new release between two and four months since the previous one. + +The `master` branch aims to always be stable, meaning that all the pieces of the specification are consistent and well +explained, and all the examples are consistent with the specification. The `dev` branch is a place of active development, +where a new change in one part of the spec might not yet be fully updated everywhere else. The team uses the +[stac-spec issue tracker](https://github.com/radiantearth/stac-spec/issues) to identify and track all that will be done for +a release. Once all the major issues are resolved the core team makes sure everything is consistent across the spec and +examples. + +Any changes to the spec must be made as pull requests to the `dev` branch. Anyone is welcome and encouraged to bring ideas +and improvements, to the issue tracker or (ideally) as pull requests. To merge a new pull request the work must be reviewed +by at least two members of the STAC spec 'core team' (who have write access to the main repository). It also must pass the +Continuous Integration (CI) testing, which checks all markdown and example files for proper formatting, and also validates all +examples against JSON schema. For more information about making pull requests see [CONTRIBUTING.md](CONTRIBUTING.md), +and there is also information on how to [run the CI checks locally](CONTRIBUTING.md#check-files). + +#### Core STAC Team + +The current list of people who are part of the 'core STAC team', and can approve pull requests. + +* [Alex Kaminsky](https://github.com/alkamin) +* [Alexandra Kirk](https://github.com/anayeaye) +* [Chris Holmes](http://github.com/cholmes) +* [Emmanuel Mathot](https://github.com/emmanuelmathot) +* [Michael Smith](https://github.com/hgs-msmith) +* [James Banting](https://github.com/jbants) +* [James Santucci](https://github.com/jisantuc) +* [Josh Fix](https://github.com/joshfix) +* [Rob Emanuele](https://github.com/lossyrob) +* [Matthias Mohr](https://github.com/m-mohr) +* [Matt Hanson](https://github.com/matthewhanson) +* [Phil Varner](https://github.com/philvarner) + +Anyone can be nominated to the core STAC team, and that generally happens after contributing a few pull requests +and/or helping review other PR's. Nominations are reviewed by the [PSC](#project-steering-committee), and must recieve +3 positive votes and no negatives. + +### Release Process + +To release a new version of the STAC spec the following list of tasks must be done. + +* **Update Issue Tracker**: Each release has a [milestone](https://github.com/radiantearth/stac-spec/milestones) in the github +issue tracker, and before a release is done all open issues that are filed against it should be reviewed. All issues do not +need to be completed, but the core release team should all review the issues to make sure that the critical ones for the +release have been addressed. Issues that aren't seen as essential should be moved to future releases, so that there are no +open issues against the milestone. +* **Agreement from the Project Steering Committee**: The PSC should meet (on phone or on gitter) and decided that the release is ready. +This should include review of the issues, as well as looking at the spec holistically, to make sure the new changes keep +with a coherent whole. +* **Final Spec Read Through**: There should be a final read through of the core specification to make sure it makes sense +and there are no typos, errors, etc. +* **Update the version numbers**: There are several places in the spec that use the version number or a branch name in text +or a link. These include the markdown files and the JSON schemas. Right now the best thing to do is just a search & replace +for the last version number and `https://schemas.stacspec.org/dev/` with `https://schemas.stacspec.org//` +(in JSON Schemas, don't replace it here). `` must correspond with the tag on GitHub, usually including a leading `v`. +Hopefully in the future there will be scripts to do this. +* **Update the Changelog**: The [changelog](CHANGELOG.md) should be reviewed to make sure it includes all major improvements +in the release. And anything in 'unreleased' section should move to the version of the spec to be released. +* **Merge dev to master**: As there is no 'build' process, since the specification *is* the markdown files in the github +repository, the key step in a release is to merge the `dev` branch into `master`, as `master` is the current stable state +of the spec. +* **Release on Github**: The final step to create the release is to add a new 'release' on +. This should use a tag like the others, with a 'v' prefix and then the +release number, like v0.5.2. The changelog should be copied over to be the release notes, and then also include a link to +the full milestone of everything closed in the issue tracker. +* **Promote the release**: A blog post and tweet should be composed and sent out, and then inform those in the gitter channel +to post / promote it. + +#### Release Candidates + +Before any release that has *major* changes (made as a judgement call by the core contributors) there should be a 'release +candidate' to ensure the wider community of implementors can try it out +and catch any errors *before* a full release. It is only through actual implementations that we can be sure the new spec +version is good, so this step is essential if there are major changes. The release should proceed as normal, but called +vX.Y.Z-rc.1. The core STAC community should be told and encouraged to update their implementations. At least 2 implementations +should be updated to the new specification before there is a real release. And ideally a client like STAC Browser is also +updated. This provides the core sanity check. If there are changes or fixes to the spec or +schemas needed from their feedback then make fixes and do rc.2. If it is just fixes to the examples or tooling then no +additional RC is needed. After there is no more changes to spec or schemas then the release process should be done on master, +with no changes to the spec - just updating the version numbers. + +### Governance + +The vast majority of decisions on STAC are made by the [Core STAC Team](#core-stac-team) reaching consensus in +discussions in pull requests and issues. Any change to the specification must have two positive reviews and no negative +reviews. + +#### Project Steering Committee + +For the rare case where a decision cannot be reached by consensus, there is a Project Steerting Committee that has ultimate +decision making authority. This consists of individuals who are intended to represent the various communities which have a +stake in the specification and surrounding ecosystem. An odd number is chosen to facilitate the voting process and help prevent ties. +This committee also handles the allocation of any funds that are raised for the project. + +Turnover is allowed and expected to accommodate people only able to become active on the project in intervals. A PSC member may step down at any time. + +#### Current Project Steering Committee + +* [Matthias Mohr](https://github.com/m-mohr) - University of Muenster, [OpenEO](https://openeo.org/) and [Radiant Earth](https://www.radiant.earth/) +* [Matt Hanson](https://github.com/matthewhanson) - [Element 84](https://www.element84.com/) +* [James Banting](https://github.com/jbants) - [SparkGeo](https://sparkgeo.com/) +* [Rob Emanuele](https://github.com/lossyrob) - [Microsoft](https://microsoft.com/) +* [Chris Holmes](https://github.com/cholmes) - [Planet](https://planet.com) and [Radiant Earth](https://www.radiant.earth/) + +#### PSC Membership + +A new PSC member can be nominated at any time. Voting for a new PSC is done by current active PSC members. PSC nominations are +generally given in recognition to very significant contributions to the specification or the broader ecosystem. PSC members +are not expected to be technical, and we hope to recognized contributions in documentation, outreach and evangelism. + +Nominated PSC members must recieve a majority of +1 vote’s from the PSC, and no -1’s. + +The initial PSC was selected by Chris Holmes, who was deemed the 'Benevolent Dictator for Life' for the bootstrapping phase. diff --git a/schema.json b/schema.json new file mode 100644 index 00000000..e69de29b From 65661d731eb15880e54eb1282f754b4a6f5974c1 Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 20 Apr 2021 09:38:42 -0400 Subject: [PATCH 12/61] Remove CI step to init submodules --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 70cf716f..ccfa3f79 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,9 +27,6 @@ jobs: - image: circleci/node:12 steps: - checkout - - run: - name: init_submodules - command: git submodule update --init --recursive - save_cache: key: v0-repo-{{ .Branch }}-{{ .Revision }} paths: From db611739a333bc8ec6389b37e9825cc195287ac9 Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 20 Apr 2021 09:39:03 -0400 Subject: [PATCH 13/61] Add PR template check that core spec was not changed. --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d4e34f8a..fe8c00a7 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,4 +10,5 @@ - [ ] This PR is made against the dev branch (all proposed changes except releases should be against dev, not master). - [ ] This PR has **no** breaking changes. +- [ ] This PR does not make any changes to the core spec in the `stac-spec` directory (these are included as a subtree and should be updated directly in [radiantearth/stac-spec](https://github.com/radiantearth/stac-spec)) - [ ] I have added my changes to the [CHANGELOG](https://github.com/radiantearth/stac-api-spec/blob/dev/CHANGELOG.md) **or** a CHANGELOG entry is not required. From e1cc9659c5cb669a121d21be574914a650c44cfd Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 4 May 2021 14:01:57 -0400 Subject: [PATCH 14/61] Fix extension links --- fragments/itemcollection/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fragments/itemcollection/README.md b/fragments/itemcollection/README.md index 1c851598..e0d6c115 100644 --- a/fragments/itemcollection/README.md +++ b/fragments/itemcollection/README.md @@ -31,7 +31,7 @@ This object describes a STAC ItemCollection. The fields `type` and `features` ar practices](../../stac-spec/best-practices.md#mixing-stac-versions) in mind. **stac_extensions**: A list of STAC content extensions the ItemCollection implements. The list contains URLs to the JSON Schema files it -can be validated against. For official [content extensions](../../stac-spec/extensions/README.md#list-of-content-extensions), a "shortcut" +can be validated against. For official [STAC extensions](https://stac-extensions.github.io/#list-of-stac-extensions), a "shortcut" can be used. This means you can specify the folder name of the extension, for example `single-file-stac` for the Single File STAC extension. This does *not* apply for [API extensions](../../extensions.md). If the versions of the extension and the item diverge, you can specify the URL of the JSON schema file. This list must only contain extensions that extend the ItemCollection itself, see the @@ -41,5 +41,5 @@ the 'Scope' column in the list of extensions. It must not contain extensions tha - The [Context Extension](../../item-search/README.md#context) adds additional fields to STAC ItemCollection relevant to their use as search results. -- The [Single File STAC Extension](../../stac-spec/extensions/single-file-stac/README.md) provides a set of Collections and Items +- The [Single File STAC Extension](https://github.com/stac-extensions/single-file-stac/blob/main/README.md) provides a set of Collections and Items as a single file catalog. From 2f81a0440d04fba78ae4f0ff83d8aa86a6e39d11 Mon Sep 17 00:00:00 2001 From: Jon Duckworth Date: Tue, 4 May 2021 15:35:10 -0400 Subject: [PATCH 15/61] Add CI check for changes to stac-spec directory --- .circleci/config.yml | 16 ++++++++++++++++ package.json | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ccfa3f79..f7a56114 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,6 +69,16 @@ jobs: command: | ssh-keyscan github.com >> ~/.ssh/known_hosts npm run publish-openapi -- $CIRCLE_TAG + check-core-changes: + working_directory: ~/stac + docker: + - image: circleci/node:12 + steps: + - *restore_repo + - *restore_dependencies + - run: + name: check-core-changes + command: npm run check-stac-spec-changes --compare-to=<< pipeline.git.base_revision >> workflows: version: 2 @@ -84,6 +94,12 @@ workflows: filters: tags: only: /^v.*/ + - check-core-changes: + requires: + - validate + filters: + tags: + only: /^v.*/ - publish: requires: - build diff --git a/package.json b/package.json index 902bb943..5c484cda 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "check-openapi-item-search": "spectral lint item-search/openapi.yaml --ruleset .circleci/.spectral.yml", "check-openapi-fragments": "spectral lint fragments/*/openapi.yaml --ruleset .circleci/.spectral-fragments.yml", "build-openapi": ".circleci/build-openapi.sh", - "publish-openapi": "node .circleci/publish.js" + "publish-openapi": "node .circleci/publish.js", + "check-stac-spec-changes": "git diff --quiet HEAD ${npm_config_compare_to} -- stac-spec" }, "dependencies": { "@stoplight/spectral": "^5.7.1", From 7484ac9a120499c7452b84a1ad7e2768922fd27a Mon Sep 17 00:00:00 2001 From: James Santucci Date: Wed, 5 May 2021 15:49:00 -0600 Subject: [PATCH 16/61] Don't deactivate other search parameters when filtering with ids (#125) * remove deactivation from item ids * update changelog * make same doc change in openapi fragment * ... and in the POST --- CHANGELOG.md | 1 + item-search/README.md | 2 +- item-search/openapi.yaml | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77cf5200..b742cec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed +- Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) - The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) ## [v1.0.0-beta.1] - 2020-12-10 diff --git a/item-search/README.md b/item-search/README.md index 295d799c..05d374ad 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -63,7 +63,7 @@ The core parameters for STAC search are defined by OAFeat, and STAC adds a few p | bbox | \[number] | OAFeat | Requested bounding box. Represented using either 2D or 3D geometries. The length of the array must be 2*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum elevation in meters and the elevation of the northeasterly most extent is the maximum. | | datetime | string | OAFeat | Single date+time, or a range ('/' seperator), formatted to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). Use double dots `..` for open date ranges. | | intersects | GeoJSON Geometry | STAC | Searches items by performing intersection between their geometry and provided GeoJSON geometry. All GeoJSON geometry types must be supported. | -| ids | \[string] | STAC | Array of Item ids to return. All other filter parameters that further restrict the number of search results (except `next` and `limit`) are ignored | +| ids | \[string] | STAC | Array of Item ids to return. | | collections | \[string] | STAC | Array of one or more Collection IDs to include in the search for items. Only Items in one of the provided Collections will be searched | Only one of either **intersects** or **bbox** should be specified. If both are specified, a 400 Bad Request response diff --git a/item-search/openapi.yaml b/item-search/openapi.yaml index 2e4a56f9..e29e64d2 100644 --- a/item-search/openapi.yaml +++ b/item-search/openapi.yaml @@ -98,8 +98,7 @@ components: name: ids in: query description: |- - Array of Item ids to return. All other filter parameters that further - restrict the number of search results are ignored + Array of Item ids to return. required: false schema: $ref: '#/components/schemas/ids' @@ -238,8 +237,7 @@ components: ids: type: array description: |- - Array of Item ids to return. All other filter parameters that further - restrict the number of search results are ignored + Array of Item ids to return. items: type: string datetimeFilter: From 6d51718410062d96912c6adbc543ceacdb131479 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 10 May 2021 17:45:07 -0400 Subject: [PATCH 17/61] initial draft for Filter extension --- CHANGELOG.md | 1 + extensions.md | 12 +- fragments/filter/README.md | 281 ++++++++++++++++++++++++++++++++++ fragments/filter/openapi.yaml | 143 +++++++++++++++++ item-search/openapi.yaml | 16 +- 5 files changed, 446 insertions(+), 7 deletions(-) create mode 100644 fragments/filter/README.md create mode 100644 fragments/filter/openapi.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index b742cec4..36cbeb29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed +- Added Filter extension to integrate OAFeat Part 3 CQL - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) - The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) diff --git a/extensions.md b/extensions.md index adea727f..ff402243 100644 --- a/extensions.md +++ b/extensions.md @@ -50,14 +50,15 @@ are scoped against ogcapi-features*. This is the list of all extensions that are contained in the stac-api-spec repository. -| Extension Name | Scope* | Description | Maturity | -|-----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|------------| -| [Fields](item-search/README.md#fields) | [Item Search](item-search/) request | Adds parameter to control which fields are returned in the response. | *Pilot* | -| [Query](item-search/README.md#query) | [Item Search](item-search/) request | Adds parameter to search Item and Collection properties. | *Pilot* | +| Extension Name | Scope* | Description | Maturity | +|----------------|--------|-------------|----------| +| [Fields](item-search/README.md#fields) | [Item Search](item-search/) request | Adds parameter to control which fields are returned in the response. | *Pilot* | +| [Filter](item-search/README.md#filter) | [Item Search](item-search/) and [STAC - Features API](ogcapi-features) `/items` requests | Adds parameter to search Item and Collection properties. | *Pilot* | | [Context](item-search/README.md#context) | [Item Search](item-search/) response ([ItemCollection](fragments/itemcollection/README.md)) | Adds search related metadata (context) to ItemCollection. | *Proposal* | | [Sort](item-search/README.md#sort) | [Item Search](item-search/) request | Adds Parameter to control sorting of returns results. | *Pilot* | | [Transaction](ogcapi-features/extensions/transaction/README.md) | [STAC - Features API](ogcapi-features) POST on `/items` endpoint, DELETE/PUT on `/items/{itemId}` endpoint | Adds PUT and DELETE endpoints for the creation, editing, and deleting of items and Collections. | *Pilot* | | [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | [STAC - Features API](ogcapi-features) on `/items` endpoint | Adds GET versions resource to collections and items endpoints and provides semantics for a versioning scheme for collections and items. | *Proposal* | +| [Query](item-search/README.md#query) | [Item Search](item-search/) request | Adds parameter to search Item and Collection properties. | *Deprecated* | ### Conformance classes of extensions @@ -67,11 +68,12 @@ the service supports. This are listed at the top of each extension description, | Extension Name | Conformance URI | |-----------------------------------------------------------------------------------|----------------------------------------------------------------------------| | [Fields](item-search/README.md#fields) | | -| [Query](item-search/README.md#query) | | +| [Filter](item-search/README.md#filter) | | | [Context](item-search/README.md#context) | | | [Sort](item-search/README.md#sort) | | | [Transaction](ogcapi-features/extensions/transaction/README.md) | | | [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | | +| [Query](item-search/README.md#query) | | ## Third-party / vendor extensions diff --git a/fragments/filter/README.md b/fragments/filter/README.md new file mode 100644 index 00000000..5cb740d7 --- /dev/null +++ b/fragments/filter/README.md @@ -0,0 +1,281 @@ +# STAC API - Filter Fragment + +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot +- **Dependents:** + - [Item Search](../../item-search) + +## Overview + +The Filter extension provides an expressive mechanism for searching based on Item attributes. + +This extension uses several conformance classes defined in the +[OGC API - Features - Part 3: Filtering and the Common Query Language (CQL)](https://portal.ogc.org/files/96288) +specification. As of May 2020, this specification is in draft status but, due to its long-standing use within +geospatial software, is expected to remain largely the same in final. + +It should be noted that the "CQL" referred to here is OGC CQL. It is **not** referencing or related two other "CQL" languages, +the [SRU (Search/Retrieve via URL) Contextual Query Language](https://www.loc.gov/standards/sru/cql/index.html) (formerly +known as Common Query Language) or the [Cassandra Query Language](https://cassandra.apache.org/doc/latest/cql/) used by the Cassandra database. + +OGC CQL has been previously described +(but not formally defined) in the [https://www.ogc.org/standards/filter](OGC Filter Encoding) standard. +OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of the declarative semantics. + +## Limitations of Item Search + +OAFeat defines a limited set of filtering capabilities. Filtering can only be done over a single collection and +with only a bbox and datetime parameter. + +The STAC Item Search specification extends the functionality of OAFeat in a few key ways: +- It allows cross-collection filtering, whereas OAFeat only allows filtering within a single collection. (`collections` parameter, accepting 0 or more collections) +- It allows filtering by Item ID (`ids` parameter) +- It allows filtering based on a single GeoJSON Geometry, rather than only a bbox (`intersects` parameter) + +## Filter expressiveness + +This extension expands this functionality further with [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) +by providing an expressive query language to construct more complex predicates. The operators are similar to +those provided by SQL. The Simple CQL conformance class requires the logical operators `AND`, `OR`, and `NOT`; +the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, `IN`; the spatial operator +`INTERSECTS` and the temporal operator `ANYINTERACTS`. + +The ANYINTERACTS operator has effectively the same semantics as the existing `datetime` filter. + +For example: +- Use of Item Property values in predicates (e.g., `item.properties.eo:cloud_cover`), using comparison operators +- Items whose `datetime` values are in the month of August of the years 2017-2021, using OR and ANYINTERACTS +- Items whose `geometry` values intersect any one of several Polygons, using OR and INTERSECTS +- Items whose `geometry` values intersect one Polygon, but do not intersect another one, using AND, NOT, and + INTERSECTS + +This extension also supports the Queryables mechanism that allows discovery of what Item fields can be used in +predicates. + +## OAFeat Part 3 Conformance Classes + +OAFeat CQL defines several conformance classes that allow implementers to create arbitrary compositions of +functionality that support whatever expressiveness they need. Implementers many choose not to incur the cost of +implementing functionality they do not need, or may not be able to implement functionality that is not supported by their underlying datastore. For example, Elasticsearch does not support the spatial predicates required by the Enhanced Spatial Operators conformance class. + +The Filter extension **requires** support of these three conformance classes: + +- Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) +- Features Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) +- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) + +Todo: briefly describe which pieces of this are in each CC. + +Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. + +The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, +Functions, Arithmetic Expressions, or Arrays conformance classes. + +There will likely be a change to Simple CQL where this conformance class only requires support of expressions with a property name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes will support arbitrary uses of properties and literals in expression. The primary motivation for this is to allow implementations that use datastores that do not easily support arbitrary expressions like these to implement Simple CQL (e.g., Elasticsearch). + +## Queryables + +The Queryables mechanism allows a client to discover what variables are available for use when writing filter +expressions. These variables can be defined per-collection, and the intersection of these variables over all collections is what is available for filtering when there are no collection restrictions. These queryables are the only variables that may be used in filter expressions, and if any variable is used in expression that is not defined as a queryable, a 400 Bad Request exception should be returned. + +(TBD: there is a proposal to allow finding what queryables are available for a subset of collections, e.g., `/queryables?collections=c1,c3`). + +The Landing Page endpoint (`/`) will have a Link with rel `http://www.opengis.net/def/rel/ogc/1.0/queryables` with an href to the endpoint `/queryables` (TBD: hopefully, this will have a parameter `collections` defined on it to query for the intersection of the queryables in a group of collections). Additionally, each Collection resource will have a Link to the queryables resource for that collection, e.g., `/collections/collection1/queryables`. + +The queryables endpoint returns a JSON Schema describing the names and types variables that may be used in filter expressions. This response is defined in JSON Schema because it is a well-specified typed schema, but it is not used for validating a JSON document derived from it. This schema defines the variables that may be used in a CQL filter. + +These queryable variables are mapped by the service to filter Items. For example, the service may define a queryable with the name "cloud_cover" that can be used in a CQL expression like `cloud_cover <= 10`, with the semantics that only Items where the `properties.eo:cloud_cover` value was <= 10 would match the filter. The server would then translate this into an appropriate query for the data within its datastore. + +Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group of options for the values of these variables. + +## GET Query Parameters and POST JSON fields + +This extension adds three query parameters or JSON fields to a request: + +- filter-lang:`cql-text` or `cql-json` +- filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as a valid value, may reject any others +- filter: CQL filter expression + + Clients are recommended to use `cql-text` for GET and `cql-json` for POST JSON. TBD what requirements we put around supporting both for each method. + +## Interaction with Endpoints + +Landing Page (/) returns: + +```json +{ + "id": "example_stacapi", + "conformsTo": [ + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson", + + "http://www.opengis.net/spec/ogcapi_common-2/1.0/req/collections", + + "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter", + "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter", + "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql", + "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text", + "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json", + + "http://stacspec.org/spec/api/1.0.0-beta.1/core", + "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-search", + "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-response" + ], + "links": [ + { + "title": "Search", + "href": "https://example.org/search", + "rel": "search", + "type": "application/geo+json" + }, + { + "title": "Queryables", + "href": "https://example.org/queryables", + "rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables", + "type": "application/schema+json" + } + ], + "stac_extensions": [], + "stac_version": "1.0.0", +} +```json + +Client can use the link with `"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables"` to retrieve the queryables. + +Queryables endpoint (`/queryables`) returns: + +```json +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "$id" : "https://example.org/queryables", + "type" : "object", + "title" : "Queryables for Example STAC API", + "description" : "Queryable names for the example STAC API Item Search filter.", + "properties" : { + "id" : { + "title" : "ID", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id" + }, + "collection" : { + "title" : "Collection", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection" + }, + "geometry" : { + "title" : "Geometry", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry" + }, + "datetime" : { + "title" : "Datetime", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime" + }, + "eo:cloud_cover" : { + "title" : "Cloud Cover", + "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" + }, + "gsd" : { + "title" : "Ground Sample Distance", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#/properties/gsd" + } + } +} +``` + +Alternately, the client could retrieve the queryables for a single collection with +`/collections/collections1/queryables`, which may have more queryables than available for the entire catalog, since +there may be queryables that are only relevant to that collection. + +Notice in this schema that instead of directly defining the type information about each field, we have instead used the JSON Schema `$ref` mechanism to refer to a STAC schema definition. This not only allows the reuse of these JSON Schema definitions, but also binds an arbitrarily-named Queryable to a specific STAC field. For example, in the above we know that the `eo:cloud_cover` field is referring to the field of the same name in the EO Extension not because they happen to have the same name, but rather because the `$ref` indicates it. The field could just as well be named "cloud_cover", "CloudCover", or "cc", and we would still know it filtered on the EO extension `eo:cloud_cover` field. + +While these do seem quite complex to write and understand, keep in mind that query construction will likely be done with a more ergonomic SDK, and query parsing will be done with the help of a ABNF grammar and OpenAPI schema. + +From the Queryables above, a client could then form the following example filter expressions. + +### Example 1 + +todo: url escape this +``` +GET filter-lang=cql-text&filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' +``` + +Using `filter-lang=cql-json`: + +``` +{ + "filter-lang"="cql-json", + "filter-crs":"http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "filter": { + "and": [ + "eq": [ + { "property": "id" }, + "LC08_L1TP_060247_20180905_20180912_01_T1_L1TP" + ], + "eq": [ + { "property": "collection" }, + "landsat8_l1tp" + ] + ] + } +} +``` + +### Example 2 +Using `filter-lang=cql-text`: + +``` +GET filter-lang=cql-text&filter= +``` + +where `` is: +``` +collection = 'landsat8_l1tp' + AND gsd <= 30 + AND eo:cloud_cover <= 10 + AND datetime ANYINTERACTS 2021-04-08T04:39:23Z/2021-05-07T12:27:57Z + AND INTERSECTS(geometry, POLYGON((43.5845 -79.5442, 43.6079 -79.4893, 43.5677 -79.4632, 43.6129 -79.3925, 43.6223 -79.3238, 43.6576 -79.3163, 43.7945 -79.1178, 43.8144 -79.1542, 43.8555 -79.1714, 43.7509 -79.6390, 43.5845 -79.5442)) +``` + +Using `filter-lang=cql-json`: + +``` +{ + "filter-lang"="cql-json", + "filter": { + "and": [ + "eq": [ + { "property": "collection" }, + "landsat8_l1tp" + ], + "lte": [ + { "property": "eo:cloud_cover" }, + "10" + ], + "anyinteracts": [ + { "property": "datetime" }, + [ "2021-04-08T04:39:23Z", "2021-05-07T12:27:57Z" ] + ], + "intersects": [ + { "property": "geometry" }, + { + "type": "Polygon", + "coordinates": [ + [ + [43.5845,-79.5442], + [43.6079,-79.4893], + [43.5677,-79.4632], + [43.6129,-79.3925], + [43.6223,-79.3238], + [43.6576,-79.3163], + [43.7945,-79.1178], + [43.8144,-79.1542], + [43.8555,-79.1714], + [43.7509,-79.6390], + [43.5845,-79.5442] + ] + ] + } + ] + ] + } +} +``` diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml new file mode 100644 index 00000000..ad1b76b1 --- /dev/null +++ b/fragments/filter/openapi.yaml @@ -0,0 +1,143 @@ +openapi: 3.0.3 +info: + title: The SpatioTemporal Asset Catalog API - Filter + description: Adds parameters to compare properties and only return the items that match + version: 1.0.0-beta.1 +paths: {} + +# todo: add /queryables endpoint +components: + parameters: + filter: + name: filter + x-stac-api-fragment: query + in: query + description: |- + **Extension:** Query + + Query for properties in items. + Use the JSON form of the query used in POST. + required: false + schema: + type: string + filter-lang: + name: filter-lang + x-stac-api-fragment: query + in: query + description: |- + **Extension:** Query + + Query for properties in items. + Use the JSON form of the query used in POST. + required: false + schema: + type: string + filter-crs: + name: filter-crs + x-stac-api-fragment: query + in: query + description: |- + **Extension:** Query + + Query for properties in items. + Use the JSON form of the query used in POST. + required: false + schema: + type: string + schemas: + searchBody: + type: object + x-stac-api-fragment: filter + description: |- + **Extension:** Filter + + Allows users to filter results by property values + properties: + filter: + $ref: '#/components/schemas/filter' + query: + type: object + description: Define which properties to query and the operations to apply + additionalProperties: + $ref: '#/components/schemas/queryProp' + example: + eo:cloud_cover: + gt: 8 + lt: 50 + platform: + eq: 'landsat-8' + datetime: + gte: '2018-02-12T00:00:00Z' + lte: '2018-03-18T12:31:12Z' + pl:item_type: + startsWith: PSScene + product: + in: + - foo + - bar + - baz + eo:gsd: + in: + - 10 + - 20 + queryProp: + description: Apply query operations to a specific property + anyOf: + - description: if the object doesn't contain any of the operators, it is equivalent to using the equals operator + - type: object + description: Match using an operator + properties: + eq: + description: Find items with a property that is equal to the specified value. For strings, a case-insensitive comparison must be performed. + nullable: true + oneOf: + - type: string + - type: number + - type: boolean + neq: + description: Find items that *don't* contain the specified value. For strings, a case-insensitive comparison must be performed. + nullable: true + oneOf: + - type: string + - type: number + - type: boolean + gt: + description: Find items with a property value greater than the specified value. + oneOf: + - type: string + format: date-time + - type: number + lt: + description: Find items with a property value less than the specified value. + oneOf: + - type: string + format: date-time + - type: number + gte: + description: Find items with a property value greater than or equal the specified value. + oneOf: + - type: string + format: date-time + - type: number + lte: + description: Find items with a property value less than or equal the specified value. + oneOf: + - type: string + format: date-time + - type: number + startsWith: + description: Find items with a property that begins with the specified string. A case-insensitive comparison must be performed. + type: string + endsWith: + description: Find items with a property that ends with the specified string. A case-insensitive comparison must be performed. + type: string + contains: + description: Find items with a property that contains the specified literal string, e.g., matches ".*.*". A case-insensitive comparison must be performed. + type: string + in: + description: Find items with a property that equals at least one entry in the specified array. A case-insensitive comparison must be performed. + type: array + items: + oneOf: + - type: string + - type: number \ No newline at end of file diff --git a/item-search/openapi.yaml b/item-search/openapi.yaml index e29e64d2..e8c3d367 100644 --- a/item-search/openapi.yaml +++ b/item-search/openapi.yaml @@ -34,13 +34,14 @@ paths: - Item Search parameters: - $ref: '#/components/parameters/bbox' + - $ref: '#/components/parameters/intersects' - $ref: '#/components/parameters/datetime' - $ref: '#/components/parameters/limit' - $ref: '#/components/parameters/ids' - $ref: '#/components/parameters/collectionsArray' # extensions - $ref: '../fragments/fields/openapi.yaml#/components/parameters/fields' - - $ref: '../fragments/query/openapi.yaml#/components/parameters/query' + - $ref: '../fragments/query/openapi.yaml#/components/parameters/filter' - $ref: '../fragments/sort/openapi.yaml#/components/parameters/sortby' responses: '200': @@ -78,7 +79,7 @@ paths: - $ref: '#/components/schemas/searchBody' # extensions - $ref: '../fragments/fields/openapi.yaml#/components/schemas/searchBody' - - $ref: '../fragments/query/openapi.yaml#/components/schemas/searchBody' + - $ref: '../fragments/filter/openapi.yaml#/components/schemas/searchBody' - $ref: '../fragments/sort/openapi.yaml#/components/schemas/searchBody' responses: '200': @@ -203,6 +204,17 @@ components: default: 10 style: form explode: false + intersects: + name: intersects + in: query + description: |- + The optional intersects parameter filters the result Items in the same was as bbox, only with + a GeoJSON Geometry rather than a bbox. + required: false + schema: + $ref: "../core/commons.yaml#/components/schemas/geometryGeoJSON" + style: form + explode: false schemas: searchBody: description: The search criteria From 3f51664ee13622c113c2190188451bcda35b3c4e Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 10 May 2021 20:43:55 -0400 Subject: [PATCH 18/61] filter extension: update text with a few missing details --- CHANGELOG.md | 9 +++++++++ fragments/filter/README.md | 6 ++++-- fragments/query/README.md | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36cbeb29..305d40f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + ### Changed - Added Filter extension to integrate OAFeat Part 3 CQL - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) - The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) +### Deprecated +- Query extension is now deprecated. Replaced by the Filter extension using OGC CQL + +### Removed + +### Fixed + ## [v1.0.0-beta.1] - 2020-12-10 ### Added diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 5cb740d7..d9f306f9 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -69,7 +69,7 @@ Todo: briefly describe which pieces of this are in each CC. Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, -Functions, Arithmetic Expressions, or Arrays conformance classes. +Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance classes and their operations is both allowed and encouraged. There will likely be a change to Simple CQL where this conformance class only requires support of expressions with a property name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes will support arbitrary uses of properties and literals in expression. The primary motivation for this is to allow implementations that use datastores that do not easily support arbitrary expressions like these to implement Simple CQL (e.g., Elasticsearch). @@ -92,7 +92,7 @@ Queryables can be static or dynamically derived. For example, `cloud_cover` migh This extension adds three query parameters or JSON fields to a request: -- filter-lang:`cql-text` or `cql-json` +- filter-lang:`cql-text` or `cql-json`. If undefined, defaults to `cql-text`. - filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as a valid value, may reject any others - filter: CQL filter expression @@ -191,6 +191,8 @@ While these do seem quite complex to write and understand, keep in mind that que From the Queryables above, a client could then form the following example filter expressions. +These parameters/fields must be supported by the Item Search endpoint. It is recommended that they also be supported in the collection items endpoint (`/collections/$collectionId/items`). + ### Example 1 todo: url escape this diff --git a/fragments/query/README.md b/fragments/query/README.md index 0a034015..b2a1d3f0 100644 --- a/fragments/query/README.md +++ b/fragments/query/README.md @@ -1,8 +1,8 @@ # STAC API - Query Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - Likely to get deprecated in the future in favor of [CQL](http://docs.opengeospatial.org/DRAFTS/19-079.html). +- **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Deprecated in + favor of the [Filter Extension](../filter/README.md) using [CQL](http://docs.opengeospatial.org/DRAFTS/19-079.html). - **Dependents:** - [Item Search](../../item-search) From 1d32f243cf55cf318647059d6d8eb92985560768 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 11 May 2021 11:43:21 -0400 Subject: [PATCH 19/61] add more details around implementation requirements --- fragments/filter/README.md | 188 +++++++++++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 8 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index d9f306f9..fb380227 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -60,16 +60,21 @@ implementing functionality they do not need, or may not be able to implement fun The Filter extension **requires** support of these three conformance classes: -- Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) -- Features Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) -- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) +- Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) - defines the Queryables mechanism and + parameters `filter-lang`, filter-crs`, and `filter` +- Features Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) - defines that the parameters defined in `Filter` apply to the Features endpoint (`/collections/{collectionId}/items`) defined by OAFeat Part 1. +- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used for the `filter` parameter defined by Filter -Todo: briefly describe which pieces of this are in each CC. +This STAC Filter extension extends the Features Filter conformance class such that these parameters also apply +to the STAC Item Search resource (/search). Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, -Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance classes and their operations is both allowed and encouraged. +Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance +classes and their operations is both allowed and encouraged. Implementation of these is often limited by the +operations supported by the implementation's datastore, for example, Elasticsearch does not support the spatial +operations required by the Enhanced Spatial Operators. There will likely be a change to Simple CQL where this conformance class only requires support of expressions with a property name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes will support arbitrary uses of properties and literals in expression. The primary motivation for this is to allow implementations that use datastores that do not easily support arbitrary expressions like these to implement Simple CQL (e.g., Elasticsearch). @@ -88,15 +93,18 @@ These queryable variables are mapped by the service to filter Items. For example Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group of options for the values of these variables. +TBD: What is the recommended naming scheme for queryables? Root item fields should have the same name. Properties should have the short name not prefixed by `properties`. Everything else should be fully-qualified? + ## GET Query Parameters and POST JSON fields -This extension adds three query parameters or JSON fields to a request: +This extension adds three GET query parameters or POST JSON fields to a request: -- filter-lang:`cql-text` or `cql-json`. If undefined, defaults to `cql-text`. +- filter-lang:`cql-text` or `cql-json`. If undefined, defaults to `cql-text` for a GET request and `cql-json` for a POST request. - filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as a valid value, may reject any others - filter: CQL filter expression - Clients are recommended to use `cql-text` for GET and `cql-json` for POST JSON. TBD what requirements we put around supporting both for each method. +API implementations advertise which `filter-lang` values are supported via conformance classes in the Landing Page. +At least one must be implemented, but it is recommended to implement both. If both are advertised as conformance classes, the server should process either for a GET request, but may only process cql-json for a POST request. If POST of cql-text is not supported, the server must return a 400 error if `filter-lang=cql-text`. ## Interaction with Endpoints @@ -281,3 +289,167 @@ Using `filter-lang=cql-json`: } } ``` + + +## Additional Examples + +We'll be imagining these as queries against [EarthSearch Sentinel 2 +COG](https://stacindex.org/catalogs/earth-search#/Cnz1sryATwWudkxyZekxWx6356v9RmvvCcLLw79uHWJUDvt2?t=items)' data. +A sample STAC Item (leaving off all the asset info) is: + +```json +{ + "type": "Feature", + "stac_version": "1.0.0-beta.2", + "stac_extensions": [ + "eo", + "view", + "proj" + ], + "id": "S2A_60HWB_20201111_0_L2A", + "bbox": [ 176.9997779369264, -39.83783732066656, 178.14624317719924, -38.842842449352425], + "geometry": { + "type": "Polygon", + "coordinates": [[[176.9997779369264, -39.83783732066656],[176.99978104582647,-38.84846679951431], + [178.14624317719924, -38.842842449352425],[177.8514661209684,-39.83471270154608], + [176.9997779369264, -39.83783732066656]]] + }, + "properties": { + "datetime": "2020-11-11T22:16:58Z", + "platform": "sentinel-2a", + "constellation": "sentinel-2", + "instruments": ["msi"], + "gsd": 10, + "view:off_nadir": 0, + "proj:epsg": 32760, + "sentinel:utm_zone": 60, + "sentinel:latitude_band": "H", + "sentinel:grid_square": "WB", + "sentinel:sequence": "0", + "sentinel:product_id": "S2A_MSIL2A_20201111T221611_N0214_R129_T60HWB_20201111T235959", + "sentinel:data_coverage": 78.49, + "eo:cloud_cover": 0.85, + "sentinel:valid_cloud_cover": true, + "created": "2020-11-12T02:08:31.563Z", + "updated": "2020-11-12T02:08:31.563Z" +}, +``` + +One problem in working with Sentinel-2 data is that many scenes only contain a tiny "sliver" of data, where the satellite's recording path intersection only a corner of a grid square. This examples shows +Show me all imagery that has low cloud cover (less than 10), and high data coverage (50), as I'd like a cloud free image that is not just +a tiny sliver of data. + +#### AND cql-text (GET) + +```http +/search?filter=sentinel:data_coverage > 50 AND eo:cloud_cover < 10 +``` + +#### AND cql-json (POST) + +```json +{ + "filter": { + "and": [ + { + "gt": { + "property": "sentinel:data_coverage", + "value": 50 + } + }, + { + "lt": { + "property": "eo:cloud_cover", + "value": 10 + } + } + ] + } +} +``` + +An 'or' is also supported, matching if either condition is true. Though it's not a sensible query you could get images that have full data +coverage or low cloud cover. + +#### OR cql-text (GET) + +```http +/search?filter=sentinel:data_coverage > 50 OR eo:cloud_cover < 10 +``` + +#### OR cql-json (POST) + +```json +{ + "filter": { + "or": [ + { + "gt": { + "property": "sentinel:data_coverage", + "value": 50 + } + }, + { + "lt": { + "property": "eo:cloud_cover", + "value": 10 + } + } + ] + } +} +``` + +### Temporal + +The temporal support in required core is pretty minimal, with just `ANYINTERACT` + +#### ANYINTERACT cql-text (GET) + +```http +/search?filter=datetime ANYINTERACT 2020-11-11 +``` + +#### ANYINTERACT cql-json (POST) + +```json +{ + "filter": { + "anyinteract": { + "property": "datetime", + "value": "2020-11-11" + } + } +} +``` + +### Geometry + +Similarly in core there is only one geometry operator - `INTERSECTS` + +#### INTERSECTS cql-text (GET) + +```http +/search?filter=INTERSECTS(geometry,POLYGON((-77.0824 38.7886,-77.0189 38.7886,-77.0189 38.8351,-77.0824 38.8351,-77.0824 38.7886))) +``` + +#### INTERSECTS cql-json (POST) + +```json +{ + "filter": { + "intersects": { + "property": "geometry", + "value": { + "type": "Polygon", + "coordinates": [[ + [-77.0824, 38.7886], [-77.0189, 38.7886], + [-77.0189, 38.8351], [-77.0824, 38.8351], + [-77.0824, 38.7886] + ]] + } + } + }, +} +``` + From 4573b1aada99a396d1f1539ceaf47fdb2737434b Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 11 May 2021 11:51:03 -0400 Subject: [PATCH 20/61] add type for collections --- core/commons.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/commons.yaml b/core/commons.yaml index 98aa60b5..f0c0b9f1 100644 --- a/core/commons.yaml +++ b/core/commons.yaml @@ -65,6 +65,7 @@ components: type: object required: - stac_version + - type - id - description - license @@ -75,6 +76,8 @@ components: $ref: '#/components/schemas/stac_version' stac_extensions: $ref: '#/components/schemas/stac_extensions' + type: + type: string id: description: identifier of the collection used, for example, in URIs type: string @@ -87,7 +90,6 @@ components: Detailed multi-line description to fully explain the catalog or collection. - [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. keywords: @@ -148,6 +150,7 @@ components: example: stac_version: 1.0.0-beta.2 stac_extensions: [] + type: Collection id: Sentinel-2 title: 'Sentinel-2 MSI: MultiSpectral Instrument, Level-1C' description: | From eb48d43a56a68c83a5d237af96673357ec79a8e7 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 11 May 2021 11:52:12 -0400 Subject: [PATCH 21/61] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 848a798c..040a1ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Catalog now has required field "type" +- Catalog and Collection definitions now have required field "type" ### Changed - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) From d7b8ce1344622f28e5fbe57c3c557ded92e38d58 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 11 May 2021 12:10:01 -0400 Subject: [PATCH 22/61] add ToC to all readmes, add text about the purpose of extensions --- README.md | 7 +++++++ core/README.md | 3 +++ item-search/README.md | 23 +++++++++++++++++++++++ ogcapi-features/README.md | 4 ++++ 4 files changed, 37 insertions(+) diff --git a/README.md b/README.md index 5e89be59..7e84ab83 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,13 @@ # STAC API +- [STAC API](#stac-api) + - [About](#about) + - [Stability Note](#stability-note) + - [Communication](#communication) + - [In this repository](#in-this-repository) + - [Contributing](#contributing) + ## About The SpatioTemporal Asset Catalog (STAC) specification aims to standardize the way geospatial assets are exposed online and queried. diff --git a/core/README.md b/core/README.md index fb09a049..e6bcfa34 100644 --- a/core/README.md +++ b/core/README.md @@ -1,5 +1,8 @@ # STAC API - Core Specification +- [STAC API - Core Specification](#stac-api---core-specification) + - [Potential Link Relations at `/`](#potential-link-relations-at-) + - **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)), and [commons.yaml](commons.yaml) is the OpenAPI version of the core [STAC spec](../stac-spec) JSON Schemas. - **Conformance URI:** diff --git a/item-search/README.md b/item-search/README.md index 05d374ad..c8fc3005 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -1,5 +1,21 @@ # STAC API - Item Search +- [STAC API - Item Search](#stac-api---item-search) + - [Query Parameters and Fields](#query-parameters-and-fields) + - [Query Examples](#query-examples) + - [Query Parameter Table](#query-parameter-table) + - [Response](#response) + - [Paging](#paging) + - [HTTP Request Methods and Content Types](#http-request-methods-and-content-types) + - [GET](#get) + - [POST](#post) + - [PUT / PATCH / DELETE](#put--patch--delete) + - [Extensions](#extensions) + - [Fields](#fields) + - [Query](#query) + - [Sort](#sort) + - [Context](#context) + - **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/item-search)) - **Conformance URI:** - **Dependencies**: [STAC API - Core](../core) @@ -11,6 +27,13 @@ valid [GeoJSON FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3. query parameters are defined by [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html), with a few additions specified in this document. +The Item Search endpoint intentionally defines only a limited group of operations. It is expected that +most behavior will be defined in [Extensions](#extensions). These extensions can be composed by an implementer to +cover only the set of functionality the implementer requires. For example, the query capability defined by +Item Search is limited, and only adds cross-collection and spatial intersects query operators to the capabilities +already defined by OAFeat. For example, the Query Extension (soon to be superseded by the Filter Extension) +provides a more expressive set of operators. + Implementing `GET /search` is **required**, `POST /search` is optional, but recommended. It is **required** to add a Link to the root endpoint (`/`) with the `rel` type set to `search` diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 357f9853..b6553c44 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -1,5 +1,9 @@ # STAC API - Features +- [STAC API - Features](#stac-api---features) + - [Endpoints](#endpoints) + - [Examples](#examples) + *based on [**OGC API - Features - Part 1: Core**](https://www.ogc.org/standards/ogcapi-features)* - **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features)) From ae9db547df38ca5c8256cd21a14d035b9d7a761e Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 11 May 2021 15:45:28 -0400 Subject: [PATCH 23/61] draft openapi spec --- fragments/filter/openapi.yaml | 157 ++++++++++++---------------------- 1 file changed, 54 insertions(+), 103 deletions(-) diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index ad1b76b1..943c08fd 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -3,44 +3,62 @@ info: title: The SpatioTemporal Asset Catalog API - Filter description: Adds parameters to compare properties and only return the items that match version: 1.0.0-beta.1 -paths: {} -# todo: add /queryables endpoint +paths: + /queryables: + get: + summary: Get the JSON Schema defining the list of variable terms that can be used in CQL expressions. + operationId: getQueryables + description: |- + This endpoint returns a list of variable terms that can be used in CQL expressions. The + precise definition of this can be found in the OGC API - Features - Part 3: Filtering and the + Common Query Language (CQL) specification. + tags: + - Queryables + # parameters: + # todo: may have collections parameter in the future + responses: + '200': + description: A JSON Schema defining the Queryables + content: + application/schema+json: + schema: + $ref: '' # TBD + default: + $ref: '../core/commons.yaml#/components/responses/Error' components: parameters: filter: name: filter - x-stac-api-fragment: query + x-stac-api-fragment: filter in: query description: |- - **Extension:** Query + **Extension:** Filter - Query for properties in items. - Use the JSON form of the query used in POST. - required: false + A filter for properties in Items. + required: true schema: type: string filter-lang: name: filter-lang - x-stac-api-fragment: query + x-stac-api-fragment: filter in: query description: |- - **Extension:** Query + **Extension:** Filter - Query for properties in items. - Use the JSON form of the query used in POST. + The Filter Language using in the text in the filter parameter. Must be one of 'cql-text' or 'cql-json'. required: false schema: type: string filter-crs: name: filter-crs - x-stac-api-fragment: query + x-stac-api-fragment: filter in: query description: |- - **Extension:** Query + **Extension:** Filter - Query for properties in items. - Use the JSON form of the query used in POST. + The CRS used by spatial predicates in the filter parameter. In STAC API, only value that must be accepted + is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'. required: false schema: type: string @@ -49,95 +67,28 @@ components: type: object x-stac-api-fragment: filter description: |- - **Extension:** Filter - - Allows users to filter results by property values + **Optional Extension:** Filter + + A filter for properties in Items. properties: filter: $ref: '#/components/schemas/filter' - query: + filter-lang: + $ref: '#/components/schemas/filter-lang' + filter-crs: + $ref: '#/components/schemas/filter-crs' + filter: + description: | + TBD type: object - description: Define which properties to query and the operations to apply - additionalProperties: - $ref: '#/components/schemas/queryProp' - example: - eo:cloud_cover: - gt: 8 - lt: 50 - platform: - eq: 'landsat-8' - datetime: - gte: '2018-02-12T00:00:00Z' - lte: '2018-03-18T12:31:12Z' - pl:item_type: - startsWith: PSScene - product: - in: - - foo - - bar - - baz - eo:gsd: - in: - - 10 - - 20 - queryProp: - description: Apply query operations to a specific property - anyOf: - - description: if the object doesn't contain any of the operators, it is equivalent to using the equals operator - - type: object - description: Match using an operator - properties: - eq: - description: Find items with a property that is equal to the specified value. For strings, a case-insensitive comparison must be performed. - nullable: true - oneOf: - - type: string - - type: number - - type: boolean - neq: - description: Find items that *don't* contain the specified value. For strings, a case-insensitive comparison must be performed. - nullable: true - oneOf: - - type: string - - type: number - - type: boolean - gt: - description: Find items with a property value greater than the specified value. - oneOf: - - type: string - format: date-time - - type: number - lt: - description: Find items with a property value less than the specified value. - oneOf: - - type: string - format: date-time - - type: number - gte: - description: Find items with a property value greater than or equal the specified value. - oneOf: - - type: string - format: date-time - - type: number - lte: - description: Find items with a property value less than or equal the specified value. - oneOf: - - type: string - format: date-time - - type: number - startsWith: - description: Find items with a property that begins with the specified string. A case-insensitive comparison must be performed. - type: string - endsWith: - description: Find items with a property that ends with the specified string. A case-insensitive comparison must be performed. - type: string - contains: - description: Find items with a property that contains the specified literal string, e.g., matches ".*.*". A case-insensitive comparison must be performed. - type: string - in: - description: Find items with a property that equals at least one entry in the specified array. A case-insensitive comparison must be performed. - type: array - items: - oneOf: - - type: string - - type: number \ No newline at end of file + filter-lang: + description: | + TBD + type: string + enum: + - 'cql-text' + - 'cql-json' + filter-crs: + description: | + TBD + type: string \ No newline at end of file From 26d22cebc610e37a2599337ec62bfd069d40fbea Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 10:19:17 -0400 Subject: [PATCH 24/61] update examples, and implementation section --- fragments/filter/README.md | 156 +++++++++++++++++++++++++------------ 1 file changed, 106 insertions(+), 50 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index fb380227..63df68ac 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -5,6 +5,34 @@ - **Dependents:** - [Item Search](../../item-search) +- [STAC API - Filter Fragment](#stac-api---filter-fragment) + - [Overview](#overview) + - [Limitations of Item Search](#limitations-of-item-search) + - [Filter expressiveness](#filter-expressiveness) + - [OAFeat Part 3 Conformance Classes](#oafeat-part-3-conformance-classes) + - [Queryables](#queryables) + - [GET Query Parameters and POST JSON fields](#get-query-parameters-and-post-json-fields) + - [Interaction with Endpoints](#interaction-with-endpoints) + - [Examples](#examples) + - [Example 1](#example-1) + - [GET with cql-text](#get-with-cql-text) + - [POST with cql-json](#post-with-cql-json) + - [Example 2](#example-2) + - [GET with cql-text](#get-with-cql-text-1) + - [POST with cql-json](#post-with-cql-json-1) + - [Additional Examples](#additional-examples) + - [AND cql-text (GET)](#and-cql-text-get) + - [AND cql-json (POST)](#and-cql-json-post) + - [OR cql-text (GET)](#or-cql-text-get) + - [OR cql-json (POST)](#or-cql-json-post) + - [Temporal](#temporal) + - [ANYINTERACTS cql-text (GET)](#anyinteracts-cql-text-get) + - [ANYINTERACTS cql-json (POST)](#anyinteracts-cql-json-post) + - [Spatial](#spatial) + - [INTERSECTS cql-text (GET)](#intersects-cql-text-get) + - [INTERSECTS cql-json (POST)](#intersects-cql-json-post) + - [Implementation](#implementation) + ## Overview The Filter extension provides an expressive mechanism for searching based on Item attributes. @@ -12,15 +40,15 @@ The Filter extension provides an expressive mechanism for searching based on Ite This extension uses several conformance classes defined in the [OGC API - Features - Part 3: Filtering and the Common Query Language (CQL)](https://portal.ogc.org/files/96288) specification. As of May 2020, this specification is in draft status but, due to its long-standing use within -geospatial software, is expected to remain largely the same in final. +geospatial software (e.g., GeoServer), is expected to remain largely the same in final. It should be noted that the "CQL" referred to here is OGC CQL. It is **not** referencing or related two other "CQL" languages, the [SRU (Search/Retrieve via URL) Contextual Query Language](https://www.loc.gov/standards/sru/cql/index.html) (formerly known as Common Query Language) or the [Cassandra Query Language](https://cassandra.apache.org/doc/latest/cql/) used by the Cassandra database. OGC CQL has been previously described -(but not formally defined) in the [https://www.ogc.org/standards/filter](OGC Filter Encoding) standard. -OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of the declarative semantics. +in the [https://www.ogc.org/standards/filter](OGC Filter Encoding) standard and [OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) (including a BNF grammar in Annex B). +OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar (largely similar to the BNF grammar in the General Model) and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of the declarative semantics. ## Limitations of Item Search @@ -65,7 +93,7 @@ The Filter extension **requires** support of these three conformance classes: - Features Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) - defines that the parameters defined in `Filter` apply to the Features endpoint (`/collections/{collectionId}/items`) defined by OAFeat Part 1. - Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used for the `filter` parameter defined by Filter -This STAC Filter extension extends the Features Filter conformance class such that these parameters also apply +This STAC Filter extension extends the Filter conformance class such that these parameters also apply to the STAC Item Search resource (/search). Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. @@ -78,10 +106,16 @@ operations required by the Enhanced Spatial Operators. There will likely be a change to Simple CQL where this conformance class only requires support of expressions with a property name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes will support arbitrary uses of properties and literals in expression. The primary motivation for this is to allow implementations that use datastores that do not easily support arbitrary expressions like these to implement Simple CQL (e.g., Elasticsearch). +There will also likely be a change where the Simple CQL conformance class is decomposed into several other +conformance classes to aid composition. After these changes, it is possible that this extension will not require +the implementation of the operators IN, BETWEEN, LIKE, and IS NULL predicates. + ## Queryables -The Queryables mechanism allows a client to discover what variables are available for use when writing filter -expressions. These variables can be defined per-collection, and the intersection of these variables over all collections is what is available for filtering when there are no collection restrictions. These queryables are the only variables that may be used in filter expressions, and if any variable is used in expression that is not defined as a queryable, a 400 Bad Request exception should be returned. +The Queryables mechanism allows a client to discover what variable terms are available for use when writing filter +expressions. These variables can be defined per-collection, and the intersection of these variables over all collections is what is available for filtering when there are no collection restrictions. These queryables are the only variables that may be used in filter expressions, and if any variable is used in expression that is not defined as a queryable, a 400 Bad Request exception should be returned. + +Implementers should add queryables for all root Item fields (e.g., id, collection, geometry) with those names. Fields in Item Properties should also be exposed with their names, and not require expressions to prefix them with `properties`. **Everything else should be fully-qualified? How to do more deeply-nested queries and those on lists is an open question. How to disambiguate names that could appear in multiple places in an Item?** (TBD: there is a proposal to allow finding what queryables are available for a subset of collections, e.g., `/queryables?collections=c1,c3`). @@ -93,8 +127,6 @@ These queryable variables are mapped by the service to filter Items. For example Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group of options for the values of these variables. -TBD: What is the recommended naming scheme for queryables? Root item fields should have the same name. Properties should have the short name not prefixed by `properties`. Everything else should be fully-qualified? - ## GET Query Parameters and POST JSON fields This extension adds three GET query parameters or POST JSON fields to a request: @@ -201,16 +233,26 @@ From the Queryables above, a client could then form the following example filter These parameters/fields must be supported by the Item Search endpoint. It is recommended that they also be supported in the collection items endpoint (`/collections/$collectionId/items`). +## Examples + +Note: the GET examples with query parameters are unescaped to make them easier to read. + ### Example 1 -todo: url escape this +#### GET with cql-text + +Note that `filter-lang` defaults to `cql-text` in this case, so this is only shown for completeness. + ``` -GET filter-lang=cql-text&filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' +GET /search?filter-lang=cql-text&filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' ``` -Using `filter-lang=cql-json`: +#### POST with cql-json + +Note that `filter-lang` defaults to `cql-json` and `filter-crs` defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` in this case. ``` +POST /search { "filter-lang"="cql-json", "filter-crs":"http://www.opengis.net/def/crs/OGC/1.3/CRS84", @@ -230,24 +272,21 @@ Using `filter-lang=cql-json`: ``` ### Example 2 -Using `filter-lang=cql-text`: -``` -GET filter-lang=cql-text&filter= -``` +#### GET with cql-text -where `` is: ``` -collection = 'landsat8_l1tp' +GET /search?filter=collection = 'landsat8_l1tp' AND gsd <= 30 AND eo:cloud_cover <= 10 AND datetime ANYINTERACTS 2021-04-08T04:39:23Z/2021-05-07T12:27:57Z AND INTERSECTS(geometry, POLYGON((43.5845 -79.5442, 43.6079 -79.4893, 43.5677 -79.4632, 43.6129 -79.3925, 43.6223 -79.3238, 43.6576 -79.3163, 43.7945 -79.1178, 43.8144 -79.1542, 43.8555 -79.1714, 43.7509 -79.6390, 43.5845 -79.5442)) ``` -Using `filter-lang=cql-json`: +#### POST with cql-json ``` +POST /search { "filter-lang"="cql-json", "filter": { @@ -290,7 +329,6 @@ Using `filter-lang=cql-json`: } ``` - ## Additional Examples We'll be imagining these as queries against [EarthSearch Sentinel 2 @@ -349,21 +387,25 @@ a tiny sliver of data. ```json { - "filter": { - "and": [ + "filter": { + "and": [ { - "gt": { - "property": "sentinel:data_coverage", - "value": 50 - } + "gt": [ + { + "property": "sentinel:data_coverage" + }, + 50 + ] }, { - "lt": { - "property": "eo:cloud_cover", - "value": 10 - } + "lt": [ + { + "property": "eo:cloud_cover" + }, + 10 + ] } - ] + ] } } ``` @@ -384,15 +426,15 @@ coverage or low cloud cover. "filter": { "or": [ { - "gt": { - "property": "sentinel:data_coverage", - "value": 50 - } + "gt": [ + { "property": "sentinel:data_coverage" }, + 50 + ] }, { "lt": { - "property": "eo:cloud_cover", - "value": 10 + { "property": "eo:cloud_cover" }, + 10 } } ] @@ -402,30 +444,33 @@ coverage or low cloud cover. ### Temporal -The temporal support in required core is pretty minimal, with just `ANYINTERACT` +The only temporal operator required is `ANYINTERACTS`, which follows the same semantics as the existing +`datetime` parameter. This is effectively that the datetime or interval operands have any overlap between them. -#### ANYINTERACT cql-text (GET) +#### ANYINTERACTS cql-text (GET) ```http -/search?filter=datetime ANYINTERACT 2020-11-11 +/search?filter=datetime ANYINTERACTS 2020-11-11 ``` -#### ANYINTERACT cql-json (POST) +#### ANYINTERACTS cql-json (POST) ```json { "filter": { - "anyinteract": { - "property": "datetime", - "value": "2020-11-11" - } + "anyinteracts": [ + { "property": "datetime" }, + "2020-11-11" + ] } } ``` -### Geometry +### Spatial -Similarly in core there is only one geometry operator - `INTERSECTS` +The only spatial operator that must be implemented is `INTERSECTS`. This has the same semantics as the one provided +in the Item Search `intersects` parameter. The `cql-text` format uses WKT geometries and the `cql-json` format uses +GeoJSON geometries. #### INTERSECTS cql-text (GET) @@ -438,9 +483,9 @@ Similarly in core there is only one geometry operator - `INTERSECTS` ```json { "filter": { - "intersects": { - "property": "geometry", - "value": { + "intersects": [ + { "property": "geometry" } , + { "type": "Polygon", "coordinates": [[ [-77.0824, 38.7886], [-77.0189, 38.7886], @@ -448,8 +493,19 @@ Similarly in core there is only one geometry operator - `INTERSECTS` [-77.0824, 38.7886] ]] } - } + ] }, } ``` +## Implementation + +* The ABNF for cql-text and OpenAPI for cql-json are in the [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) +* xtraplatform-spatial has a CQL [ANTLR 4 grammer](https://github.com/interactive-instruments/xtraplatform-spatial/tree/master/xtraplatform-cql/src/main/antlr/de/ii/xtraplatform/cql/infra) +* [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi +* [https://github.com/azavea/franklin](Franklin) is working on it. + +Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not +OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. +[Stacatto](https://github.com/planetlabs/staccato) uses this for their query language implementation, but it is +not compliant with this extension. From b898e7d44612f628f65518c9d6a685b23def4b3c Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 10:49:22 -0400 Subject: [PATCH 25/61] add cors recommendation --- README.md | 3 +++ implementation.md | 22 ++++++++++++++++++++++ overview.md | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 implementation.md diff --git a/README.md b/README.md index 7e84ab83..efb73a05 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,9 @@ Sub-modules aren't checked out by default, so to get the directory populated either use `git submodule update --init --recursive` if you've already cloned it, or clone from the start with `git clone --recursive git@github.com:radiantearth/stac-api-spec.git`. +**Implementation Recommendations:** Recommendations for implementing a STAC API may be found [here](implementation.md). +These are mostly concerns that apply to an entire API implementation and are not part of the specification itself. + ## Contributing Anyone building software that catalogs imagery or other geospatial assets is welcome to collaborate. diff --git a/implementation.md b/implementation.md new file mode 100644 index 00000000..b2b30a66 --- /dev/null +++ b/implementation.md @@ -0,0 +1,22 @@ +# Implementation Recommendations + +This document describes implementation recommendations for a STAC API. + +## CORS + +It is recommended that public APIs advertise a permissive CORS configuration so UIs running on a different domain +may more easily access them. + +APIs should acknowledge pre-flight request headers. In general, these header values should be set on responses: + +``` +access-control-allow-origin: * +access-control-allow-methods: OPTIONS, POST, GET +access-control-allow-headers: Content-Type +access-control-allow-credentials: false +``` + +It is relatively safe to use these headers for all endpoints. However, one may want to restrict the methods to only those that apply to each endpoint. For example, the `/collection/{collectionId}/items` endpoint should only allow OPTIONS and GET, since POST is only used by the Transactions Extension, which presumably would require authentication as it is mutating data. + +Implementations that support the Transactions Extension or require credentials for some operations will need to +implement different behavior, for example, allowing credentials when requests are coming from a trusted domain or also allowing DELETE, PUT, or PATCH methods. diff --git a/overview.md b/overview.md index ee17dc35..631d707d 100644 --- a/overview.md +++ b/overview.md @@ -6,7 +6,7 @@ a subset of the entire catalog, such as for a certain date range, in a particula they care about. STAC API extensions specifies those query parameters, and compliant servers return collections of STAC Items that match the user's preferences. A lot of additional functionality can added through the [OGC API](https://ogcapi.ogc.org/) family of standards, particularly [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) (OAFeat, for our -shorthand). +shorthand). Notes on implementation recommendations may be found [here](implementation.md). ## STAC API Description From 747d2859f69bf0d928d7067063ad24692e629fa2 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 10:51:18 -0400 Subject: [PATCH 26/61] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 040a1ad0..8c1e377a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Catalog and Collection definitions now have required field "type" +- Added recommendation to enable CORS for public APIs ### Changed - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) From facfd04a1ffc3a84cfb22d4e6bf7208a010e5610 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 11:50:52 -0400 Subject: [PATCH 27/61] remove stray comma --- fragments/filter/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 63df68ac..fe464c8b 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -370,7 +370,8 @@ A sample STAC Item (leaving off all the asset info) is: "sentinel:valid_cloud_cover": true, "created": "2020-11-12T02:08:31.563Z", "updated": "2020-11-12T02:08:31.563Z" -}, + } +} ``` One problem in working with Sentinel-2 data is that many scenes only contain a tiny "sliver" of data, where the satellite's recording path intersection only a corner of a grid square. This examples shows From e5031e8f5fdd599862920f1422ac7ee55e78b0fe Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 11:59:24 -0400 Subject: [PATCH 28/61] wordsmith text --- fragments/filter/README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index fe464c8b..af20e666 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -47,7 +47,7 @@ the [SRU (Search/Retrieve via URL) Contextual Query Language](https://www.loc.go known as Common Query Language) or the [Cassandra Query Language](https://cassandra.apache.org/doc/latest/cql/) used by the Cassandra database. OGC CQL has been previously described -in the [https://www.ogc.org/standards/filter](OGC Filter Encoding) standard and [OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) (including a BNF grammar in Annex B). +in [OGC Filter Encoding](https://www.ogc.org/standards/filter) and [OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) (including a BNF grammar in Annex B). OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar (largely similar to the BNF grammar in the General Model) and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of the declarative semantics. ## Limitations of Item Search @@ -60,10 +60,13 @@ The STAC Item Search specification extends the functionality of OAFeat in a few - It allows filtering by Item ID (`ids` parameter) - It allows filtering based on a single GeoJSON Geometry, rather than only a bbox (`intersects` parameter) +However, it does not contain a formalized way to filter based on arbitrary fields in an Item. For example, there is +no way to express the filter "item.properties.eo:cloud_cover is less than 10". + ## Filter expressiveness -This extension expands this functionality further with [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) -by providing an expressive query language to construct more complex predicates. The operators are similar to +This extension expands the capabilities of Item Search with [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) +by providing an expressive query language to construct more complex filter predicates. The operators are similar to those provided by SQL. The Simple CQL conformance class requires the logical operators `AND`, `OR`, and `NOT`; the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, `IN`; the spatial operator `INTERSECTS` and the temporal operator `ANYINTERACTS`. @@ -140,7 +143,7 @@ At least one must be implemented, but it is recommended to implement both. If bo ## Interaction with Endpoints -Landing Page (/) returns: +Landing Page (`/`) returns: ```json { @@ -179,7 +182,7 @@ Landing Page (/) returns: "stac_extensions": [], "stac_version": "1.0.0", } -```json +``` Client can use the link with `"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables"` to retrieve the queryables. @@ -433,11 +436,11 @@ coverage or low cloud cover. ] }, { - "lt": { + "lt": [ { "property": "eo:cloud_cover" }, 10 } - } + ] ] } } From d22cda2f3ba363e9add02d6b8e497ebdbb41b231 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 12:03:53 -0400 Subject: [PATCH 29/61] wordsmith text --- fragments/filter/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index af20e666..149b2d35 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -65,7 +65,8 @@ no way to express the filter "item.properties.eo:cloud_cover is less than 10". ## Filter expressiveness -This extension expands the capabilities of Item Search with [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) +This extension expands the capabilities of Item Search and the OAFeat Items resource with +[OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) by providing an expressive query language to construct more complex filter predicates. The operators are similar to those provided by SQL. The Simple CQL conformance class requires the logical operators `AND`, `OR`, and `NOT`; the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, `IN`; the spatial operator @@ -97,7 +98,8 @@ The Filter extension **requires** support of these three conformance classes: - Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used for the `filter` parameter defined by Filter This STAC Filter extension extends the Filter conformance class such that these parameters also apply -to the STAC Item Search resource (/search). +to the STAC Item Search resource (/search). The OAFeat Filter conformance class already requires that these +parameters work for GET requests to the Items resource (/collections/collectionId/items). POST with a JSON body to the Items resource is not supported, as POST is used by the Transactions Extension for creating Item objects. Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. @@ -132,7 +134,7 @@ Queryables can be static or dynamically derived. For example, `cloud_cover` migh ## GET Query Parameters and POST JSON fields -This extension adds three GET query parameters or POST JSON fields to a request: +This extension adds three GET query parameters or POST JSON fields to an Item Search request: - filter-lang:`cql-text` or `cql-json`. If undefined, defaults to `cql-text` for a GET request and `cql-json` for a POST request. - filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as a valid value, may reject any others From c810d2cbfef48bdc2d2e5d990ae11a133987e607 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 12:05:22 -0400 Subject: [PATCH 30/61] wordsmith text --- fragments/filter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 149b2d35..61ca67de 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -236,7 +236,7 @@ While these do seem quite complex to write and understand, keep in mind that que From the Queryables above, a client could then form the following example filter expressions. -These parameters/fields must be supported by the Item Search endpoint. It is recommended that they also be supported in the collection items endpoint (`/collections/$collectionId/items`). +These parameters/fields must be supported by the Item Search endpoint and Items resource (`/collections/$collectionId/items`). ## Examples From 7edc33085e40ba05eacb13f6fb0034339486e073 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 12 May 2021 16:27:13 -0400 Subject: [PATCH 31/61] improve openapi definition --- fragments/filter/README.md | 14 +- fragments/filter/cql_schema.json | 580 +++++++++++++++++++++++++++++++ fragments/filter/openapi.yaml | 76 +++- 3 files changed, 653 insertions(+), 17 deletions(-) create mode 100644 fragments/filter/cql_schema.json diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 61ca67de..b43e1a72 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -10,6 +10,7 @@ - [Limitations of Item Search](#limitations-of-item-search) - [Filter expressiveness](#filter-expressiveness) - [OAFeat Part 3 Conformance Classes](#oafeat-part-3-conformance-classes) + - [Grammar and schemas](#grammar-and-schemas) - [Queryables](#queryables) - [GET Query Parameters and POST JSON fields](#get-query-parameters-and-post-json-fields) - [Interaction with Endpoints](#interaction-with-endpoints) @@ -101,7 +102,7 @@ This STAC Filter extension extends the Filter conformance class such that these to the STAC Item Search resource (/search). The OAFeat Filter conformance class already requires that these parameters work for GET requests to the Items resource (/collections/collectionId/items). POST with a JSON body to the Items resource is not supported, as POST is used by the Transactions Extension for creating Item objects. -Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). It is recommended that (at least) GET requests support CQL Text and POST requests support CQL JSON. +Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. It is recommended that clients use CQL Text in GET requests and CQL JSON in POST requests. The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance @@ -115,6 +116,15 @@ There will also likely be a change where the Simple CQL conformance class is dec conformance classes to aid composition. After these changes, it is possible that this extension will not require the implementation of the operators IN, BETWEEN, LIKE, and IS NULL predicates. +## Grammar and schemas + +- The [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) includes an ABNF for cql-text and both JSON Schema and OpenAPI specifications for cql-json. The standalone files are: + - [cql.bnf](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.bnf) + - [cql.json](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.json) + - [cql.yml](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.yml) +- A JSON Schema for only the parts of the CQL JSON encoding required by this extension is [here](cql_schema.json) +- xtraplatform-spatial has a CQL [ANTLR 4 grammer](https://github.com/interactive-instruments/xtraplatform-spatial/tree/master/xtraplatform-cql/src/main/antlr/de/ii/xtraplatform/cql/infra) + ## Queryables The Queryables mechanism allows a client to discover what variable terms are available for use when writing filter @@ -506,8 +516,6 @@ GeoJSON geometries. ## Implementation -* The ABNF for cql-text and OpenAPI for cql-json are in the [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) -* xtraplatform-spatial has a CQL [ANTLR 4 grammer](https://github.com/interactive-instruments/xtraplatform-spatial/tree/master/xtraplatform-cql/src/main/antlr/de/ii/xtraplatform/cql/infra) * [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi * [https://github.com/azavea/franklin](Franklin) is working on it. diff --git a/fragments/filter/cql_schema.json b/fragments/filter/cql_schema.json new file mode 100644 index 00000000..5115df0f --- /dev/null +++ b/fragments/filter/cql_schema.json @@ -0,0 +1,580 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "oneOf": [ + {"$ref": "#/$defs/andExpression"}, + {"$ref": "#/$defs/orExpression"}, + {"$ref": "#/$defs/notExpression"}, + {"$ref": "#/$defs/comparisonPredicate"}, + {"$ref": "#/$defs/spatialPredicate"}, + {"$ref": "#/$defs/temporalPredicate"} + ], + "$recursiveAnchor": true, + "$defs": { + "$comment": "=======================================================", + "$comment": "= BOOLEAN VALUE EXPRESSION =", + "$comment": "=======================================================", + + "andExpression": { + "type": "object", + "required": ["and"], + "properties": { + "and": { + "type": "array", + "minItems": 2, + "items": { + "$recursiveRef": "#" + } + } + } + }, + + "orExpression": { + "type": "object", + "required": ["or"], + "properties": { + "or": { + "type": "array", + "minItems": 2, + "items": { + "$recursiveRef": "#" + } + } + } + }, + + "notExpression": { + "type": "object", + "required": ["not"], + "properties": { + "not": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "$recursiveRef": "#" + } + } + } + }, + + "$comment": "=======================================================", + "$comment": "= COMPARISON PREDICATES =", + "$comment": "=======================================================", + + "comparisonPredicate" : { + "oneOf": [ + {"$ref": "#/$defs/binaryComparisonPredicate"}, + {"$ref": "#/$defs/isLikePredicate" }, + {"$ref": "#/$defs/isBetweenPredicate"}, + {"$ref": "#/$defs/isInListPredicate" }, + {"$ref": "#/$defs/isNullPredicate" } + ] + }, + + "binaryComparisonPredicate": { + "oneOf": [ + { "$ref": "#/$defs/eqExpression" }, + { "$ref": "#/$defs/ltExpression" }, + { "$ref": "#/$defs/gtExpression" }, + { "$ref": "#/$defs/lteExpression" }, + { "$ref": "#/$defs/gteExpression" } + ] + }, + + "eqExpression": { + "type": "object", + "required": ["eq"], + "properties": { + "eq": { "$ref": "#/$defs/scalarOperands" } + } + }, + + "ltExpression": { + "type": "object", + "required": ["lt"], + "properties": { + "lt": { "$ref": "#/$defs/scalarOperands" } + } + }, + + "gtExpression": { + "type": "object", + "required": ["gt"], + "properties": { + "gt": { "$ref": "#/$defs/scalarOperands" } + } + }, + + "lteExpression": { + "type": "object", + "required": ["lte"], + "properties": { + "lte": { "$ref": "#/$defs/scalarOperands" } + } + }, + + "gteExpression": { + "type": "object", + "required": ["gte"], + "properties": { + "gte": { "$ref": "#/$defs/scalarOperands" } + } + }, + + "isBetweenPredicate": { + "type": "object", + "required": ["between"], + "properties": { + "between": { + "type": "object", + "required" : [ "value", "lower", "upper" ], + "properties": { + "value": { "$ref": "#/$defs/valueExpression" }, + "lower": { "$ref": "#/$defs/scalarExpression" }, + "upper": { "$ref": "#/$defs/scalarExpression" } + } + } + } + }, + + "isLikePredicate": { + "type": "object", + "required": ["like"], + "properties": { + "like": { "$ref": "#/$defs/scalarOperands" }, + "wildcard": { "type": "string", "default": "%" }, + "singleChar": { "type": "string", "default": "." }, + "escapeChar": { "type": "string", "default": "\\" }, + "nocase": { "type": "boolean", "default": true } + } + }, + + "isInListPredicate": { + "type": "object", + "required": ["in"], + "properties": { + "in": { + "type": "object", + "required": ["value","list"], + "properties": { + "value": { "$ref": "#/$defs/valueExpression" }, + "list": { + "type": "array", + "items": { + "$ref": "#/$defs/valueExpression" + } + }, + "nocase": { + "type": "boolean", + "default": true + } + } + } + } + }, + + "valueExpression": { + "oneOf": [ + {"$ref": "#/$defs/scalarExpression"}, + {"$ref": "#/$defs/spatialLiteral"}, + {"$ref": "#/$defs/typedTemporalLiteral"} + ] + }, + + "scalarOperands": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/$defs/scalarExpression" + } + }, + + "scalarExpression": { + "oneOf": [ + {"$ref": "#/$defs/scalarLiteral"}, + {"$ref": "#/$defs/propertyRef"} + ] + }, + + "isNullPredicate": { + "type": "object", + "required": [ "isNull" ], + "properties": { + "isNull": { + "$ref": "#/$defs/scalarExpression" + } + } + }, + + "$comment": "=======================================================", + "$comment": "= SPATIAL PREDICATES =", + "$comment": "=======================================================", + + "spatialPredicate" : { + "oneOf": [ + {"$ref": "#/$defs/intersectsExpression"} + ] + }, + + "intersectsExpression": { + "type": "object", + "required": ["intersects"], + "properties": { + "intersects": { "$ref": "#/$defs/spatialOperands" } + } + }, + + "spatialOperands": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/$defs/geomExpression" + } + }, + + "geomExpression": { + "oneOf": [ + {"$ref": "#/$defs/spatialLiteral"}, + {"$ref": "#/$defs/propertyRef"} + ] + }, + + "$comment": "=======================================================", + "$comment": "= TEMPORAL EXPRESSIONS =", + "$comment": "=======================================================", + + "temporalPredicate" : { + "oneOf": [ + {"$ref": "#/$defs/anyinteractsExpression"} + ] + }, + + "anyinteractsExpression": { + "type": "object", + "required": ["anyinteracts"], + "properties": { + "anyinteracts": { "$ref": "#/$defs/temporalOperands" } + } + }, + + "temporalOperands": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/$defs/temporalExpression" + } + }, + + "temporalExpression": { + "oneOf": [ + {"$ref": "#/$defs/temporalLiteral"}, + {"$ref": "#/$defs/propertyRef"} + ] + }, + + "$comment": "=======================================================", + "$comment": "= PROPERTY REFERENCE =", + "$comment": "=======================================================", + "propertyRef": { + "type": "object", + "required": ["property"], + "properties": { + "propertyName": { "type": "string" } + } + }, + + "$comment": "=======================================================", + "$comment": "= LITERALS =", + "$comment": "=======================================================", + + "scalarLiteral": { + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "boolean"} + ] + }, + + "spatialLiteral": { + "oneOf": [ + { "$ref": "#/$defs/geometryLiteral" }, + { "$ref": "#/$defs/envelopeLiteral" } + ] + }, + + "geometryLiteral": { + "oneOf": [ + { "$ref": "#/$defs/point" }, + { "$ref": "#/$defs/linestring" }, + { "$ref": "#/$defs/polygon" }, + { "$ref": "#/$defs/multipoint" }, + { "$ref": "#/$defs/multilinestring" }, + { "$ref": "#/$defs/multipolygon" } + ] + }, + + "point": { + "title": "GeoJSON Point", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "linestring": { + "title": "GeoJSON LineString", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "polygon": { + "title": "GeoJSON Polygon", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "multipoint": { + "title": "GeoJSON MultiPoint", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["MultiPoint"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "multilinestring": { + "title": "GeoJSON MultiLineString", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["MultiLineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "multipolygon": { + "title": "GeoJSON MultiPolygon", + "type": "object", + "required": ["type","coordinates"], + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + + "envelopeLiteral": { + "type": "object", + "required": [ "bbox" ], + "properties": { + "bbox": { "$ref": "#/$defs/bbox" } + } + }, + + "bbox": { + "type": "array", + "oneOf": [ + { "minItems": 4, "maxItems": 4}, + { "minItems": 6, "maxItems": 6} + ], + "items": { + "type": "number" + } + }, + + "temporalLiteral": { + "oneOf": [ + { "$ref": "#/$defs/timeString" }, + { "$ref": "#/$defs/periodString" } + ] + }, + + "timeString": { + "oneOf": [ + { "type": "string", "format": "date" }, + { "type": "string", "format": "date-time" } + ] + }, + + "periodString": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "oneOf": [ + { "$ref": "#/$defs/timeString" }, + { "type": "string", "enum": [".."] } + ] + } + }, + + "typedTemporalLiteral": { + "oneOf": [ + { "$ref": "#/$defs/typedTimeString" }, + { "$ref": "#/$defs/typedPeriodString" } + ] + }, + + "typedTimeString": { + "type": "object", + "required": ["datetime"], + "properties": { + "datetime": { + "$ref": "#/$defs/timeString" + } + } + }, + + "typedPeriodString": { + "type": "object", + "required": ["datetime"], + "properties": { + "datetime": { + "$ref": "#/$defs/periodString" + } + } + } + } +} + diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index 943c08fd..658fc949 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -5,6 +5,17 @@ info: version: 1.0.0-beta.1 paths: + "/": + get: + responses: + '200': + description: Landing Page + links: + queryables: + operationId: getQueryables + description: |- + A link with rel=queryables for the entire catalog. + /queryables: get: summary: Get the JSON Schema defining the list of variable terms that can be used in CQL expressions. @@ -19,11 +30,40 @@ paths: # todo: may have collections parameter in the future responses: '200': - description: A JSON Schema defining the Queryables + description: A JSON Schema defining the Queryables allowed in CQL expressions + content: + application/schema+json: + schema: + $ref: 'https://json-schema.org/draft/2019-09/schema' + default: + $ref: '../core/commons.yaml#/components/responses/Error' + /collections/{collectionId}: + get: + responses: + '200': + description: Collection description + links: + queryables: + operationId: getQueryables + description: |- + A link with rel=queryables for queryables to only apply to this collection. + /collections/{collectionId}/queryables: + get: + summary: Get the JSON Schema defining the list of variable terms that can be used in CQL expressions. + operationId: getQueryables + description: |- + This endpoint returns a list of variable terms that can be used in CQL expressions. The + precise definition of this can be found in the OGC API - Features - Part 3: Filtering and the + Common Query Language (CQL) specification. + tags: + - Queryables + responses: + '200': + description: A JSON Schema defining the Queryables allowed in CQL expressions filtering only that collection content: application/schema+json: schema: - $ref: '' # TBD + $ref: 'https://json-schema.org/draft/2019-09/schema' default: $ref: '../core/commons.yaml#/components/responses/Error' components: @@ -35,10 +75,12 @@ components: description: |- **Extension:** Filter - A filter for properties in Items. + A CQL filter expression for filtering items. required: true schema: - type: string + oneOf: + - $ref: '#/components/schemas/filter-cql-json' + - $ref: '#/components/schemas/filter-cql-text' filter-lang: name: filter-lang x-stac-api-fragment: filter @@ -46,10 +88,10 @@ components: description: |- **Extension:** Filter - The Filter Language using in the text in the filter parameter. Must be one of 'cql-text' or 'cql-json'. + The CQL filter encoding that the 'filter' value uses. Must be one of 'cql-text' or 'cql-json'. required: false schema: - type: string + $ref: '#/components/schemas/filter-lang' filter-crs: name: filter-crs x-stac-api-fragment: filter @@ -61,7 +103,7 @@ components: is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'. required: false schema: - type: string + $ref: '#/components/schemas/filter-crs' schemas: searchBody: type: object @@ -72,23 +114,29 @@ components: A filter for properties in Items. properties: filter: - $ref: '#/components/schemas/filter' + $ref: '#/components/schemas/filter-cql-json' filter-lang: $ref: '#/components/schemas/filter-lang' filter-crs: $ref: '#/components/schemas/filter-crs' - filter: + filter-cql-text: description: | - TBD - type: object + A CQL filter expression in the 'cql-text' encoding. + type: string + filter-cql-json: + description: | + A CQL filter expression in the 'cql-json' encoding. + $ref: './cql_schema.json' filter-lang: description: | - TBD + The CQL filter encoding that the 'filter' value uses. type: string enum: - 'cql-text' - 'cql-json' filter-crs: description: | - TBD - type: string \ No newline at end of file + The coordinate reference system (CRS) used by spatial literals in the 'filter' value. The only value that STAC APIs must + accept is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'. + type: string + format: uri \ No newline at end of file From eba41352ec9bda279ac266313bc036d2e6a6faae Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 13 May 2021 13:03:44 -0400 Subject: [PATCH 32/61] add text about synthetic queryables --- fragments/filter/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index b43e1a72..af3273cc 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -54,7 +54,7 @@ OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as a ## Limitations of Item Search OAFeat defines a limited set of filtering capabilities. Filtering can only be done over a single collection and -with only a bbox and datetime parameter. +with only a single `bbox` (rectangular spatial filter) parameter and a single datetime (instant or interval) parameter. The STAC Item Search specification extends the functionality of OAFeat in a few key ways: - It allows cross-collection filtering, whereas OAFeat only allows filtering within a single collection. (`collections` parameter, accepting 0 or more collections) @@ -62,7 +62,8 @@ The STAC Item Search specification extends the functionality of OAFeat in a few - It allows filtering based on a single GeoJSON Geometry, rather than only a bbox (`intersects` parameter) However, it does not contain a formalized way to filter based on arbitrary fields in an Item. For example, there is -no way to express the filter "item.properties.eo:cloud_cover is less than 10". +no way to express the filter "item.properties.eo:cloud_cover is less than 10". It also does not have a way to logically combine +multiple spatial or temporal filters. ## Filter expressiveness @@ -70,12 +71,12 @@ This extension expands the capabilities of Item Search and the OAFeat Items reso [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) by providing an expressive query language to construct more complex filter predicates. The operators are similar to those provided by SQL. The Simple CQL conformance class requires the logical operators `AND`, `OR`, and `NOT`; -the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, `IN`; the spatial operator -`INTERSECTS` and the temporal operator `ANYINTERACTS`. +the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, and `IN`; the spatial operator +`INTERSECTS`; and the temporal operator `ANYINTERACTS`. The ANYINTERACTS operator has effectively the same semantics as the existing `datetime` filter. -For example: +CQL enables these types of queries: - Use of Item Property values in predicates (e.g., `item.properties.eo:cloud_cover`), using comparison operators - Items whose `datetime` values are in the month of August of the years 2017-2021, using OR and ANYINTERACTS - Items whose `geometry` values intersect any one of several Polygons, using OR and INTERSECTS @@ -142,6 +143,8 @@ These queryable variables are mapped by the service to filter Items. For example Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group of options for the values of these variables. +Queryables can also be used to advertise synthesized property values. The only requirement in CQL is that the property have a type and evaluate to literal value of that type or NULL. For example, a filter like "Items must have an Asset with an eo:band with the common_name of 'nir'" can be expressed. A Queryable `assets_bands` could be defined to have a type of array of string and have the semantics that it contains all of `common_name` values across all assets and bands for an Item. This could then be filtered with the CQL expression `'nir' in assets_bands`. Implementations would then expand this expression into the appropriate query against its datastore. (TBD if this will actually work or not. This is also related to the upcoming restriction on property/literal comparisons) + ## GET Query Parameters and POST JSON fields This extension adds three GET query parameters or POST JSON fields to an Item Search request: @@ -231,6 +234,10 @@ Queryables endpoint (`/queryables`) returns: "gsd" : { "title" : "Ground Sample Distance", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#/properties/gsd" + }, + "assets_bands" : { + "title" : "Asset eo:bands common names", + "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:bands/common_name" } } } From 84df4239efbb231134dd503e17f295ce847cd53a Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 09:32:07 -0400 Subject: [PATCH 33/61] add service-doc to core links table, add links diagram --- core/README.md | 66 +++++++++++++++++++++++----- core/stac-api.gv | 110 ++++++++++++++++++++++++++++++++++++++++++++++ core/stac-api.png | Bin 0 -> 138282 bytes 3 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 core/stac-api.gv create mode 100644 core/stac-api.png diff --git a/core/README.md b/core/README.md index e6bcfa34..b1ccdd6e 100644 --- a/core/README.md +++ b/core/README.md @@ -1,7 +1,7 @@ # STAC API - Core Specification - [STAC API - Core Specification](#stac-api---core-specification) - - [Potential Link Relations at `/`](#potential-link-relations-at-) + - [Recommended Link Relations at `/`](#recommended-link-relations-at-) - **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)), and [commons.yaml](commons.yaml) is the OpenAPI version of the core [STAC spec](../stac-spec) JSON Schemas. @@ -9,7 +9,9 @@ - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Dependencies**: None -The core of a STAC API is its landing page, which is the starting point to discover STAC data and what the API supports. +The base of a STAC API is its landing page. This resource is the starting point to discover what behaviors the API supports via link relations. +This behavior in a RESTful API is known as Hypermedia as the Engine of Application State (HATEOAS). STAC API relies heavily on hypermedia for API endpoint +navigation. ```json { @@ -18,23 +20,59 @@ The core of a STAC API is its landing page, which is the starting point to disco "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "conformance", + "type": "application/json", + "href": "https://stacserver.org/conformance" + }, + { + "rel": "service-desc", + "type": "application/vnd.oai.openapi+json;version=3.0", + "href": "https://stacserver.org/api" + }, + { + "rel": "service-doc", + "type": "text/html", + "href": "https://stacserver.org/api.html" + }, + { + "rel": "data", + "type": "application/json", + "href": "https://stacserver.org/collections" + }, { "rel": "child", + "type": "application/json", "href": "https://stacserver.org/collections/sentinel-2", }, { "rel": "child", + "type": "application/json", "href": "https://stacserver.org/collections/landsat-8", }, { "rel": "search", - "type": "application/json", + "type": "application/geo+json", "href": "https://stacserver.org/search" } ], "conformsTo" : [ "https://api.stacspec.org/v1.0.0-beta.1/core", - "https://api.stacspec.org/v1.0.0-beta.1/item-search" + "https://api.stacspec.org/v1.0.0-beta.1/item-search", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" ] } ``` @@ -65,15 +103,23 @@ The root endpoint (`/`) is most useful when it presents a complete `Catalog` rep that all `Collections` and `Items` can be navigated to by transitively traversing links from this root. This spec does not require any API endpoints from OAFeat or STAC API to be implemented, so the following links may not exist if the endpoint has not been implemented. -## Potential Link Relations at `/` +## Recommended Link Relations at `/` + +![Diagram of STAC link relations](stac-api.png) + +In each node of the diagram above, there is also a `self` and `root` link that are not depicted to keep the digram more concise. + | **`rel`** | **href to** | **From** | **Description** | |-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| -| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collections and Items | +| `root` | The root URI | STAC Core | Reference to self URI | +| `self` | The root URI | OAFeat | Reference to self URI | +| `conformance` | OGC `/conformance` endpoint | OAFeat / OACommon | Only required for OGC API Compliant implementations | +| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | +| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | | `data` | OAFeat/OACommon `/collections` endpoint | Commons Collection | The full list of Collections provided by the API | +| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collections and Items | | `search` | The STAC search endpoint (often `/search`) | STAC Search | Cross-collection query endpoint to select sub-sets of STAC Items | -| `service-desc` | The OpenAPI description of this service | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | -| `conformance` | OGC `/conformance` endpoint | OAFeat / OACommon | Only required for OGC API Compliant implementations | -It is also valid to have `item` links from the landing page, but most STAC API's are used to serve up a massive amount of features, so they typically -use several layers of `child` links before getting to Items. +It is also valid to have `item` links from the landing page, but most STAC API's are used to serve up a large number of features, so they typically +use several layers of intermediate `child` links before getting to Items. diff --git a/core/stac-api.gv b/core/stac-api.gv new file mode 100644 index 00000000..ce078bd6 --- /dev/null +++ b/core/stac-api.gv @@ -0,0 +1,110 @@ +# All have self link to self (self edge) +# All have root link to root + +digraph g { + label = ; + labelloc = "t"; + fontsize = "24" + + graph [ + rankdir = "LR" + ]; + + node [ + fontsize = "16" + shape = "ellipse" + ]; + + edge [ + ]; + + "/" [ + label = "Landing Page\n/|links[rel=conformance]\l|links[rel=service-doc]\l|links[rel=service-desc]\l|links[rel=data]\l|links[rel=child]\l|links[rel=child]\l|links[rel=search]\l" + shape = "record" + ]; + + "collections" [ + label = " Collections\n/collections|collections[0].links[rel=self]\l|collections[1].links[rel=self]\l" + shape = "record" + ]; + + "collectionY" [ + label = " Collection y\n/collections/y|links[rel=items]\l" + shape = "record" + ]; + + "collectionX" [ + label = " Collection x\n/collections/x|links[rel=items]\l" + shape = "record" + ]; + + "service-desc" [ + label = "OpenAPI Spec\n/api" + shape = "record" + ]; + + "service-doc" [ + label = "Service Doc\n/api.html" + shape = "record" + ]; + + "conformance" [ + label = "Conformance\n/conformance" + shape = "record" + ]; + + "collectionY-items" [ + label = " Collection y Items\n/collections/y/items| features[0].links[rel=self]" + shape = "record" + ]; + + "collectionX-items" [ + label = " Collection x Items\n/collections/x/items| features[0].links[rel=self]" + shape = "record" + ]; + + "collectionY-item1" [ + label = " Item y:1\n/collections/y/items/1|links[rel=parent]\l|links[rel=collection]\l" + shape = "record" + ]; + + "collectionX-item1" [ + label = " Item x:1\n/collections/x/items/1|links[rel=parent]\l|links[rel=collection]\l" + shape = "record" + ]; + + "item-search" [ + label = " Item Search\n/items|features[0].links[rel=self]\l|features[1].links[rel=self]\l" + shape = "record" + ]; + + "/":l3 -> "collections":f0; + "/":l4 -> "collectionX":f0; + "/":l5 -> "collectionY":f0; + "/":l6 -> "item-search":f0; + "/":l7 -> "service-desc"; + "/":l8 -> "service-doc"; + "/":l9 -> "conformance"; + + "item-search":f1 -> "collectionX-item1":f0; + "item-search":f2 -> "collectionY-item1":f0; + + "collections":c2 -> "collectionY":f0; + "collections":c1 -> "collectionX":f0; + + "collectionY":l0 -> "collectionY-items":f0; + "collectionY-items":f1 -> "collectionY-item1":f0; + cxi2 [shape=point,width=0.01,height=0.01]; + cxi2 -> "collectionY":f0; + "collectionY-item1":l0 -> cxi2 [dir=none]; + "collectionY-item1":l1 -> cxi2 [dir=none]; + + "collectionX":l0 -> "collectionX-items":f0; + "collectionX-items":f1 -> "collectionX-item1":f0; + cxi1 [shape=point,width=0.01,height=0.01]; + cxi1 -> "collectionX":f0; + "collectionX-item1":l0 -> cxi1 [dir=none]; + "collectionX-item1":l1 -> cxi1 [dir=none]; + + +} diff --git a/core/stac-api.png b/core/stac-api.png new file mode 100644 index 0000000000000000000000000000000000000000..330f9bfb19e4b938fa3211c1a7bfb41e9b47d63a GIT binary patch literal 138282 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

!?py*#Tw6*1TB`-3pU1+N; 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%0DpvyAGhB; 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ð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�y}c@~?`q^2+EU@zv=Zno{(Df6jl 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 literal 0 HcmV?d00001 From fc7dcf71d2ec59ef2a59197c2665e312c1ae632c Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 09:34:24 -0400 Subject: [PATCH 34/61] remove duplicate core in conformsTo --- core/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/core/README.md b/core/README.md index b1ccdd6e..d69758f0 100644 --- a/core/README.md +++ b/core/README.md @@ -70,7 +70,6 @@ navigation. "https://api.stacspec.org/v1.0.0-beta.1/core", "https://api.stacspec.org/v1.0.0-beta.1/item-search", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", - "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" ] From 81edfa551a05491d2ca98fd719bb8e04e447bf04 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 09:47:30 -0400 Subject: [PATCH 35/61] refine CORS description --- implementation.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/implementation.md b/implementation.md index b2b30a66..5b6e955c 100644 --- a/implementation.md +++ b/implementation.md @@ -4,8 +4,9 @@ This document describes implementation recommendations for a STAC API. ## CORS -It is recommended that public APIs advertise a permissive CORS configuration so UIs running on a different domain -may more easily access them. +Web browsers enforce a mechanism called [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to prevent +malicious access to APIs. It is recommended that public STAC APIs advertise a permissive [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +configuration so browser-based UIs running on a different domain may more easily access them. APIs should acknowledge pre-flight request headers. In general, these header values should be set on responses: @@ -13,10 +14,10 @@ APIs should acknowledge pre-flight request headers. In general, these header val access-control-allow-origin: * access-control-allow-methods: OPTIONS, POST, GET access-control-allow-headers: Content-Type -access-control-allow-credentials: false ``` It is relatively safe to use these headers for all endpoints. However, one may want to restrict the methods to only those that apply to each endpoint. For example, the `/collection/{collectionId}/items` endpoint should only allow OPTIONS and GET, since POST is only used by the Transactions Extension, which presumably would require authentication as it is mutating data. Implementations that support the Transactions Extension or require credentials for some operations will need to -implement different behavior, for example, allowing credentials when requests are coming from a trusted domain or also allowing DELETE, PUT, or PATCH methods. +implement different behavior, for example, allowing credentials when requests are coming from a trusted domain, allowing DELETE, PUT, or PATCH methods, or +permitting the `If-Match` request header. From 1d29ba2ac38aea04811e6ffb36b6278cd7a68f98 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 12:57:58 -0400 Subject: [PATCH 36/61] link and make more consistent uses of Catalog, Item, and Collection (#132) * link and make more consistent uses of Catalog, Item, and Collection * fix remark lint error, fix remark config to not report errors for long table lines * sync remark config with stac-spec * change case back for Unreleased * revert changes to ogcapi features yaml --- .circleci/rc.yaml | 21 ++++---- .remarkignore | 1 + CODE_OF_CONDUCT.md | 4 +- README.md | 18 +++---- core/README.md | 14 ++--- core/openapi.yaml | 2 +- extensions.md | 4 +- fragments/fields/README.md | 6 +-- fragments/itemcollection/README.md | 11 ++-- fragments/query/README.md | 2 +- item-search/README.md | 16 +++--- item-search/examples.md | 2 +- item-search/openapi.yaml | 4 +- ogcapi-features/README.md | 20 ++++---- .../extensions/transaction/README.md | 2 +- overview.md | 51 ++++++++++--------- package.json | 2 +- 17 files changed, 96 insertions(+), 84 deletions(-) create mode 100644 .remarkignore diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index b0dd538c..625a856a 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -4,15 +4,17 @@ plugins: # Apply some recommended defaults for consistency - remark-preset-lint-consistent - remark-preset-lint-recommended - - lint-no-html +# No HTML for security - can't activate yet due to STAC logo in README.md +# - lint-no-html # General formatting - - - remark-lint-emphasis-marker - - '*' +# - - remark-lint-emphasis-marker +# - '*' - remark-lint-hard-break-spaces - remark-lint-blockquote-indentation - remark-lint-no-consecutive-blank-lines - - - remark-lint-maximum-line-length - - 150 +# Detect overly long lines - be liberal for now and don't restrict to 80 yet +# - - remark-lint-maximum-line-length +# - 150 # Code - remark-lint-fenced-code-flag - remark-lint-fenced-code-marker @@ -21,23 +23,24 @@ plugins: - 'fenced' # Headings - remark-lint-heading-increment + - remark-lint-no-duplicate-headings - remark-lint-no-multiple-toplevel-headings - remark-lint-no-heading-punctuation - - remark-lint-maximum-heading-length - 70 - - remark-lint-heading-style - atx - - - remark-lint-no-shortcut-reference-link - - false # Lists - remark-lint-list-item-bullet-indent - remark-lint-ordered-list-marker-style - remark-lint-ordered-list-marker-value - remark-lint-checkbox-character-style - - - remark-lint-unordered-list-marker-style - - '-' +# - - remark-lint-unordered-list-marker-style +# - '-' - - remark-lint-list-item-indent - space # Tables - remark-lint-table-pipes +# - remark-lint-table-pipe-alignment # Wait for https://github.com/remarkjs/remark-lint/issues/226 +# Urls - remark-lint-no-literal-urls \ No newline at end of file diff --git a/.remarkignore b/.remarkignore new file mode 100644 index 00000000..32524460 --- /dev/null +++ b/.remarkignore @@ -0,0 +1 @@ +/CHANGELOG.md \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 984c77e6..f56c9682 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -70,7 +70,7 @@ members of the project's leadership. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at -[homepage]: - For answers to common questions about this code of conduct, see + +[homepage]: diff --git a/README.md b/README.md index efb73a05..b018c04a 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ time. The core STAC specification lives at [gitub.com/radiantearth/stac-spec](ht A STAC API is the dynamic version of a SpatioTemporal Asset Catalog. It returns a STAC [Catalog](stac-spec/catalog-spec/catalog-spec.md), [Collection](stac-spec/collection-spec/collection-spec.md), [Item](stac-spec/item-spec/item-spec.md), or a STAC API [ItemCollection](fragments/itemcollection/README.md), depending on the endpoint. -Catalogs and Collections are JSON, while Items and ItemCollections are GeoJSON-compliant entities with foreign members. -Typically, a Feature is used when returning a single Item, and FeatureCollection when multiple Items (rather than a JSON array of Item entities). +Catalog and Collection objects are JSON, while Item and ItemCollection objects are GeoJSON-compliant entities with foreign members. +Typically, a Feature is used when returning a single Item object, and FeatureCollection when multiple Item objects (rather than a +JSON array of Item entities). The API can be implemented in compliance with the *[OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html)* standard (we'll use OAFeat for shorthand). In this case STAC API can be thought of as a specialized Features API -to search STAC Catalogs, where the features returned are STAC [Items](stac-spec/item-spec/item-spec.md), +to search STAC catalogs, where the features returned are STAC [Item](stac-spec/item-spec/item-spec.md) objects, that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets. The specification for STAC API is provided as files that follow the [OpenAPI](http://openapis.org/) 3.0 specification, @@ -35,10 +36,9 @@ rendered online into HTML at , in additi This specification has evolved over the past couple years, and is used in production in a variety of deployments. It is currently in a 'beta' state, with no major changes anticipated. For 1.0-beta we remain fully aligned with [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) Version 1.0, and we are working to stay aligned -as the additional OGC API components mature. This may result in minor changes as things evolve. The STAC API specification - -follows [Semantic Versioning](https://semver.org/), so once 1.0.0 is reached any breaking change will require the spec to -go to 2.0.0. +as the additional OGC API components mature. This may result in minor changes as things evolve. The STAC API +specification follows [Semantic Versioning](https://semver.org/), so once 1.0.0 is reached any breaking change +will require the spec to go to 2.0.0. ## Communication @@ -53,11 +53,11 @@ The **[Overview](overview.md)** document describes all the various parts of the **STAC API - Core Specification:** The *[core](core/)* folder describes the core STAC API specification that enables browsing catalogs and -retrieving the API capabilities. This includes the OpenAPI schemas for STAC items, catalogs and collections. +retrieving the API capabilities. This includes the OpenAPI schemas for STAC Item, Catalog and Collection objects. **STAC API - Item Search Specification:** The *[item-search](item-search)* folder contains the Item Search specification, which enables -cross-collection search of STAC Items at a `search` endpoint, as well as a number of extensions. +cross-collection search of STAC Item objects at a `search` endpoint, as well as a number of extensions. **STAC API - Features:** The *[ogcapi-features](ogcapi-features)* folder describes how a STAC API can fully implement [OGC API - diff --git a/core/README.md b/core/README.md index e6bcfa34..9e4a7480 100644 --- a/core/README.md +++ b/core/README.md @@ -42,7 +42,7 @@ The core of a STAC API is its landing page, which is the starting point to disco There are a few requirements for the returned document: - The returned JSON must be a valid [STAC Catalog](../stac-spec/catalog-spec/catalog-spec.md), and it can provide any number of 'child' links -to navigate down to additional Catalogs, Collections & Items. +to navigate down to additional Catalog, [Collection](../stac-spec/collection-spec/README.md), and [Item](../stac-spec/item-spec/README.md) objects. - The `links` section is a required part of STAC Catalog, and serves as the list of API endpoints. These can live at any location, the client must inspect the the `rel` (relationship) to understand what capabilities are offered at each location. - The `conformsTo` section must provide the capabilities of this service. The relevant conformance URI's are listed in each part of the @@ -53,7 +53,7 @@ classes](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_o the landing page. This is a slight break from how OGC API does things, as STAC feels it is important for clients to be able to understand conformance in a single request. To be conformant to OGC API's the `/conformance` endpoint must be implemented as well. -This particular catalog provides the ability to browse down to STAC Collections through its `child` links, and also provides the search +This particular catalog provides the ability to browse down to child STAC Collection objects through its `child` links, and also provides the search endpoint to be able to search across items in its collections. Note though that none of those links are required, other servers may provide different conformance classes and a different set of links. @@ -62,18 +62,18 @@ in it. Any API implementing that is considered a valid STAC API, and clients can capabilities are on offer and how to reach them. The root endpoint (`/`) is most useful when it presents a complete `Catalog` representation of all the data contained in the API, such -that all `Collections` and `Items` can be navigated to by transitively traversing links from this root. This spec does not require any +that all `Collection` and `Item` objects can be navigated to by transitively traversing links from this root. This spec does not require any API endpoints from OAFeat or STAC API to be implemented, so the following links may not exist if the endpoint has not been implemented. ## Potential Link Relations at `/` | **`rel`** | **href to** | **From** | **Description** | |-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| -| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collections and Items | -| `data` | OAFeat/OACommon `/collections` endpoint | Commons Collection | The full list of Collections provided by the API | -| `search` | The STAC search endpoint (often `/search`) | STAC Search | Cross-collection query endpoint to select sub-sets of STAC Items | +| `child` | The child STAC Catalog & Collection objects | STAC Core | Provides curated paths to get to STAC Collection and Item objects | +| `data` | OAFeat/OACommon `/collections` endpoint | Commons Collection | The full list of Collection objects provided by the API | +| `search` | The STAC search endpoint (often `/search`) | STAC Search | Cross-collection query endpoint to select sub-sets of STAC `Item` objects | | `service-desc` | The OpenAPI description of this service | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | | `conformance` | OGC `/conformance` endpoint | OAFeat / OACommon | Only required for OGC API Compliant implementations | It is also valid to have `item` links from the landing page, but most STAC API's are used to serve up a massive amount of features, so they typically -use several layers of `child` links before getting to Items. +use several layers of `child` links before getting to `Item` objects. diff --git a/core/openapi.yaml b/core/openapi.yaml index 688337b8..096f49f8 100644 --- a/core/openapi.yaml +++ b/core/openapi.yaml @@ -50,7 +50,7 @@ components: description: |- The landing page provides links to the API definition (link relations `service-desc` and `service-doc`) - and the Feature Collections (path `/collections`, link relation + and the Feature Collection (path `/collections`, link relation `data`). content: application/json: diff --git a/extensions.md b/extensions.md index adea727f..dcc6a7a2 100644 --- a/extensions.md +++ b/extensions.md @@ -56,8 +56,8 @@ This is the list of all extensions that are contained in the stac-api-spec repos | [Query](item-search/README.md#query) | [Item Search](item-search/) request | Adds parameter to search Item and Collection properties. | *Pilot* | | [Context](item-search/README.md#context) | [Item Search](item-search/) response ([ItemCollection](fragments/itemcollection/README.md)) | Adds search related metadata (context) to ItemCollection. | *Proposal* | | [Sort](item-search/README.md#sort) | [Item Search](item-search/) request | Adds Parameter to control sorting of returns results. | *Pilot* | -| [Transaction](ogcapi-features/extensions/transaction/README.md) | [STAC - Features API](ogcapi-features) POST on `/items` endpoint, DELETE/PUT on `/items/{itemId}` endpoint | Adds PUT and DELETE endpoints for the creation, editing, and deleting of items and Collections. | *Pilot* | -| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | [STAC - Features API](ogcapi-features) on `/items` endpoint | Adds GET versions resource to collections and items endpoints and provides semantics for a versioning scheme for collections and items. | *Proposal* | +| [Transaction](ogcapi-features/extensions/transaction/README.md) | [STAC - Features API](ogcapi-features) POST on `/items` endpoint, DELETE/PUT on `/items/{itemId}` endpoint | Adds PUT and DELETE endpoints for the creation, editing, and deleting of Item objects. | *Pilot* | +| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | [STAC - Features API](ogcapi-features) on `/items` endpoint | Adds GET versions resource to Collection and Item endpoints and provides semantics for a versioning scheme for Collection and Item objects. | *Proposal* | ### Conformance classes of extensions diff --git a/fragments/fields/README.md b/fragments/fields/README.md index 98023037..a3d54ccc 100644 --- a/fragments/fields/README.md +++ b/fragments/fields/README.md @@ -5,8 +5,8 @@ - **Dependents:** - [Item Search](../../item-search) -STAC API by default returns everything within an item. But Items can have hundreds of fields, or incredibly large -geometries, and even smaller Items can get big when millions are requested but not all information is used. This +STAC API by default returns everything within an item. But Item objects can have hundreds of fields, or incredibly large +geometries, and even smaller Item objects can get big when millions are requested but not all information is used. This fragment provides a mechanism for clients to request that servers to explicitly include or exclude certain fields. When used in a POST request with `Content-Type: application/json`, this adds an attribute `fields` with @@ -24,7 +24,7 @@ contract about what the response will be. Implementations are still considered c are in the response or ones specified as part of `exclude` are. For example, implementations may choose to always include simple string fields like `id` and `type` regardless of the `exclude` specification. However, it is recommended that implementations honor excludes for attributes with more complex and arbitrarily large values -(e.g., `geometry`, `assets`). For example, some Items may have a geometry with a simple 5 point polygon, but these +(e.g., `geometry`, `assets`). For example, some Item objects may have a geometry with a simple 5 point polygon, but these polygons can be very large when reprojected to EPSG:4326, as in the case of a highly-decimated sinusoidal polygons. Implementations are also not required to implement semantics for nested values whereby one can include an object, but exclude attributes of that object, e.g., include `properties` but exclude `properties.datetime`. diff --git a/fragments/itemcollection/README.md b/fragments/itemcollection/README.md index e0d6c115..706c6c48 100644 --- a/fragments/itemcollection/README.md +++ b/fragments/itemcollection/README.md @@ -7,7 +7,7 @@ that is augmented with [foreign members](https://tools.ietf.org/html/rfc7946#sec Similarly to the relationship between a GeoJSON Feature and a STAC Item, a STAC ItemCollection should be a valid GeoJSON FeatureCollection to allow interoperability with existing tools that support GeoJSON. -Items are represented in JSON format and are very flexible. Any JSON object that contains all the +Item objects are represented in JSON format and are very flexible. Any JSON object that contains all the required fields is a valid STAC ItemCollection. - Examples: @@ -24,7 +24,7 @@ This object describes a STAC ItemCollection. The fields `type` and `features` ar | stac_version | string | **REQUIRED.** The STAC version the ItemCollection implements. | | stac_extensions | \[string\] | A list of extensions the ItemCollection implements. | | type | string | **REQUIRED.** Always "FeatureCollection" to provide compatibility with GeoJSON. | -| features | [STAC Item](../../stac-spec/item-spec/item-spec.md) | **REQUIRED** A possibly-empty array of Items. | +| features | [STAC Item](../../stac-spec/item-spec/item-spec.md) | **REQUIRED** A possibly-empty array of Item objects. | | links | [Link Object](../../stac-spec/item-spec/item-spec.md#link-object) | An array of Links related to this ItemCollection. | **stac_version**: In general, STAC versions can be mixed, but please keep the [recommended best @@ -35,11 +35,12 @@ can be validated against. For official [STAC extensions](https://stac-extensions can be used. This means you can specify the folder name of the extension, for example `single-file-stac` for the Single File STAC extension. This does *not* apply for [API extensions](../../extensions.md). If the versions of the extension and the item diverge, you can specify the URL of the JSON schema file. This list must only contain extensions that extend the ItemCollection itself, see the -the 'Scope' column in the list of extensions. It must not contain extensions that extend the Items, these must be specified in the Items directly. +the 'Scope' column in the list of extensions. It must not contain extensions that extend the Item objects, these must be specified in the +Item object directly. ## Extensions - The [Context Extension](../../item-search/README.md#context) adds additional fields to STAC ItemCollection relevant to their use as search results. -- The [Single File STAC Extension](https://github.com/stac-extensions/single-file-stac/blob/main/README.md) provides a set of Collections and Items - as a single file catalog. +- The [Single File STAC Extension](https://github.com/stac-extensions/single-file-stac/blob/main/README.md) provides a set of Collection + and Item objects as a single file catalog. diff --git a/fragments/query/README.md b/fragments/query/README.md index 0a034015..2c3babc0 100644 --- a/fragments/query/README.md +++ b/fragments/query/README.md @@ -6,7 +6,7 @@ - **Dependents:** - [Item Search](../../item-search) -The `query` parameter adds additional filters for searching on the properties of Items. +The `query` parameter adds additional filters for searching on the properties of Item objects. The syntax for the `query` filter is: diff --git a/item-search/README.md b/item-search/README.md index c8fc3005..3f0b3463 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -21,9 +21,11 @@ - **Dependencies**: [STAC API - Core](../core) - **Examples**: [examples.md](examples.md) -A search endpoint, linked to from the STAC landing page, provides the ability to query STAC `Items` across collections. -It retrieves a group of Items that match the provided parameters, wrapped in an [ItemCollection](../fragments/itemcollection/README.md) (which is a -valid [GeoJSON FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) that contains STAC Items). Several core +A search endpoint, linked to from the STAC landing page, provides the ability to query STAC [Item](../stac-spec/item-spec/README.md) +objects across collections. +It retrieves a group of Item objects that match the provided parameters, wrapped in an +[ItemCollection](../fragments/itemcollection/README.md) (which is a +valid [GeoJSON FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) that contains STAC Item objects). Several core query parameters are defined by [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html), with a few additions specified in this document. @@ -87,7 +89,7 @@ The core parameters for STAC search are defined by OAFeat, and STAC adds a few p | datetime | string | OAFeat | Single date+time, or a range ('/' seperator), formatted to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). Use double dots `..` for open date ranges. | | intersects | GeoJSON Geometry | STAC | Searches items by performing intersection between their geometry and provided GeoJSON geometry. All GeoJSON geometry types must be supported. | | ids | \[string] | STAC | Array of Item ids to return. | -| collections | \[string] | STAC | Array of one or more Collection IDs to include in the search for items. Only Items in one of the provided Collections will be searched | +| collections | \[string] | STAC | Array of one or more Collection IDs that each matching Item must be in. | Only one of either **intersects** or **bbox** should be specified. If both are specified, a 400 Bad Request response should be returned. See [examples](examples.md) to see sample requests. @@ -95,9 +97,9 @@ should be returned. See [examples](examples.md) to see sample requests. ## Response The response to a request (GET or POST) to the search endpoint should always be an -`[ItemCollection](../core/itemcollection-spec.md)` - a valid [GeoJSON +`[ItemCollection](../core/itemcollection-spec.md)` object - a valid [GeoJSON FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) that consists entirely of STAC -[Items](../stac-spec/item-spec/item-spec.md). +[Item](../stac-spec/item-spec/item-spec.md) objects. ### Paging @@ -211,7 +213,7 @@ through the use of a `fields` parameter. The full description of how this extens The STAC search endpoint, `/search`, by default only accepts a limited set of parameters to limit the results by properties. The Query extension adds a new parameter, `query`, that can take a number of comparison operators to -match predicates between the fields requested and the values of Items. It can be used with both GET and POST, though +match predicates between the fields requested and the values of Item objects. It can be used with both GET and POST, though GET includes the exact same JSON. The full details on the JSON structure are specified in the [query fragment](../fragments/query/). diff --git a/item-search/examples.md b/item-search/examples.md index 0e2913ff..96d40cde 100644 --- a/item-search/examples.md +++ b/item-search/examples.md @@ -2,7 +2,7 @@ ### Single Collection example -This example shows how you can request Items from a single collection from the `search` endpoint, without having to using +This example shows how you can request Item objects from a single collection from the `search` endpoint, without having to using OGC API - Features. It requests 100 results in `mycollection` that is in New Zealand at anytime on January 1st, 2019: ```json diff --git a/item-search/openapi.yaml b/item-search/openapi.yaml index e29e64d2..e6bfe40b 100644 --- a/item-search/openapi.yaml +++ b/item-search/openapi.yaml @@ -108,7 +108,7 @@ components: in: query description: | Array of Collection IDs to include in the search for items. - Only Items in one of the provided Collections will be searched + Only Item objects in one of the provided collections will be searched required: false schema: $ref: '#/components/schemas/collectionsArray' @@ -231,7 +231,7 @@ components: type: array description: |- Array of Collection IDs to include in the search for items. - Only Items in one of the provided Collections will be searched. + Only Item objects in one of the provided collections will be searched. items: type: string ids: diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index cd75bbde..a20b233d 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -7,7 +7,7 @@ *based on [**OGC API - Features - Part 1: Core**](https://www.ogc.org/standards/ogcapi-features)* - **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features)) - uses all the OGC API - Features openapi fragments to describe returning STAC Items. + uses all the OGC API - Features openapi fragments to describe returning STAC Item objects. - **Conformance URIs:** - - - [Requirements Class Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#rc_core)) @@ -18,7 +18,7 @@ - [OGC API - Features](https://www.ogc.org/standards/ogcapi-features) Adding OGC API - Features (OAFeat) to a STAC API means fully implementing all their requirements, and then returning STAC -[Items](../stac-spec/item-spec/README.md) from their `/items` endpoints. In OAFeat OpenAPI 3.0 and GeoJSON are optional +[Item](../stac-spec/item-spec/README.md) objects from their `/items` endpoints. In OAFeat OpenAPI 3.0 and GeoJSON are optional conformance classes, enabling flexibility, but for STAC they are required, since STAC uses OpenAPI 3.0 and GeoJSON at its core. So the full conformance class list is in the following table. @@ -36,9 +36,9 @@ The core OGC API - Features endpoints are shown below, with details provided in | ----------------------------------------------- | ---------------- | ----------- | | `/` | [Catalog](../stac-spec/catalog-spec/README.md) | Landing page, links to API capabilities | | `/conformance` | JSON | Info about standards to which the API conforms | -| `/collections` | JSON | Object containing an array of Collections within the Catalog and Links | +| `/collections` | JSON | Object containing an array of Collection objects in the Catalog, and Link relations | | `/collections/{collectionId}` | [Collection](../stac-spec/collection-spec/README.md) | Returns single Collection JSON | -| `/collections/{collectionId}/items` | [ItemCollection](../fragments/itemcollection/README.md) | GeoJSON FeatureCollection-conformant entity of Items in collection | +| `/collections/{collectionId}/items` | [ItemCollection](../fragments/itemcollection/README.md) | GeoJSON FeatureCollection-conformant entity of Item objects in collection | | `/collections/{collectionId}/items/{featureId}` | [Item](../stac-spec/item-spec/README.md) | Returns single Item (GeoJSON Feature) | | `/api` | OpenAPI 3.0 JSON | Returns an OpenAPI description of the service from the `service-desc` link `rel` - not required to be `/api`, but the document is required | @@ -48,16 +48,16 @@ plus any number of properties. The core [STAC Item spec](../stac-spec/item-spec/ enhances the core `Feature` with additional requirements and options to enable cataloging of spatiotemporal 'assets' like satellite imagery. -OAFeat also defines the concept of a Collection, which contains Features. In OAFeat Collections are the sets of data that can +OAFeat also defines the concept of a Collection, which contains Features. In OAFeat, a Collection is a set of data that can be queried ([7.11](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_collections_)), and each describes basic information about the geospatial dataset, like its name and description, as well as the spatial and temporal extents of all -the data contained. [STAC collections](../stac-spec/collection-spec/README.md) contain this same -information, along with other STAC specific fields to provide additional metadata for searching spatiotemporal assets, and -thus are compliant with both OAFeat Collections and STAC Collections and are returned from the `/collections/{collection_id}` +the data contained. A [STAC Collection](../stac-spec/collection-spec/README.md) contains this same +information, along with other STAC-specific fields to provide additional metadata for searching spatiotemporal assets, and +thus are compliant with both OAFeat Collection and STAC Collection, and are returned from the `/collections/{collection_id}` endpoint. In OAFeat, Features are the individual records within a Collection and are usually provided in GeoJSON format. -[STAC Items](../stac-spec/item-spec/README.md) are compliant with the OAFeat Features +[STAC Item](../stac-spec/item-spec/README.md) objects are compliant with the OAFeat Features [GeoJSON requirements class](http://docs.ogc.org/is/17-069r3/17-069r3.html#_requirements_class_geojson), and are returned from the `/collections/{collection_id}/items/{item_id}` endpoint. The return of other encodings ([html](http://docs.ogc.org/is/17-069r3/17-069r3.html#rc_html), [gml](http://docs.ogc.org/is/17-069r3/17-069r3.html#rc_gmlsf0)) @@ -71,7 +71,7 @@ provide support for the full superset of STAC API query parameters to the `/coll where the collection ID in the path is equivalent to providing that single value in the `collections` query parameter in STAC API. -Implementing OAFeat enables a wider range of clients to access the API's STAC Items, as it is a more widely implemented +Implementing OAFeat enables a wider range of clients to access the API's STAC Item objects, as it is a more widely implemented protocol than STAC. ## Examples diff --git a/ogcapi-features/extensions/transaction/README.md b/ogcapi-features/extensions/transaction/README.md index d3c95003..537f5fb7 100644 --- a/ogcapi-features/extensions/transaction/README.md +++ b/ogcapi-features/extensions/transaction/README.md @@ -15,7 +15,7 @@ The transaction API extension supports the creation, editing, and deleting of it STAC Transactions are based on the [OGC API - Features](https://ogcapi.ogc.org/features/) transactions, as specified in [Part 4: Simple Transactions](http://docs.opengeospatial.org/DRAFTS/20-002.html). The core OGC standard lays out the end points for transactions, without specifying any content types. For STAC we -use STAC Items in our transactions, and those transaction must be done at the OGC API - Features endpoints, +use STAC Item objects in our transactions, and those transaction must be done at the OGC API - Features endpoints, under `/collections/{collectionID}/items`. The OpenAPI document (specified as an OpenAPI fragment that gets build in the full STAC OpenAPI document) simply gives the STAC examples of using the Simple Transactions API mechanism. diff --git a/overview.md b/overview.md index 631d707d..59a0dd20 100644 --- a/overview.md +++ b/overview.md @@ -1,9 +1,11 @@ # About -The STAC API defines a RESTful JSON-based server to browse and query [SpatioTemporal Asset Catalogs](stac-spec/) -(STAC). While the core STAC specification provides a structure and language to describe assets, users usually want to access +The STAC API defines a RESTful JSON-based web API to browse and query [SpatioTemporal Asset Catalog](stac-spec/) +(STAC) objects. While the core STAC specification provides a structure and language to describe assets, users +usually want to access a subset of the entire catalog, such as for a certain date range, in a particular area of interest, or matching properties -they care about. STAC API extensions specifies those query parameters, and compliant servers return collections of STAC Items that +they care about. STAC API extensions specify those query parameters, and compliant servers return STAC [Item](stac-spec/item-spec/README.md) +objects that match the user's preferences. A lot of additional functionality can added through the [OGC API](https://ogcapi.ogc.org/) family of standards, particularly [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) (OAFeat, for our shorthand). Notes on implementation recommendations may be found [here](implementation.md). @@ -15,8 +17,8 @@ of what parts of the fuller STAC API specification it conforms to. The `links` s off point for the more powerful capabilities - it contains a list of URL's, each described by particular link 'relationships' (`rel`) to indicate their functionality. Note that the [STAC Core specification](stac-spec) provides most all the content of API responses - the STAC API is primarily concerned with the return of STAC -[Items](stac-spec/item-spec/README.md) and [Collections](stac-spec/collection-spec/README.md) through API functionality. -See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.1/core) for more details. +[Item](stac-spec/item-spec/README.md) and [Collection](stac-spec/collection-spec/README.md) objects via a +RESTful web API. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.1/core) for more details. There are then two major sets of functionality that build on the core, which are designed to be complementary, letting implementations choose which parts they want to utilize. Most every STAC API implements at least one, and many follow @@ -28,20 +30,20 @@ The [item search](item-search) functionality is one of the most common, provided located at a `/search` endpoint. It re-uses all of the OAFeat [query parameters](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_items_) specified in their 'core', and adds a couple more. It does not require a full implementation of OAFeat, it is instead a simplified construct that can run a -search across any set of indexed STAC [`Items`](stac-spec/item-spec/README.md). See the [rendered OpenAPI +search across any set of indexed STAC [`Item`](stac-spec/item-spec/README.md) objects. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.1/item-spec) for more details. ### OGC API - Features -The other major functionality for a STAC API is to [serve STAC Collections and Items](ogcapi-features) through -[OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html). This enables any OAFeat client -to access STAC Items in the same way they do any other data. Every STAC Collection becomes available at the -`collections/` endpoint, with each `collections/{collectionId}/items` endpoint allowing search of the items -in that single collection. For STAC this means implementing [OGC API - Features +The other major functionality for a STAC API is to [serve STAC Collection and Item](ogcapi-features) objects through +the [OGC API - Features](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html) endpoints. This enables any OAFeat client +to access STAC Item objects in the same way they do any other data. Every STAC Collection becomes available at the +`/collections` endpoint, with each `/collections/{collectionId}/items` endpoint allowing search of the items +in that single collection. For STAC, this means implementing [OGC API - Features Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_requirements_class_core), as well as following their [GeoJSON](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_requirements_class_geojson) and [OpenAPI](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_requirements_class_openapi_3_0) options, since STAC -is always in GeoJSON and OpenAPI is used to specify STAC API. Full compliance involves splitting STAC `Items` into +is always in GeoJSON and OpenAPI is used to specify STAC API. Full compliance involves splitting STAC `Item` objects into individual `/collections/{collectionId}/items` endpoints that expose querying single collections, as OAFeat does not currently allow cross-collection search. And it adds a few other requirements, which are highlighted in the [features description](ogcapi-features/), in order to help STAC implementors understand OAFeat without having to @@ -89,34 +91,37 @@ column is more of an example in some cases. OGC API makes some endpoint location | Endpoint | Specified in | Link Relationship | Returns | Description | |-----------------------------------------------------|----------------------------|-------------------|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| | `/` | [Core](core) | root | [Catalog](stac-spec/catalog-spec/catalog-spec.md) | Extends `/` from OAFeat to return a full STAC catalog. | -| `/search` | [Item Search](item-search) | search | [ItemCollection](fragments/itemcollection/README.md) | Retrieves a group of Items matching the provided search predicates, probably containing search metadata from the `search` extension | -| **`/collections`** | [OAFeat](ogcapi-features) | data | JSON | Object with a list of Collections contained in the catalog and links | +| `/search` | [Item Search](item-search) | search | [ItemCollection](fragments/itemcollection/README.md) | Retrieves a group of Item objects matching the provided search predicates, probably containing search metadata from the `search` extension | +| **`/collections`** | [OAFeat](ogcapi-features) | data | JSON | Object with a list of Collection objects contained in the catalog and links | | **`/conformance`** | [OAFeat](ogcapi-features) | conformance | JSON | Info about standards to which the API conforms | | `/api` | [OAFeat](ogcapi-features) | service-desc | OpenAPI 3.0 JSON | The OpenAPI definition of the endpoints in this service | | **`/collections/{collectionId}`** | [OAFeat](ogcapi-features) | collection | Collection | Returns single Collection JSON | -| **`/collections/{collectionId}/items`** | [OAFeat](ogcapi-features) | items | ItemCollection | GeoJSON FeatureCollection-conformant entity of Items in collection | +| **`/collections/{collectionId}/items`** | [OAFeat](ogcapi-features) | items | ItemCollection | GeoJSON FeatureCollection-conformant entity of Item objects in collection | | **`/collections/{collectionId}/items/{featureId}`** | [OAFeat](ogcapi-features) | Item | Returns single Item (GeoJSON Feature) | | ## Conformance Classes STAC API is evolving to utilize OAFeat's -'[Conformance](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes)' JSON structure. For +'[Conformance](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes)' +JSON structure. For STAC API 1.0.0-beta.1 we declare new STAC Conformance classes, with the core ones detailed in the table below. [STAC -Features](ogcapi-features) requires the core OAFeat conformance classes, and declares that those endpoints return STAC Collections -and Features. The core STAC conformance classes communicate the conformance JSON only in the root (`/`) document, while OGC API -requires they also live at the `/conformance` endpoint. STAC's conformance structure is detailed in the [core](core/). Note all +Features](ogcapi-features) requires the core OAFeat conformance classes, and declares that those endpoints return +STAC Collection and Feature objects. +The core STAC conformance classes communicate the conformance JSON only in the root (`/`) document, while OGC API +requires they also live at the `/conformance` endpoint. STAC's conformance structure is detailed in the +[core](core/). Note all conformance URI's serve up a rendered HTML version of the corresponding OpenAPI document at the given location. **NOTE:** *By 1.0.0 we aim to have requirements classes specified in detail, as testable assertions, -like OGC does, but for now the core reference is just this spec document and the OpenAPI yaml. We also desire to have the -URI's for conformance to actually resolve to machine-readable information clients can use.* +like OGC does, but for now the core reference is just this spec document and the OpenAPI yaml. We also desire to +have the URI's for conformance to actually resolve to machine-readable information clients can use.* ### Conformance Class Table | **Name** | **Specified in** | **Conformance URI** | **Description** | |---------------------------|----------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | STAC Core | [Core](core) | | Specifies the STAC Landing page `/`, communicating conformance and available endpoints. | -| Item Search | [Item Search](item-search) | | Enables search of all STAC Items on the server, with the STAC `[/search](#stac-api-endpoints)` endpoint. | -| STAC Features | [STAC API - Features](ogcapi-features) | | Specifies the use of OGC API - Features to serve STAC Items and Collections | +| Item Search | [Item Search](item-search) | | Enables search of all STAC Item objects on the server, with the STAC `[/search](#stac-api-endpoints)` endpoint. | +| STAC Features | [STAC API - Features](ogcapi-features) | | Specifies the use of OGC API - Features to serve STAC Item and Collection objects | Additional conformance classes are specified in the [STAC Extensions](extensions.md#Conformance-classes-of-extensions). diff --git a/package.json b/package.json index 5c484cda..426441e5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "serve": "redoc-cli serve core/openapi.yaml --watch --options.expandResponses \"200,201,202,203,204\" --options.pathInMiddlePanel true", "check": "npm run check-markdown && npm run check-openapi", - "check-markdown": "remark . -f -r .circleci/rc.yaml --no-ignore --ignore-pattern stac-spec/", + "check-markdown": "remark . -f -r .circleci/rc.yaml --ignore-pattern stac-spec/", "check-openapi": "npm run check-openapi-core && npm run check-openapi-commons && npm run check-openapi-ogcapi-features && npm run check-openapi-item-search && npm run check-openapi-fragments", "check-openapi-core": "spectral lint core/openapi.yaml --ruleset .circleci/.spectral.yml", "check-openapi-commons": "spectral lint core/commons.yaml --ruleset .circleci/.spectral-fragments.yml", From e8490d9e4be060c8b747f14ac9541908155e65f8 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 14:45:54 -0400 Subject: [PATCH 37/61] add better example, fix linting error in markdown and schemas --- fragments/filter/README.md | 194 ++++++-- .../filter/{cql_schema.json => cql.json} | 23 - fragments/filter/cql.yml | 434 ++++++++++++++++++ fragments/filter/openapi.yaml | 3 +- implementation.md | 2 +- item-search/README.md | 29 +- item-search/openapi.yaml | 2 +- 7 files changed, 606 insertions(+), 81 deletions(-) rename fragments/filter/{cql_schema.json => cql.json} (91%) create mode 100644 fragments/filter/cql.yml diff --git a/fragments/filter/README.md b/fragments/filter/README.md index af3273cc..b7c14f46 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -16,23 +16,27 @@ - [Interaction with Endpoints](#interaction-with-endpoints) - [Examples](#examples) - [Example 1](#example-1) - - [GET with cql-text](#get-with-cql-text) - - [POST with cql-json](#post-with-cql-json) + - [Example 1: GET with cql-text](#example-1-get-with-cql-text) + - [Example 1: POST with cql-json](#example-1-post-with-cql-json) - [Example 2](#example-2) - - [GET with cql-text](#get-with-cql-text-1) - - [POST with cql-json](#post-with-cql-json-1) - - [Additional Examples](#additional-examples) - - [AND cql-text (GET)](#and-cql-text-get) - - [AND cql-json (POST)](#and-cql-json-post) - - [OR cql-text (GET)](#or-cql-text-get) - - [OR cql-json (POST)](#or-cql-json-post) - - [Temporal](#temporal) - - [ANYINTERACTS cql-text (GET)](#anyinteracts-cql-text-get) - - [ANYINTERACTS cql-json (POST)](#anyinteracts-cql-json-post) - - [Spatial](#spatial) - - [INTERSECTS cql-text (GET)](#intersects-cql-text-get) - - [INTERSECTS cql-json (POST)](#intersects-cql-json-post) - - [Implementation](#implementation) + - [Example 2: GET with cql-text](#example-2-get-with-cql-text) + - [Example 2: POST with cql-json](#example-2-post-with-cql-json) + - [Example 3](#example-3) + - [Example 3: GET with cql-text](#example-3-get-with-cql-text) + - [Example 3: POST with cql-json](#example-3-post-with-cql-json) + - [Example 4](#example-4) + - [Example 4: AND cql-text (GET)](#example-4-and-cql-text-get) + - [Example 4: AND cql-json (POST)](#example-4-and-cql-json-post) + - [Example 5](#example-5) + - [Example 5: OR cql-text (GET)](#example-5-or-cql-text-get) + - [Example 5: OR cql-json (POST)](#example-5-or-cql-json-post) + - [Example 6: Temporal](#example-6-temporal) + - [Example 6: ANYINTERACTS cql-text (GET)](#example-6-anyinteracts-cql-text-get) + - [Example 6: ANYINTERACTS cql-json (POST)](#example-6-anyinteracts-cql-json-post) + - [Example 6: Spatial](#example-6-spatial) + - [Example 6: INTERSECTS cql-text (GET)](#example-6-intersects-cql-text-get) + - [Example 6: INTERSECTS cql-json (POST)](#example-6-intersects-cql-json-post) + - [Implementations](#implementations) ## Overview @@ -123,7 +127,8 @@ the implementation of the operators IN, BETWEEN, LIKE, and IS NULL predicates. - [cql.bnf](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.bnf) - [cql.json](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.json) - [cql.yml](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.yml) -- A JSON Schema for only the parts of the CQL JSON encoding required by this extension is [here](cql_schema.json) +- A JSON Schema for only the parts of the CQL JSON encoding required by this extension is [here](cql.json) +- A OpenAPI specification for only the parts of the CQL JSON encoding required by this extension is [here](cql.yml) - xtraplatform-spatial has a CQL [ANTLR 4 grammer](https://github.com/interactive-instruments/xtraplatform-spatial/tree/master/xtraplatform-cql/src/main/antlr/de/ii/xtraplatform/cql/infra) ## Queryables @@ -212,31 +217,31 @@ Queryables endpoint (`/queryables`) returns: "description" : "Queryable names for the example STAC API Item Search filter.", "properties" : { "id" : { - "title" : "ID", + "description" : "ID", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id" }, "collection" : { - "title" : "Collection", + "description" : "Collection", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection" }, "geometry" : { - "title" : "Geometry", + "description" : "Geometry", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry" }, "datetime" : { - "title" : "Datetime", + "description" : "Datetime", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime" }, "eo:cloud_cover" : { - "title" : "Cloud Cover", + "description" : "Cloud Cover", "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" }, "gsd" : { - "title" : "Ground Sample Distance", + "description" : "Ground Sample Distance", "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#/properties/gsd" }, "assets_bands" : { - "title" : "Asset eo:bands common names", + "description" : "Asset eo:bands common names", "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:bands/common_name" } } @@ -261,19 +266,21 @@ Note: the GET examples with query parameters are unescaped to make them easier t ### Example 1 -#### GET with cql-text +This example uses the queryables definition in (Interaction with Endpoints)(#interaction-with-endpoints). + +#### Example 1: GET with cql-text Note that `filter-lang` defaults to `cql-text` in this case, so this is only shown for completeness. -``` +```http GET /search?filter-lang=cql-text&filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' ``` -#### POST with cql-json +#### Example 1: POST with cql-json Note that `filter-lang` defaults to `cql-json` and `filter-crs` defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` in this case. -``` +```http POST /search { "filter-lang"="cql-json", @@ -295,9 +302,11 @@ POST /search ### Example 2 -#### GET with cql-text +This example uses the queryables definition in (Interaction with Endpoints)(#interaction-with-endpoints). -``` +#### Example 2: GET with cql-text + +```http GET /search?filter=collection = 'landsat8_l1tp' AND gsd <= 30 AND eo:cloud_cover <= 10 @@ -305,9 +314,9 @@ GET /search?filter=collection = 'landsat8_l1tp' AND INTERSECTS(geometry, POLYGON((43.5845 -79.5442, 43.6079 -79.4893, 43.5677 -79.4632, 43.6129 -79.3925, 43.6223 -79.3238, 43.6576 -79.3163, 43.7945 -79.1178, 43.8144 -79.1542, 43.8555 -79.1714, 43.7509 -79.6390, 43.5845 -79.5442)) ``` -#### POST with cql-json +#### Example 2: POST with cql-json -``` +```http POST /search { "filter-lang"="cql-json", @@ -351,11 +360,95 @@ POST /search } ``` -## Additional Examples +### Example 3 + +Queryable properties can be used on either side of an operator. This is a generic example, as there are are few STAC properties +that are comparable in a meaningful way. This example uses a contrived example of two proprietary properties, `prop1` and `prop2` that are of the +same type. + +This queryables JSON Schema is used in these examples: + +```json +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "$id" : "https://example.org/queryables", + "type" : "object", + "title" : "Queryables for Example STAC API", + "description" : "Queryable names for the example STAC API Item Search filter.", + "properties" : { + "prop1" : { + "description" : "Property 1", + "type": "integer" + }, + "prop2" : { + "description" : "Property 2", + "type": "integer" + } + } +} +``` + +#### Example 3: GET with cql-text + +```http +GET /search?filter=prop1 = prop2 +``` + +#### Example 3: POST with cql-json + +```http +POST /search +{ + "filter-lang"="cql-json", + "filter": { + "eq": [ + { "property": "prop1" }, + { "property": "prop2" } + ] + } +} +``` + +### Example 4 We'll be imagining these as queries against [EarthSearch Sentinel 2 COG](https://stacindex.org/catalogs/earth-search#/Cnz1sryATwWudkxyZekxWx6356v9RmvvCcLLw79uHWJUDvt2?t=items)' data. -A sample STAC Item (leaving off all the asset info) is: + +The queryables defined are as follows: + +```json +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "$id" : "https://example.org/queryables", + "type" : "object", + "title" : "Queryables for Example STAC API", + "description" : "Queryable names for the example STAC API Item Search filter.", + "properties" : { + "geometry" : { + "description" : "Geometry", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry" + }, + "datetime" : { + "description" : "Datetime", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime" + }, + "eo:cloud_cover" : { + "description" : "Cloud Cover", + "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" + }, + "sentinel:data_coverage" : { + "description" : "Sentinel Data Coverage", + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } +} +``` + +Note that `sentinel:data_coverage` is a proprietary extension (e.g., not defined in a formal, public way), and must be defined directly within the schema. + +A sample STAC Item (excluding `assets`) is: ```json { @@ -396,17 +489,18 @@ A sample STAC Item (leaving off all the asset info) is: } ``` -One problem in working with Sentinel-2 data is that many scenes only contain a tiny "sliver" of data, where the satellite's recording path intersection only a corner of a grid square. This examples shows +One problem in working with Sentinel-2 data is that many scenes only contain a tiny "sliver" of data, where the satellite's +recording path intersection only a corner of a grid square. This examples shows Show me all imagery that has low cloud cover (less than 10), and high data coverage (50), as I'd like a cloud free image that is not just a tiny sliver of data. -#### AND cql-text (GET) +#### Example 4: AND cql-text (GET) ```http /search?filter=sentinel:data_coverage > 50 AND eo:cloud_cover < 10 ``` -#### AND cql-json (POST) +#### Example 4: AND cql-json (POST) ```json { @@ -436,13 +530,17 @@ a tiny sliver of data. An 'or' is also supported, matching if either condition is true. Though it's not a sensible query you could get images that have full data coverage or low cloud cover. -#### OR cql-text (GET) +### Example 5 + +This uses the same queryables as Example 4. + +#### Example 5: OR cql-text (GET) ```http /search?filter=sentinel:data_coverage > 50 OR eo:cloud_cover < 10 ``` -#### OR cql-json (POST) +#### Example 5: OR cql-json (POST) ```json { @@ -465,18 +563,20 @@ coverage or low cloud cover. } ``` -### Temporal +### Example 6: Temporal + +This uses the same queryables as Example 4. The only temporal operator required is `ANYINTERACTS`, which follows the same semantics as the existing `datetime` parameter. This is effectively that the datetime or interval operands have any overlap between them. -#### ANYINTERACTS cql-text (GET) +#### Example 6: ANYINTERACTS cql-text (GET) ```http /search?filter=datetime ANYINTERACTS 2020-11-11 ``` -#### ANYINTERACTS cql-json (POST) +#### Example 6: ANYINTERACTS cql-json (POST) ```json { @@ -489,19 +589,19 @@ The only temporal operator required is `ANYINTERACTS`, which follows the same se } ``` -### Spatial +### Example 6: Spatial The only spatial operator that must be implemented is `INTERSECTS`. This has the same semantics as the one provided in the Item Search `intersects` parameter. The `cql-text` format uses WKT geometries and the `cql-json` format uses GeoJSON geometries. -#### INTERSECTS cql-text (GET) +#### Example 6: INTERSECTS cql-text (GET) ```http /search?filter=INTERSECTS(geometry,POLYGON((-77.0824 38.7886,-77.0189 38.7886,-77.0189 38.8351,-77.0824 38.8351,-77.0824 38.7886))) ``` -#### INTERSECTS cql-json (POST) +#### Example 6: INTERSECTS cql-json (POST) ```json { @@ -521,10 +621,10 @@ GeoJSON geometries. } ``` -## Implementation +## Implementations * [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi -* [https://github.com/azavea/franklin](Franklin) is working on it. +* [Franklin](https://github.com/azavea/franklin) is working on it. Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. diff --git a/fragments/filter/cql_schema.json b/fragments/filter/cql.json similarity index 91% rename from fragments/filter/cql_schema.json rename to fragments/filter/cql.json index 5115df0f..0bfbef33 100644 --- a/fragments/filter/cql_schema.json +++ b/fragments/filter/cql.json @@ -11,10 +11,6 @@ ], "$recursiveAnchor": true, "$defs": { - "$comment": "=======================================================", - "$comment": "= BOOLEAN VALUE EXPRESSION =", - "$comment": "=======================================================", - "andExpression": { "type": "object", "required": ["and"], @@ -58,10 +54,6 @@ } }, - "$comment": "=======================================================", - "$comment": "= COMPARISON PREDICATES =", - "$comment": "=======================================================", - "comparisonPredicate" : { "oneOf": [ {"$ref": "#/$defs/binaryComparisonPredicate"}, @@ -208,10 +200,6 @@ } }, - "$comment": "=======================================================", - "$comment": "= SPATIAL PREDICATES =", - "$comment": "=======================================================", - "spatialPredicate" : { "oneOf": [ {"$ref": "#/$defs/intersectsExpression"} @@ -242,10 +230,6 @@ ] }, - "$comment": "=======================================================", - "$comment": "= TEMPORAL EXPRESSIONS =", - "$comment": "=======================================================", - "temporalPredicate" : { "oneOf": [ {"$ref": "#/$defs/anyinteractsExpression"} @@ -276,9 +260,6 @@ ] }, - "$comment": "=======================================================", - "$comment": "= PROPERTY REFERENCE =", - "$comment": "=======================================================", "propertyRef": { "type": "object", "required": ["property"], @@ -287,10 +268,6 @@ } }, - "$comment": "=======================================================", - "$comment": "= LITERALS =", - "$comment": "=======================================================", - "scalarLiteral": { "oneOf": [ { "type": "string" }, diff --git a/fragments/filter/cql.yml b/fragments/filter/cql.yml new file mode 100644 index 00000000..41abdeab --- /dev/null +++ b/fragments/filter/cql.yml @@ -0,0 +1,434 @@ +--- +openapi: 3.0.3 +info: + title: Schema of Common Query Language (CQL) + description: 'For use in OpenAPI 3.0 documents' + version: '1.0.0-draft.1' +paths: {} +components: + schemas: + booleanValueExpression: + type: object + oneOf: + - "$ref": "#/components/schemas/andExpression" + - "$ref": "#/components/schemas/orExpression" + - "$ref": "#/components/schemas/notExpression" + - "$ref": "#/components/schemas/comparisonPredicate" + - "$ref": "#/components/schemas/spatialPredicate" + - "$ref": "#/components/schemas/temporalPredicate" + andExpression: + type: object + required: + - and + properties: + and: + type: array + minItems: 2 + items: + "$ref": "#/components/schemas/booleanValueExpression" + orExpression: + type: object + required: + - or + properties: + or: + type: array + minItems: 2 + items: + "$ref": "#/components/schemas/booleanValueExpression" + notExpression: + type: object + required: + - not + properties: + not: + type: array + minItems: 1 + maxItems: 1 + items: + "$ref": "#/components/schemas/booleanValueExpression" + comparisonPredicate: + oneOf: + - "$ref": "#/components/schemas/binaryComparisonPredicate" + - "$ref": "#/components/schemas/isLikePredicate" + - "$ref": "#/components/schemas/isBetweenPredicate" + - "$ref": "#/components/schemas/isInListPredicate" + - "$ref": "#/components/schemas/isNullPredicate" + binaryComparisonPredicate: + oneOf: + - "$ref": "#/components/schemas/eqExpression" + - "$ref": "#/components/schemas/ltExpression" + - "$ref": "#/components/schemas/gtExpression" + - "$ref": "#/components/schemas/lteExpression" + - "$ref": "#/components/schemas/gteExpression" + eqExpression: + type: object + required: + - eq + properties: + eq: + "$ref": "#/components/schemas/scalarOperands" + ltExpression: + type: object + required: + - lt + properties: + lt: + "$ref": "#/components/schemas/scalarOperands" + gtExpression: + type: object + required: + - gt + properties: + gt: + "$ref": "#/components/schemas/scalarOperands" + lteExpression: + type: object + required: + - lte + properties: + lte: + "$ref": "#/components/schemas/scalarOperands" + gteExpression: + type: object + required: + - gte + properties: + gte: + "$ref": "#/components/schemas/scalarOperands" + isBetweenPredicate: + type: object + required: + - between + properties: + between: + type: object + required: + - value + - lower + - upper + properties: + value: + "$ref": "#/components/schemas/valueExpression" + lower: + "$ref": "#/components/schemas/scalarExpression" + upper: + "$ref": "#/components/schemas/scalarExpression" + isLikePredicate: + type: object + required: + - like + properties: + like: + "$ref": "#/components/schemas/scalarOperands" + wildcard: + type: string + default: "%" + singleChar: + type: string + default: "." + escapeChar: + type: string + default: "\\" + nocase: + type: boolean + default: true + isInListPredicate: + type: object + required: + - in + properties: + in: + type: object + required: + - value + - list + properties: + value: + "$ref": "#/components/schemas/valueExpression" + list: + type: array + items: + "$ref": "#/components/schemas/valueExpression" + nocase: + type: boolean + default: true + valueExpression: + oneOf: + - "$ref": "#/components/schemas/scalarExpression" + - "$ref": "#/components/schemas/spatialLiteral" + - "$ref": "#/components/schemas/typedTemporalLiteral" + scalarOperands: + type: array + minItems: 2 + maxItems: 2 + items: + "$ref": "#/components/schemas/scalarExpression" + scalarExpression: + oneOf: + - "$ref": "#/components/schemas/scalarLiteral" + - "$ref": "#/components/schemas/propertyRef" + isNullPredicate: + type: object + required: + - isNull + properties: + isNull: + "$ref": "#/components/schemas/scalarExpression" + spatialPredicate: + oneOf: + - "$ref": "#/components/schemas/intersectsExpression" + intersectsExpression: + type: object + required: + - intersects + properties: + intersects: + "$ref": "#/components/schemas/spatialOperands" + spatialOperands: + type: array + minItems: 2 + maxItems: 2 + items: + "$ref": "#/components/schemas/geomExpression" + geomExpression: + oneOf: + - "$ref": "#/components/schemas/spatialLiteral" + - "$ref": "#/components/schemas/propertyRef" + temporalPredicate: + oneOf: + - "$ref": "#/components/schemas/anyinteractsExpression" + anyinteractsExpression: + type: object + required: + - anyinteracts + properties: + anyinteracts: + "$ref": "#/components/schemas/temporalOperands" + temporalOperands: + type: array + minItems: 2 + maxItems: 2 + items: + "$ref": "#/components/schemas/temporalExpression" + temporalExpression: + oneOf: + - "$ref": "#/components/schemas/temporalLiteral" + - "$ref": "#/components/schemas/propertyRef" + propertyRef: + type: object + required: + - property + properties: + propertyName: + type: string + scalarLiteral: + oneOf: + - type: string + - type: number + - type: boolean + spatialLiteral: + oneOf: + - "$ref": "#/components/schemas/geometryLiteral" + - "$ref": "#/components/schemas/envelopeLiteral" + geometryLiteral: + oneOf: + - "$ref": "#/components/schemas/point" + - "$ref": "#/components/schemas/linestring" + - "$ref": "#/components/schemas/polygon" + - "$ref": "#/components/schemas/multipoint" + - "$ref": "#/components/schemas/multilinestring" + - "$ref": "#/components/schemas/multipolygon" + geojson-bbox: + type: array + minItems: 4 + maxItems: 4 + items: + type: number + point: + title: GeoJSON Point + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - Point + coordinates: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + linestring: + title: GeoJSON LineString + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - LineString + coordinates: + type: array + minItems: 2 + items: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + polygon: + title: GeoJSON Polygon + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - Polygon + coordinates: + type: array + items: + type: array + minItems: 4 + items: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + multipoint: + title: GeoJSON MultiPoint + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - MultiPoint + coordinates: + type: array + items: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + multilinestring: + title: GeoJSON MultiLineString + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - MultiLineString + coordinates: + type: array + items: + type: array + minItems: 2 + items: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + multipolygon: + title: GeoJSON MultiPolygon + type: object + required: + - type + - coordinates + properties: + type: + type: string + enum: + - MultiPolygon + coordinates: + type: array + items: + type: array + items: + type: array + minItems: 4 + items: + type: array + minItems: 2 + items: + type: number + bbox: + "$ref": "#/components/schemas/geojson-bbox" + envelopeLiteral: + type: object + required: + - bbox + properties: + bbox: + "$ref": "#/components/schemas/bbox" + bbox: + type: array + oneOf: + - minItems: 4 + maxItems: 4 + - minItems: 6 + maxItems: 6 + items: + type: number + temporalLiteral: + oneOf: + - "$ref": "#/components/schemas/timeString" + - "$ref": "#/components/schemas/periodString" + timeString: + oneOf: + - type: string + format: date + - type: string + format: date-time + periodString: + type: array + minItems: 2 + maxItems: 2 + items: + oneOf: + - type: string + format: date + - type: string + format: date-time + - type: string + enum: + - .. + typedTemporalLiteral: + oneOf: + - "$ref": "#/components/schemas/typedTimeString" + - "$ref": "#/components/schemas/typedPeriodString" + typedTimeString: + type: object + required: + - datetime + properties: + datetime: + "$ref": "#/components/schemas/timeString" + typedPeriodString: + type: object + required: + - datetime + properties: + datetime: + "$ref": "#/components/schemas/periodString" diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index 658fc949..e3219f4b 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -6,6 +6,7 @@ info: paths: "/": + description: Landing Page get: responses: '200': @@ -126,7 +127,7 @@ components: filter-cql-json: description: | A CQL filter expression in the 'cql-json' encoding. - $ref: './cql_schema.json' + $ref: './cql.yml' filter-lang: description: | The CQL filter encoding that the 'filter' value uses. diff --git a/implementation.md b/implementation.md index 5b6e955c..dc53fe7a 100644 --- a/implementation.md +++ b/implementation.md @@ -10,7 +10,7 @@ configuration so browser-based UIs running on a different domain may more easily APIs should acknowledge pre-flight request headers. In general, these header values should be set on responses: -``` +```http access-control-allow-origin: * access-control-allow-methods: OPTIONS, POST, GET access-control-allow-headers: Content-Type diff --git a/item-search/README.md b/item-search/README.md index 3f0b3463..95573c37 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -12,9 +12,10 @@ - [PUT / PATCH / DELETE](#put--patch--delete) - [Extensions](#extensions) - [Fields](#fields) - - [Query](#query) + - [Filter](#filter) - [Sort](#sort) - [Context](#context) + - [Query](#query) - **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/item-search)) - **Conformance URI:** @@ -205,17 +206,17 @@ allows the client to suggest to the server which Item attributes should be inclu through the use of a `fields` parameter. The full description of how this extension works can be found in the [fields fragment](../fragments/fields/). -### Query +### Filter -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot -- **Definition**: [STAC API - Query Fragment](../fragments/query/) +- **Definition**: [STAC API - Filter Fragment](../fragments/filter/) The STAC search endpoint, `/search`, by default only accepts a limited set of parameters to limit the results -by properties. The Query extension adds a new parameter, `query`, that can take a number of comparison operators to -match predicates between the fields requested and the values of Item objects. It can be used with both GET and POST, though -GET includes the exact same JSON. The full details on the JSON structure are specified in the [query -fragment](../fragments/query/). +by properties. The Filter extension adds a new parameter, `filter`, that can take a number of comparison operators to +match predicates between the fields requested and the values of Item objects. It can be used with both GET and POST and supports two +query formats, `cql-text` and `cql-json`. The full details on the JSON structure are specified in the [filter +fragment](../fragments/filter/). ### Sort @@ -239,3 +240,15 @@ of this extension can be found in the [sort fragment](../fragments/sort). This extension is intended to augment the core ItemCollection responses from the `search` API endpoint with a JSON object called `context` that includes the number of items `matched`, `returned` and the `limit` requested. The full description and examples of this are found in the [context fragment](../fragments/context). + +### Query + +- **Conformance URI:** +- **Extension [Maturity Classification](../extensions.md#extension-maturity):** Deprecated +- **Definition**: [STAC API - Query Fragment](../fragments/query/) + +The STAC search endpoint, `/search`, by default only accepts a limited set of parameters to limit the results +by properties. The Query extension adds a new parameter, `query`, that can take a number of comparison operators to +match predicates between the fields requested and the values of Item objects. It can be used with both GET and POST, though +GET includes the exact same JSON. The full details on the JSON structure are specified in the [query +fragment](../fragments/query/). diff --git a/item-search/openapi.yaml b/item-search/openapi.yaml index 0954ad76..d364dda8 100644 --- a/item-search/openapi.yaml +++ b/item-search/openapi.yaml @@ -41,7 +41,7 @@ paths: - $ref: '#/components/parameters/collectionsArray' # extensions - $ref: '../fragments/fields/openapi.yaml#/components/parameters/fields' - - $ref: '../fragments/query/openapi.yaml#/components/parameters/filter' + - $ref: '../fragments/filter/openapi.yaml#/components/parameters/filter' - $ref: '../fragments/sort/openapi.yaml#/components/parameters/sortby' responses: '200': From 518184f957fbc87788390f153c7d5be280c80c54 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 15:10:10 -0400 Subject: [PATCH 38/61] fix ref to cql yml --- fragments/filter/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index e3219f4b..90df6bac 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -127,7 +127,7 @@ components: filter-cql-json: description: | A CQL filter expression in the 'cql-json' encoding. - $ref: './cql.yml' + $ref: './cql.yml#/components/schemas/booleanValueExpression' filter-lang: description: | The CQL filter encoding that the 'filter' value uses. From 2bdf845a1c79f0888c1e52b0e2eec66c62b38f16 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 14 May 2021 15:13:26 -0400 Subject: [PATCH 39/61] minor fixes for filter openapi --- fragments/filter/openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index 90df6bac..16872695 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -37,7 +37,7 @@ paths: schema: $ref: 'https://json-schema.org/draft/2019-09/schema' default: - $ref: '../core/commons.yaml#/components/responses/Error' + $ref: '../../core/commons.yaml#/components/responses/Error' /collections/{collectionId}: get: responses: @@ -51,7 +51,7 @@ paths: /collections/{collectionId}/queryables: get: summary: Get the JSON Schema defining the list of variable terms that can be used in CQL expressions. - operationId: getQueryables + operationId: getQueryablesForCollection description: |- This endpoint returns a list of variable terms that can be used in CQL expressions. The precise definition of this can be found in the OGC API - Features - Part 3: Filtering and the @@ -66,7 +66,7 @@ paths: schema: $ref: 'https://json-schema.org/draft/2019-09/schema' default: - $ref: '../core/commons.yaml#/components/responses/Error' + $ref: '../../core/commons.yaml#/components/responses/Error' components: parameters: filter: From 19b76fe142e50a9bce98e6c7efc4f8e9a4784b27 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 17 May 2021 14:22:43 -0400 Subject: [PATCH 40/61] rework example landing page, link rel table, and conformsTo for conformance classes to make it more clear which of them go with which conformance classes --- core/README.md | 129 +++++++++++++----------------- item-search/README.md | 61 ++++++++++++++ ogcapi-features/README.md | 51 ++++++++++++ overview.md | 75 +++++++++++++++++ core/stac-api.gv => stac-api.gv | 0 core/stac-api.png => stac-api.png | Bin 6 files changed, 244 insertions(+), 72 deletions(-) rename core/stac-api.gv => stac-api.gv (100%) rename core/stac-api.png => stac-api.png (100%) diff --git a/core/README.md b/core/README.md index 1eab0b5b..ca8f5551 100644 --- a/core/README.md +++ b/core/README.md @@ -2,6 +2,7 @@ - [STAC API - Core Specification](#stac-api---core-specification) - [Recommended Link Relations at `/`](#recommended-link-relations-at-) + - [Example Landing Page with for STAC API - Core](#example-landing-page-with-for-stac-api---core) - **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)), and [commons.yaml](commons.yaml) is the OpenAPI version of the core [STAC spec](../stac-spec) JSON Schemas. @@ -9,9 +10,59 @@ - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Dependencies**: None -The base of a STAC API is its landing page. This resource is the starting point to discover what behaviors the API supports via link relations. -This behavior in a RESTful API is known as Hypermedia as the Engine of Application State (HATEOAS). STAC API relies heavily on hypermedia for API endpoint -navigation. +The base of a STAC API is its landing page. This resource is the starting point to discover what behaviors the API supports via the `conformsTo` values and link relations. +This behavior in a RESTful API is known as +[Hypermedia as the Engine of Application State (HATEOAS)](https://en.wikipedia.org/wiki/HATEOAS). +STAC API relies heavily on hypermedia for API resource navigation. + +There are a few requirements for the returned document: + +- The returned JSON must be a valid [STAC Catalog](../stac-spec/catalog-spec/catalog-spec.md), and it can provide any number of 'child' links +to navigate down to additional Catalog, [Collection](../stac-spec/collection-spec/README.md), and [Item](../stac-spec/item-spec/README.md) objects. +- The `links` section is a required part of STAC Catalog, and serves as the list of API endpoints. These can live at any location, the +client must inspect the the `rel` (relationship) to understand what capabilities are offered at each location. +- The `conformsTo` section must provide the capabilities of this service. This is the field + that indicates to clients that this is a STAC API and how to access conformance classes, including this + one. The relevant conformance URI's are listed in each part of the + API specification. If a conformance URI is listed then the service must implement all of the required capabilities. + +Note the `conformsTo` JSON object follows exactly the structure of OGC API - Features [declaration of conformance +classes](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes), except is available directly under +the landing page. This is a slight break from how OGC API does things, as STAC feels it is important for clients to be able to understand +conformance in a single request. Implementers choosing to also implement the OGC API - Features and/or STAC API - Features conformance classes must also implment the `/conformance` endpoint. + +This particular catalog provides the ability to browse down to child STAC Collection objects through its `child` links, and also provides the search +endpoint to be able to search across items in its collections. Note though that none of those links are required, other servers may provide +different conformance classes and a different set of links. + +The only requirements of the STAC API core class are to provide a valid STAC Catalog that includes a valid `conformsTo` JSON object +in it. Any API implementing that is considered a valid STAC API, and clients can inspect the document to figure out what other +capabilities are on offer and how to reach them. + +The root endpoint (`/`) is most useful when it presents a complete `Catalog` representation of all the data contained in the API, such +that all `Collection` and `Item` objects can be navigated to by transitively traversing links from this root. This spec does not require any +API endpoints from OAFeat or STAC API to be implemented, so the following links may not exist if the endpoint has not been implemented. + +## Recommended Link Relations at `/` + +When implementing the STAC API Core conformance class, it it recommended to implement these Link relations. + +| **`rel`** | **href to** | **From** | **Description** | +|-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| +| `root` | The root URI | STAC Core | Reference to self URI | +| `self` | The root URI | OAFeat | Reference to self URI | +| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | +| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | +| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collection and Item objects | + +It is also valid to have `item` links from the landing page, but most STAC API services are used to +serve up a large number of features, so they typically +use several layers of intermediate `child` links before getting to Item objects. + +## Example Landing Page with for STAC API - Core + +This document is what would be expected from an api that only implements STAC API - Core. In practice, +most APIs will also implement other conformance classes, and those will be reflected in the `links` and `conformsTo` fields. ```json { @@ -19,6 +70,9 @@ navigation. "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", + "conformsTo" : [ + "https://api.stacspec.org/v1.0.0-beta.1/core" + ], "links": [ { "rel": "self", @@ -30,11 +84,6 @@ navigation. "type": "application/json", "href": "https://stacserver.org" }, - { - "rel": "conformance", - "type": "application/json", - "href": "https://stacserver.org/conformance" - }, { "rel": "service-desc", "type": "application/vnd.oai.openapi+json;version=3.0", @@ -45,11 +94,6 @@ navigation. "type": "text/html", "href": "https://stacserver.org/api.html" }, - { - "rel": "data", - "type": "application/json", - "href": "https://stacserver.org/collections" - }, { "rel": "child", "type": "application/json", @@ -59,66 +103,7 @@ navigation. "rel": "child", "type": "application/json", "href": "https://stacserver.org/collections/landsat-8", - }, - { - "rel": "search", - "type": "application/geo+json", - "href": "https://stacserver.org/search" } - ], - "conformsTo" : [ - "https://api.stacspec.org/v1.0.0-beta.1/core", - "https://api.stacspec.org/v1.0.0-beta.1/item-search", - "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", - "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", - "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" ] } ``` - -There are a few requirements for the returned document: - -- The returned JSON must be a valid [STAC Catalog](../stac-spec/catalog-spec/catalog-spec.md), and it can provide any number of 'child' links -to navigate down to additional Catalog, [Collection](../stac-spec/collection-spec/README.md), and [Item](../stac-spec/item-spec/README.md) objects. -- The `links` section is a required part of STAC Catalog, and serves as the list of API endpoints. These can live at any location, the -client must inspect the the `rel` (relationship) to understand what capabilities are offered at each location. -- The `conformsTo` section must provide the capabilities of this service. The relevant conformance URI's are listed in each part of the -API specification. If a conformance URI is listed then the service must implement all of the required capabilities. - -Note the `conformsTo` JSON object follows exactly the structure of OGC API - Features [declaration of conformance -classes](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes), except is available directly under -the landing page. This is a slight break from how OGC API does things, as STAC feels it is important for clients to be able to understand -conformance in a single request. To be conformant to OGC API's the `/conformance` endpoint must be implemented as well. - -This particular catalog provides the ability to browse down to child STAC Collection objects through its `child` links, and also provides the search -endpoint to be able to search across items in its collections. Note though that none of those links are required, other servers may provide -different conformance classes and a different set of links. - -The only requirements of the STAC API core class are to provide a valid STAC Catalog that includes a valid `conformsTo` JSON object -in it. Any API implementing that is considered a valid STAC API, and clients can inspect the document to figure out what other -capabilities are on offer and how to reach them. - -The root endpoint (`/`) is most useful when it presents a complete `Catalog` representation of all the data contained in the API, such -that all `Collection` and `Item` objects can be navigated to by transitively traversing links from this root. This spec does not require any -API endpoints from OAFeat or STAC API to be implemented, so the following links may not exist if the endpoint has not been implemented. - -## Recommended Link Relations at `/` - -![Diagram of STAC link relations](stac-api.png) - -In each node of the diagram above, there is also a `self` and `root` link that are not depicted to keep the digram more concise. - - -| **`rel`** | **href to** | **From** | **Description** | -|-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| -| `root` | The root URI | STAC Core | Reference to self URI | -| `self` | The root URI | OAFeat | Reference to self URI | -| `conformance` | OGC `/conformance` endpoint | OAFeat / OACommon | Only required for OGC API Compliant implementations | -| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | -| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | -| `data` | OAFeat/OACommon `/collections` endpoint | Commons Collection | The full list of Collection objects provided by the API | -| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collection and Item objects | -| `search` | The STAC search endpoint (often `/search`) | STAC Search | Cross-collection query endpoint to select sub-sets of STAC Item objects | - -It is also valid to have `item` links from the landing page, but most STAC API's are used to serve up a large number of features, so they typically -use several layers of intermediate `child` links before getting to Items. diff --git a/item-search/README.md b/item-search/README.md index 3f0b3463..85f81ac2 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -10,6 +10,8 @@ - [GET](#get) - [POST](#post) - [PUT / PATCH / DELETE](#put--patch--delete) + - [Recommended Link Relations at `/`](#recommended-link-relations-at-) + - [Example Landing Page for STAC API - Item Search](#example-landing-page-for-stac-api---item-search) - [Extensions](#extensions) - [Fields](#fields) - [Query](#query) @@ -186,6 +188,65 @@ searching on specific Item properties. The other HTTP verbs are not supported in STAC Item Search. The [Transaction Extension](../ogcapi-features/extensions/transaction/README.md) does implement them, for STAC and OAFeat implementations that want to enable writing and deleting items. +## Recommended Link Relations at `/` + +When implementing the STAC API - Item Search conformance class, it it recommended to implement these Link relations. + +| **`rel`** | **href to** | **From** | **Description** | +|-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| +| `root` | The root URI | STAC Core | Reference to self URI | +| `self` | The root URI | OAFeat | Reference to self URI | +| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | +| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | +| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collection and Item objects | +| `search` | The STAC search endpoint (often `/search`) | STAC Search | Cross-collection query endpoint to select sub-sets of STAC Item objects | + +It is also valid to have `item` links from the landing page, but most STAC API services are used to +serve up a large number of features, so they typically +use several layers of intermediate `child` links before getting to Item objects. + +## Example Landing Page for STAC API - Item Search + +```json +{ + "stac_version": "1.0.0-beta.2", + "id": "example-stac", + "title": "A simple STAC API Example", + "description": "This Catalog aims to demonstrate the a simple landing page", + "conformsTo" : [ + "https://api.stacspec.org/v1.0.0-beta.1/core", + "https://api.stacspec.org/v1.0.0-beta.1/item-search" + ], + "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "service-desc", + "type": "application/vnd.oai.openapi+json;version=3.0", + "href": "https://stacserver.org/api" + }, + { + "rel": "service-doc", + "type": "text/html", + "href": "https://stacserver.org/api.html" + }, + { + "rel": "search", + "type": "application/geo+json", + "href": "https://stacserver.org/search" + } + ] +} +``` + ## Extensions These extensions provide additional functionality that enhances the core item search. All are specified as diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index a20b233d..260cde60 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -3,6 +3,7 @@ - [STAC API - Features](#stac-api---features) - [Endpoints](#endpoints) - [Examples](#examples) + - [Example Landing Page for STAC API - Features](#example-landing-page-for-stac-api---features) *based on [**OGC API - Features - Part 1: Core**](https://www.ogc.org/standards/ogcapi-features)* @@ -103,3 +104,53 @@ Request 10 results from the data in `mycollection` from between January 1st (inc ```http GET /collections/mycollection/items?datetime=2019-01-01T00:00:00Z/2019-03-31T23:59:59Z&limit=10 ``` + +## Example Landing Page for STAC API - Features + +```json +{ + "stac_version": "1.0.0-beta.2", + "id": "example-stac", + "title": "A simple STAC API Example", + "description": "This Catalog aims to demonstrate the a simple landing page", + "conformsTo" : [ + "https://api.stacspec.org/v1.0.0-beta.1/core", + "https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" + ], + "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "conformance", + "type": "application/json", + "href": "https://stacserver.org/conformance" + }, + { + "rel": "service-desc", + "type": "application/vnd.oai.openapi+json;version=3.0", + "href": "https://stacserver.org/api" + }, + { + "rel": "service-doc", + "type": "text/html", + "href": "https://stacserver.org/api.html" + }, + { + "rel": "data", + "type": "application/json", + "href": "https://stacserver.org/collections" + } + ] +} +``` \ No newline at end of file diff --git a/overview.md b/overview.md index 59a0dd20..a345035a 100644 --- a/overview.md +++ b/overview.md @@ -12,6 +12,8 @@ shorthand). Notes on implementation recommendations may be found [here](impleme ## STAC API Description +### Core + The [core](core/) of STAC API simply returns a valid [STAC Catalog](stac-spec/catalog-spec/catalog-spec.md) and a description of what parts of the fuller STAC API specification it conforms to. The `links` section of the Catalog is the jumping off point for the more powerful capabilities - it contains a list of URL's, each described by particular link @@ -125,3 +127,76 @@ have the URI's for conformance to actually resolve to machine-readable informati | STAC Features | [STAC API - Features](ogcapi-features) | | Specifies the use of OGC API - Features to serve STAC Item and Collection objects | Additional conformance classes are specified in the [STAC Extensions](extensions.md#Conformance-classes-of-extensions). + +## Recommended Link Relations at `/` + +When all three conformance classes (Core, Features, Item Search) are implemented, the relationships among +various resources are shown in the following diagram. In each node, there is also a `self` and `root` link that are not depicted to keep the diagram more concise. + +![Diagram of STAC link relations](stac-api.png) + +These Landing Page will at least have the following: + +```json +{ + "stac_version": "1.0.0-beta.2", + "id": "example-stac", + "title": "A simple STAC API Example", + "description": "This Catalog aims to demonstrate the a simple landing page", + "conformsTo" : [ + "https://api.stacspec.org/v1.0.0-beta.1/core", + "https://api.stacspec.org/v1.0.0-beta.1/item-search", + "https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", + "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" + ], + "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stacserver.org" + }, + { + "rel": "conformance", + "type": "application/json", + "href": "https://stacserver.org/conformance" + }, + { + "rel": "service-desc", + "type": "application/vnd.oai.openapi+json;version=3.0", + "href": "https://stacserver.org/api" + }, + { + "rel": "service-doc", + "type": "text/html", + "href": "https://stacserver.org/api.html" + }, + { + "rel": "data", + "type": "application/json", + "href": "https://stacserver.org/collections" + }, + { + "rel": "child", + "type": "application/json", + "href": "https://stacserver.org/collections/sentinel-2", + }, + { + "rel": "child", + "type": "application/json", + "href": "https://stacserver.org/collections/landsat-8", + }, + { + "rel": "search", + "type": "application/geo+json", + "href": "https://stacserver.org/search" + } + ] +} +``` diff --git a/core/stac-api.gv b/stac-api.gv similarity index 100% rename from core/stac-api.gv rename to stac-api.gv diff --git a/core/stac-api.png b/stac-api.png similarity index 100% rename from core/stac-api.png rename to stac-api.png From 88749846dc4e490bae58169d39a5f58d1e33284d Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 17 May 2021 14:25:56 -0400 Subject: [PATCH 41/61] fix typo --- core/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/README.md b/core/README.md index ca8f5551..087635af 100644 --- a/core/README.md +++ b/core/README.md @@ -2,7 +2,7 @@ - [STAC API - Core Specification](#stac-api---core-specification) - [Recommended Link Relations at `/`](#recommended-link-relations-at-) - - [Example Landing Page with for STAC API - Core](#example-landing-page-with-for-stac-api---core) + - [Example Landing Page for STAC API - Core](#example-landing-page-for-stac-api---core) - **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)), and [commons.yaml](commons.yaml) is the OpenAPI version of the core [STAC spec](../stac-spec) JSON Schemas. @@ -59,7 +59,7 @@ It is also valid to have `item` links from the landing page, but most STAC API s serve up a large number of features, so they typically use several layers of intermediate `child` links before getting to Item objects. -## Example Landing Page with for STAC API - Core +## Example Landing Page for STAC API - Core This document is what would be expected from an api that only implements STAC API - Core. In practice, most APIs will also implement other conformance classes, and those will be reflected in the `links` and `conformsTo` fields. From 2fbab9c6829aeb1d2ac4730b2b17189c876a7adf Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 18 May 2021 20:21:33 -0400 Subject: [PATCH 42/61] add links to example landing page --- core/README.md | 6 ++++-- item-search/README.md | 5 +++++ ogcapi-features/README.md | 6 ++++++ overview.md | 4 ++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/README.md b/core/README.md index 087635af..4523f275 100644 --- a/core/README.md +++ b/core/README.md @@ -61,8 +61,10 @@ use several layers of intermediate `child` links before getting to Item objects. ## Example Landing Page for STAC API - Core -This document is what would be expected from an api that only implements STAC API - Core. In practice, -most APIs will also implement other conformance classes, and those will be reflected in the `links` and `conformsTo` fields. +This JSON is what would be expected from an api that only implements STAC API - Core. In practice, +most APIs will also implement other conformance classes, and those will be reflected in the `links` and +`conformsTo` fields. A more typical Landing Page example is in +the [overview](../overview.md#example-landing-page) document. ```json { diff --git a/item-search/README.md b/item-search/README.md index 85f81ac2..b33a23f3 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -207,6 +207,11 @@ use several layers of intermediate `child` links before getting to Item objects. ## Example Landing Page for STAC API - Item Search +This JSON is what would be expected from an api that only implements STAC API - Item Search. In practice, +most APIs will also implement other conformance classes, and those will be reflected in the `links` and +`conformsTo` fields. A more typical Landing Page example is in +the [overview](../overview.md#example-landing-page) document. + ```json { "stac_version": "1.0.0-beta.2", diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 260cde60..54b5f254 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -107,6 +107,12 @@ GET /collections/mycollection/items?datetime=2019-01-01T00:00:00Z/2019-03-31T23: ## Example Landing Page for STAC API - Features +This JSON is what would be expected from an api that only implements STAC API - Features. In practice, +most APIs will also implement other conformance classes, and those will be reflected in the `links` and +`conformsTo` fields. A more typical Landing Page example is in +the [overview](../overview.md#example-landing-page) document. + + ```json { "stac_version": "1.0.0-beta.2", diff --git a/overview.md b/overview.md index a345035a..cda38b75 100644 --- a/overview.md +++ b/overview.md @@ -128,14 +128,14 @@ have the URI's for conformance to actually resolve to machine-readable informati Additional conformance classes are specified in the [STAC Extensions](extensions.md#Conformance-classes-of-extensions). -## Recommended Link Relations at `/` +## Example Landing Page When all three conformance classes (Core, Features, Item Search) are implemented, the relationships among various resources are shown in the following diagram. In each node, there is also a `self` and `root` link that are not depicted to keep the diagram more concise. ![Diagram of STAC link relations](stac-api.png) -These Landing Page will at least have the following: +The Landing Page will at least have the following `conformsTo` and `links`: ```json { From e3726185e21beedc3e4f1657bef0d3b4b66b8722 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 19 May 2021 10:53:26 -0400 Subject: [PATCH 43/61] clarification on queryables, line breaks, wordsmithing --- .circleci/rc.yaml | 21 ++--- core/README.md | 20 +++-- fragments/filter/README.md | 180 +++++++++++++++++++++++++++---------- implementation.md | 8 +- ogcapi-features/README.md | 3 +- overview.md | 3 +- 6 files changed, 163 insertions(+), 72 deletions(-) diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index 625a856a..b0dd538c 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -4,17 +4,15 @@ plugins: # Apply some recommended defaults for consistency - remark-preset-lint-consistent - remark-preset-lint-recommended -# No HTML for security - can't activate yet due to STAC logo in README.md -# - lint-no-html + - lint-no-html # General formatting -# - - remark-lint-emphasis-marker -# - '*' + - - remark-lint-emphasis-marker + - '*' - remark-lint-hard-break-spaces - remark-lint-blockquote-indentation - remark-lint-no-consecutive-blank-lines -# Detect overly long lines - be liberal for now and don't restrict to 80 yet -# - - remark-lint-maximum-line-length -# - 150 + - - remark-lint-maximum-line-length + - 150 # Code - remark-lint-fenced-code-flag - remark-lint-fenced-code-marker @@ -23,24 +21,23 @@ plugins: - 'fenced' # Headings - remark-lint-heading-increment - - remark-lint-no-duplicate-headings - remark-lint-no-multiple-toplevel-headings - remark-lint-no-heading-punctuation - - remark-lint-maximum-heading-length - 70 - - remark-lint-heading-style - atx + - - remark-lint-no-shortcut-reference-link + - false # Lists - remark-lint-list-item-bullet-indent - remark-lint-ordered-list-marker-style - remark-lint-ordered-list-marker-value - remark-lint-checkbox-character-style -# - - remark-lint-unordered-list-marker-style -# - '-' + - - remark-lint-unordered-list-marker-style + - '-' - - remark-lint-list-item-indent - space # Tables - remark-lint-table-pipes -# - remark-lint-table-pipe-alignment # Wait for https://github.com/remarkjs/remark-lint/issues/226 -# Urls - remark-lint-no-literal-urls \ No newline at end of file diff --git a/core/README.md b/core/README.md index 4523f275..118ec657 100644 --- a/core/README.md +++ b/core/README.md @@ -10,7 +10,8 @@ - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Dependencies**: None -The base of a STAC API is its landing page. This resource is the starting point to discover what behaviors the API supports via the `conformsTo` values and link relations. +The base of a STAC API is its landing page. This resource is the starting point to discover what behaviors +the API supports via the `conformsTo` values and link relations. This behavior in a RESTful API is known as [Hypermedia as the Engine of Application State (HATEOAS)](https://en.wikipedia.org/wiki/HATEOAS). STAC API relies heavily on hypermedia for API resource navigation. @@ -29,7 +30,8 @@ client must inspect the the `rel` (relationship) to understand what capabilities Note the `conformsTo` JSON object follows exactly the structure of OGC API - Features [declaration of conformance classes](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes), except is available directly under the landing page. This is a slight break from how OGC API does things, as STAC feels it is important for clients to be able to understand -conformance in a single request. Implementers choosing to also implement the OGC API - Features and/or STAC API - Features conformance classes must also implment the `/conformance` endpoint. +conformance in a single request. Implementers choosing to also implement the OGC API - Features and/or +STAC API - Features conformance classes must also implment the `/conformance` endpoint. This particular catalog provides the ability to browse down to child STAC Collection objects through its `child` links, and also provides the search endpoint to be able to search across items in its collections. Note though that none of those links are required, other servers may provide @@ -47,13 +49,13 @@ API endpoints from OAFeat or STAC API to be implemented, so the following links When implementing the STAC API Core conformance class, it it recommended to implement these Link relations. -| **`rel`** | **href to** | **From** | **Description** | -|-----------|--------------------------------------------|--------------------|------------------------------------------------------------------| -| `root` | The root URI | STAC Core | Reference to self URI | -| `self` | The root URI | OAFeat | Reference to self URI | -| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | -| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | -| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collection and Item objects | +| **`rel`** | **href to** | **From** | **Description** | +| -------------- | ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `root` | The root URI | STAC Core | Reference to self URI | +| `self` | The root URI | OAFeat | Reference to self URI | +| `service-desc` | The OpenAPI service description | OAFeat OpenAPI | Uses the `application/vnd.oai.openapi+json;version=3.0` media type to refer to the OpenAPI 3.0 document that defines the service's API | +| `service-doc` | An HTML service description | OAFeat OpenAPI | Uses the `text/html` media type to refer to a human-consumable description of the service | +| `child` | The child STAC Catalogs & Collections | STAC Core | Provides curated paths to get to STAC Collection and Item objects | It is also valid to have `item` links from the landing page, but most STAC API services are used to serve up a large number of features, so they typically diff --git a/fragments/filter/README.md b/fragments/filter/README.md index b7c14f46..cdb7cf02 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -44,16 +44,25 @@ The Filter extension provides an expressive mechanism for searching based on Ite This extension uses several conformance classes defined in the [OGC API - Features - Part 3: Filtering and the Common Query Language (CQL)](https://portal.ogc.org/files/96288) -specification. As of May 2020, this specification is in draft status but, due to its long-standing use within -geospatial software (e.g., GeoServer), is expected to remain largely the same in final. +specification. As of May 2020, this specification is in draft status. The only anticipated change before final is to the +division of behavior among conformance classes, as described further +in [OAFeat Part 3 Conformance Classes](#oafeat-part-3-conformance-classes). + +OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar (largely similar to the BNF grammar +in the General Model) and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of +the declarative semantics. +The CQL Text format has long-standing use within +geospatial software (e.g., GeoServer), is expected not to change before final. +OGC CQL Text has been previously described +in [OGC Filter Encoding](https://www.ogc.org/standards/filter) and +[OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) +(including a BNF grammar in Annex B). The CQL JSON format is newly-defined, but also not +expected to change before final. It should be noted that the "CQL" referred to here is OGC CQL. It is **not** referencing or related two other "CQL" languages, the [SRU (Search/Retrieve via URL) Contextual Query Language](https://www.loc.gov/standards/sru/cql/index.html) (formerly -known as Common Query Language) or the [Cassandra Query Language](https://cassandra.apache.org/doc/latest/cql/) used by the Cassandra database. - -OGC CQL has been previously described -in [OGC Filter Encoding](https://www.ogc.org/standards/filter) and [OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) (including a BNF grammar in Annex B). -OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar (largely similar to the BNF grammar in the General Model) and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of the declarative semantics. +known as Common Query Language) or the [Cassandra Query Language](https://cassandra.apache.org/doc/latest/cql/) used by the +Cassandra database. ## Limitations of Item Search @@ -61,7 +70,8 @@ OAFeat defines a limited set of filtering capabilities. Filtering can only be do with only a single `bbox` (rectangular spatial filter) parameter and a single datetime (instant or interval) parameter. The STAC Item Search specification extends the functionality of OAFeat in a few key ways: -- It allows cross-collection filtering, whereas OAFeat only allows filtering within a single collection. (`collections` parameter, accepting 0 or more collections) +- It allows cross-collection filtering, whereas OAFeat only allows filtering within a single collection. + (`collections` parameter, accepting 0 or more collections) - It allows filtering by Item ID (`ids` parameter) - It allows filtering based on a single GeoJSON Geometry, rather than only a bbox (`intersects` parameter) @@ -92,22 +102,46 @@ predicates. ## OAFeat Part 3 Conformance Classes -OAFeat CQL defines several conformance classes that allow implementers to create arbitrary compositions of +OAFeat CQL defines several conformance classes that allow implementers to create compositions of functionality that support whatever expressiveness they need. Implementers many choose not to incur the cost of -implementing functionality they do not need, or may not be able to implement functionality that is not supported by their underlying datastore. For example, Elasticsearch does not support the spatial predicates required by the Enhanced Spatial Operators conformance class. +implementing functionality they do not need, or may not be able to implement functionality that is not supported by +their underlying datastore. For example, Elasticsearch does not support the spatial predicates required by the +Enhanced Spatial Operators conformance class. + +It is likely that the OAFeat "Simple CQL" conformance class will be decomposed into several smaller conformance classes, +as described [here](https://github.com/opengeospatial/ogcapi-features/issues/579). Until the OAFeat CQL reaches final, it is +considered compliant within a STAC API to advertise the Simple CQL conformance class but only partially-implement it, e.g., to +only implement the logical and comparison operators. It is recommended +to provide an out-of-band way for users to discover what operators from the Simple CQL class are implemented. After OAFeat CQL is +finalized, implementations will be expected to provide correct conformance classes with respect to their implementation of CQL. -The Filter extension **requires** support of these three conformance classes: +An additional change that may be made to the Simple CQL conformance class is to only require support of expressions with a property +name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes to +will support arbitrary uses of properties and literals in expressions on either side. The primary motivation for this is to allow +implementations that use datastores that do not easily support right-hand side properties to implement Simple CQL +(e.g., Elasticsearch). Implementers should feel free to only implement `property operand literal` expressions at this time. + +The Filter extension **requires** these two conformance classes from OAFeat Part 3: - Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) - defines the Queryables mechanism and parameters `filter-lang`, filter-crs`, and `filter` -- Features Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) - defines that the parameters defined in `Filter` apply to the Features endpoint (`/collections/{collectionId}/items`) defined by OAFeat Part 1. -- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used for the `filter` parameter defined by Filter +- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used + for the `filter` parameter defined by Filter + +Additionally, it is **recommended** that the OAFeat Part 3 Features Filter conformance class +(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) be implemented, which "binds" the Filter and Simple CQL +conformance classes to the OAFeat Part 1 Features endpoint (`/collections/{collectionId}/items`). Note that POST with a JSON body +to the Features resource is not supported, as POST is used by the +[Transaction Extension](../../ogcapi-features/extensions/transaction/README.md) for creating items. -This STAC Filter extension extends the Filter conformance class such that these parameters also apply -to the STAC Item Search resource (/search). The OAFeat Filter conformance class already requires that these -parameters work for GET requests to the Items resource (/collections/collectionId/items). POST with a JSON body to the Items resource is not supported, as POST is used by the Transactions Extension for creating Item objects. +This STAC Filter extension operates similarly to OAFeat Part 3 Features Filter, in that it binds the OAFeat Filter and Simple CQL +conformance classes to the STAC Item Search resource (`/search`). -Additional, the implementation must support at least one of "CQL Text" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. It is recommended that clients use CQL Text in GET requests and CQL JSON in POST requests. +Additionally, the implementation must support at least one of "CQL Text" +(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" +(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). If both are advertised as being supported, it is only +required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. +It is recommended that clients use CQL Text in GET requests and CQL JSON in POST requests. The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance @@ -115,15 +149,10 @@ classes and their operations is both allowed and encouraged. Implementation of t operations supported by the implementation's datastore, for example, Elasticsearch does not support the spatial operations required by the Enhanced Spatial Operators. -There will likely be a change to Simple CQL where this conformance class only requires support of expressions with a property name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes will support arbitrary uses of properties and literals in expression. The primary motivation for this is to allow implementations that use datastores that do not easily support arbitrary expressions like these to implement Simple CQL (e.g., Elasticsearch). - -There will also likely be a change where the Simple CQL conformance class is decomposed into several other -conformance classes to aid composition. After these changes, it is possible that this extension will not require -the implementation of the operators IN, BETWEEN, LIKE, and IS NULL predicates. - ## Grammar and schemas -- The [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) includes an ABNF for cql-text and both JSON Schema and OpenAPI specifications for cql-json. The standalone files are: +- The [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) includes an ABNF for cql-text and both JSON Schema and + OpenAPI specifications for cql-json. The standalone files are: - [cql.bnf](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.bnf) - [cql.json](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.json) - [cql.yml](https://github.com/opengeospatial/ogcapi-features/blob/master/extensions/cql/standard/schema/cql.yml) @@ -134,32 +163,80 @@ the implementation of the operators IN, BETWEEN, LIKE, and IS NULL predicates. ## Queryables The Queryables mechanism allows a client to discover what variable terms are available for use when writing filter -expressions. These variables can be defined per-collection, and the intersection of these variables over all collections is what is available for filtering when there are no collection restrictions. These queryables are the only variables that may be used in filter expressions, and if any variable is used in expression that is not defined as a queryable, a 400 Bad Request exception should be returned. +expressions. These variables can be defined per-collection, and the intersection of these variables over all collections is what +is available for filtering when there are no collection restrictions. These queryables are the only variables that may be used +in filter expressions, and if any variable is used in expression that is not defined as a queryable and error should be +returned according to OAFeat Part 3. It is recognized that this is a severe restriction in STAC APIs that have highly variable +and dynamic content. It is possible that this will change in the OAFeat Part 3 spec, see +this [issue](https://github.com/opengeospatial/ogcapi-features/issues/582). For now, implementers may choose to allow +fully-qualified property +names not advertised in queryables (e.g., `properties.eo:cloud_cover`, in anticipation that there be some +mechanism to explicitly allow or advertise this in the future. + +Queryables are advertised via a JSON Schema document retrieved from the `/queryables` endpoint. An example that advertises +that only the `eo:cloud_cover` property may be used in filter expressions looks like: + +```json +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "$id" : "https://example.org/queryables", + "type" : "object", + "title" : "Queryables for Example STAC API", + "description" : "Queryable names for the example STAC API Item Search filter.", + "properties" : { + "eo:cloud_cover" : { + "description" : "Cloud Cover", + "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" + } + } +} +``` -Implementers should add queryables for all root Item fields (e.g., id, collection, geometry) with those names. Fields in Item Properties should also be exposed with their names, and not require expressions to prefix them with `properties`. **Everything else should be fully-qualified? How to do more deeply-nested queries and those on lists is an open question. How to disambiguate names that could appear in multiple places in an Item?** +Implementers should add queryables for all root Item fields (e.g., id, collection, geometry) with those names. Fields in Item +Properties should also be exposed with their names, and not require expressions to prefix them with `properties`. -(TBD: there is a proposal to allow finding what queryables are available for a subset of collections, e.g., `/queryables?collections=c1,c3`). +There may also be support for finding what queryables are available for a subset of collections, e.g., +`/queryables?collections=c1,c3`) as described in [this issue](https://github.com/opengeospatial/ogcapi-features/issues/576). -The Landing Page endpoint (`/`) will have a Link with rel `http://www.opengis.net/def/rel/ogc/1.0/queryables` with an href to the endpoint `/queryables` (TBD: hopefully, this will have a parameter `collections` defined on it to query for the intersection of the queryables in a group of collections). Additionally, each Collection resource will have a Link to the queryables resource for that collection, e.g., `/collections/collection1/queryables`. +The Landing Page endpoint (`/`) will have a Link with rel `http://www.opengis.net/def/rel/ogc/1.0/queryables` with an href to +the endpoint `/queryables`. Additionally, each Collection resource will have a Link to the queryables resource for that +collection, e.g., `/collections/collection1/queryables`. -The queryables endpoint returns a JSON Schema describing the names and types variables that may be used in filter expressions. This response is defined in JSON Schema because it is a well-specified typed schema, but it is not used for validating a JSON document derived from it. This schema defines the variables that may be used in a CQL filter. +The queryables endpoint returns a JSON Schema describing the names and types variables that may be used in filter expressions. +This response is defined in JSON Schema because it is a well-specified typed schema, but it is not used for validating a JSON +document derived from it. This schema defines the variables that may be used in a CQL filter. -These queryable variables are mapped by the service to filter Items. For example, the service may define a queryable with the name "cloud_cover" that can be used in a CQL expression like `cloud_cover <= 10`, with the semantics that only Items where the `properties.eo:cloud_cover` value was <= 10 would match the filter. The server would then translate this into an appropriate query for the data within its datastore. +These queryable variables are mapped by the service to filter Items. For example, the service may define a queryable with the +name "cloud_cover" that can be used in a CQL expression like `cloud_cover <= 10`, with the semantics that only Items where the +`properties.eo:cloud_cover` value was <= 10 would match the filter. The server would then translate this into an appropriate +query for the data within its datastore. -Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group of options for the values of these variables. +Queryables can be static or dynamically derived. For example, `cloud_cover` might be specified to have a value 0 to 100 or a field +may have a set of enumerated values dynamically determined by an aggreation at runtime. This schema can be used by a UI or +interactive client to dynamically expose to the user the fields that are available for filtering, and provide a precise group +of options for the values of these variables. -Queryables can also be used to advertise synthesized property values. The only requirement in CQL is that the property have a type and evaluate to literal value of that type or NULL. For example, a filter like "Items must have an Asset with an eo:band with the common_name of 'nir'" can be expressed. A Queryable `assets_bands` could be defined to have a type of array of string and have the semantics that it contains all of `common_name` values across all assets and bands for an Item. This could then be filtered with the CQL expression `'nir' in assets_bands`. Implementations would then expand this expression into the appropriate query against its datastore. (TBD if this will actually work or not. This is also related to the upcoming restriction on property/literal comparisons) +Queryables can also be used to advertise synthesized property values. The only requirement in CQL is that the property have a type +and evaluate to literal value of that type or NULL. For example, a filter like "Items must have an Asset with an eo:band with +the common_name of 'nir'" can be expressed. A Queryable `assets_bands` could be defined to have a type of array of string and +have the semantics that it contains all of `common_name` values across all assets and bands for an Item. This could then be +filtered with the CQL expression `'nir' in assets_bands`. Implementations would then expand this expression into the +appropriate query against its datastore. (TBD if this will actually work or not. This is also related to the upcoming +restriction on property/literal comparisons) ## GET Query Parameters and POST JSON fields This extension adds three GET query parameters or POST JSON fields to an Item Search request: - filter-lang:`cql-text` or `cql-json`. If undefined, defaults to `cql-text` for a GET request and `cql-json` for a POST request. -- filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as a valid value, may reject any others +- filter-crs: recommended to not be passed, but server must only accept `http://www.opengis.net/def/crs/OGC/1.3/CRS84` as + a valid value, may reject any others - filter: CQL filter expression API implementations advertise which `filter-lang` values are supported via conformance classes in the Landing Page. -At least one must be implemented, but it is recommended to implement both. If both are advertised as conformance classes, the server should process either for a GET request, but may only process cql-json for a POST request. If POST of cql-text is not supported, the server must return a 400 error if `filter-lang=cql-text`. +At least one must be implemented, but it is recommended to implement both. If both are advertised as conformance classes, the +server should process either for a GET request, but may only process cql-json for a POST request. If POST of cql-text is not +supported, the server must return a 400 error if `filter-lang=cql-text`. ## Interaction with Endpoints @@ -252,9 +329,15 @@ Alternately, the client could retrieve the queryables for a single collection wi `/collections/collections1/queryables`, which may have more queryables than available for the entire catalog, since there may be queryables that are only relevant to that collection. -Notice in this schema that instead of directly defining the type information about each field, we have instead used the JSON Schema `$ref` mechanism to refer to a STAC schema definition. This not only allows the reuse of these JSON Schema definitions, but also binds an arbitrarily-named Queryable to a specific STAC field. For example, in the above we know that the `eo:cloud_cover` field is referring to the field of the same name in the EO Extension not because they happen to have the same name, but rather because the `$ref` indicates it. The field could just as well be named "cloud_cover", "CloudCover", or "cc", and we would still know it filtered on the EO extension `eo:cloud_cover` field. +Notice in this schema that instead of directly defining the type information about each field, we have instead used the JSON +Schema `$ref` mechanism to refer to a STAC schema definition. This not only allows the reuse of these JSON Schema definitions, +but also binds an arbitrarily-named Queryable to a specific STAC field. For example, in the above we know that the +`eo:cloud_cover` field is referring to the field of the same name in the EO Extension not because they happen to have the same +name, but rather because the `$ref` indicates it. The field could just as well be named "cloud_cover", "CloudCover", or "cc", +and we would still know it filtered on the EO extension `eo:cloud_cover` field. -While these do seem quite complex to write and understand, keep in mind that query construction will likely be done with a more ergonomic SDK, and query parsing will be done with the help of a ABNF grammar and OpenAPI schema. +While these do seem quite complex to write and understand, keep in mind that query construction will likely be done with a +more ergonomic SDK, and query parsing will be done with the help of a ABNF grammar and OpenAPI schema. From the Queryables above, a client could then form the following example filter expressions. @@ -264,27 +347,30 @@ These parameters/fields must be supported by the Item Search endpoint and Items Note: the GET examples with query parameters are unescaped to make them easier to read. +The parameter `filter-crs` always defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` for a STAC API, so is not shown +in any of these examples. + ### Example 1 This example uses the queryables definition in (Interaction with Endpoints)(#interaction-with-endpoints). #### Example 1: GET with cql-text -Note that `filter-lang` defaults to `cql-text` in this case, so this is only shown for completeness. +Note that `filter-lang` defaults to `cql-text` in this case. The parameter `filter-crs` defaults +to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` for a STAC API. ```http -GET /search?filter-lang=cql-text&filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' +GET /search?filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' ``` #### Example 1: POST with cql-json -Note that `filter-lang` defaults to `cql-json` and `filter-crs` defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` in this case. +Note that `filter-lang` defaults to `cql-json` in this case. The parameter `filter-crs` defaults +to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` for a STAC API. ```http POST /search { - "filter-lang"="cql-json", - "filter-crs":"http://www.opengis.net/def/crs/OGC/1.3/CRS84", "filter": { "and": [ "eq": [ @@ -446,7 +532,8 @@ The queryables defined are as follows: } ``` -Note that `sentinel:data_coverage` is a proprietary extension (e.g., not defined in a formal, public way), and must be defined directly within the schema. +Note that `sentinel:data_coverage` is a proprietary extension (e.g., not defined in a formal, public way), and must +be defined directly within the schema. A sample STAC Item (excluding `assets`) is: @@ -556,9 +643,9 @@ This uses the same queryables as Example 4. "lt": [ { "property": "eo:cloud_cover" }, 10 - } ] - ] + } + ] } } ``` @@ -623,8 +710,9 @@ GeoJSON geometries. ## Implementations -* [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi -* [Franklin](https://github.com/azavea/franklin) is working on it. +- [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the + [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi +- [Franklin](https://github.com/azavea/franklin) is working on it. Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. diff --git a/implementation.md b/implementation.md index dc53fe7a..dd81ebcf 100644 --- a/implementation.md +++ b/implementation.md @@ -16,8 +16,12 @@ access-control-allow-methods: OPTIONS, POST, GET access-control-allow-headers: Content-Type ``` -It is relatively safe to use these headers for all endpoints. However, one may want to restrict the methods to only those that apply to each endpoint. For example, the `/collection/{collectionId}/items` endpoint should only allow OPTIONS and GET, since POST is only used by the Transactions Extension, which presumably would require authentication as it is mutating data. +It is relatively safe to use these headers for all endpoints. However, one may want to restrict the methods to +only those that apply to each endpoint. For example, the `/collection/{collectionId}/items` endpoint should +only allow OPTIONS and GET, since POST is only used by the Transactions Extension, which presumably would +require authentication as it is mutating data. Implementations that support the Transactions Extension or require credentials for some operations will need to -implement different behavior, for example, allowing credentials when requests are coming from a trusted domain, allowing DELETE, PUT, or PATCH methods, or +implement different behavior, for example, allowing credentials when requests are coming from a trusted domain, +allowing DELETE, PUT, or PATCH methods, or permitting the `If-Match` request header. diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 54b5f254..89c69cbf 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -112,7 +112,6 @@ most APIs will also implement other conformance classes, and those will be refle `conformsTo` fields. A more typical Landing Page example is in the [overview](../overview.md#example-landing-page) document. - ```json { "stac_version": "1.0.0-beta.2", @@ -159,4 +158,4 @@ the [overview](../overview.md#example-landing-page) document. } ] } -``` \ No newline at end of file +``` diff --git a/overview.md b/overview.md index cda38b75..eaee28f9 100644 --- a/overview.md +++ b/overview.md @@ -131,7 +131,8 @@ Additional conformance classes are specified in the [STAC Extensions](extensions ## Example Landing Page When all three conformance classes (Core, Features, Item Search) are implemented, the relationships among -various resources are shown in the following diagram. In each node, there is also a `self` and `root` link that are not depicted to keep the diagram more concise. +various resources are shown in the following diagram. In each node, there is also a `self` and `root` link +that are not depicted to keep the diagram more concise. ![Diagram of STAC link relations](stac-api.png) From 9f9a3dd8b86cc7fe0ece71d8bfea8491c9c36911 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 19 May 2021 12:19:58 -0400 Subject: [PATCH 44/61] fix formatting --- fragments/filter/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index cdb7cf02..1b1dbaec 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -124,7 +124,7 @@ implementations that use datastores that do not easily support right-hand side p The Filter extension **requires** these two conformance classes from OAFeat Part 3: - Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) - defines the Queryables mechanism and - parameters `filter-lang`, filter-crs`, and `filter` + parameters `filter-lang`, `filter-crs`, and `filter` - Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used for the `filter` parameter defined by Filter @@ -405,7 +405,7 @@ GET /search?filter=collection = 'landsat8_l1tp' ```http POST /search { - "filter-lang"="cql-json", + "filter-lang": "cql-json", "filter": { "and": [ "eq": [ @@ -485,7 +485,7 @@ GET /search?filter=prop1 = prop2 ```http POST /search { - "filter-lang"="cql-json", + "filter-lang": "cql-json", "filter": { "eq": [ { "property": "prop1" }, @@ -498,7 +498,7 @@ POST /search ### Example 4 We'll be imagining these as queries against [EarthSearch Sentinel 2 -COG](https://stacindex.org/catalogs/earth-search#/Cnz1sryATwWudkxyZekxWx6356v9RmvvCcLLw79uHWJUDvt2?t=items)' data. +COG](https://stacindex.org/catalogs/earth-search#/Cnz1sryATwWudkxyZekxWx6356v9RmvvCcLLw79uHWJUDvt2?t=items) data. The queryables defined are as follows: @@ -712,7 +712,8 @@ GeoJSON geometries. - [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi -- [Franklin](https://github.com/azavea/franklin) is working on it. +- [Franklin](https://github.com/azavea/franklin) is working on it in [this PR](https://github.com/azavea/franklin/pull/750). +- [Geotools](https://github.com/geotools/geotools) has support for [CQL text](https://github.com/geotools/geotools/tree/main/modules/library/cql/src/main/java/org/geotools/filter/text/cql2) Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. From 7265eb8313f3e4029f63cf6fc83f0498b6d90b46 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 19 May 2021 21:10:15 -0400 Subject: [PATCH 45/61] redefine conformance classes --- fragments/filter/README.md | 86 ++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 1b1dbaec..00fd3c27 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -1,6 +1,12 @@ # STAC API - Filter Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Conformance Classes:** + - Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter`) + - Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql`) + - Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/item-search-filter`) + - CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text`) + - CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json`) - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) @@ -9,7 +15,7 @@ - [Overview](#overview) - [Limitations of Item Search](#limitations-of-item-search) - [Filter expressiveness](#filter-expressiveness) - - [OAFeat Part 3 Conformance Classes](#oafeat-part-3-conformance-classes) + - [Conformance Classes](#conformance-classes) - [Grammar and schemas](#grammar-and-schemas) - [Queryables](#queryables) - [GET Query Parameters and POST JSON fields](#get-query-parameters-and-post-json-fields) @@ -100,55 +106,65 @@ CQL enables these types of queries: This extension also supports the Queryables mechanism that allows discovery of what Item fields can be used in predicates. -## OAFeat Part 3 Conformance Classes +## Conformance Classes OAFeat CQL defines several conformance classes that allow implementers to create compositions of functionality that support whatever expressiveness they need. Implementers many choose not to incur the cost of implementing functionality they do not need, or may not be able to implement functionality that is not supported by -their underlying datastore. For example, Elasticsearch does not support the spatial predicates required by the +their underlying datastore, e.g., Elasticsearch does not support the spatial predicates required by the Enhanced Spatial Operators conformance class. -It is likely that the OAFeat "Simple CQL" conformance class will be decomposed into several smaller conformance classes, -as described [here](https://github.com/opengeospatial/ogcapi-features/issues/579). Until the OAFeat CQL reaches final, it is -considered compliant within a STAC API to advertise the Simple CQL conformance class but only partially-implement it, e.g., to -only implement the logical and comparison operators. It is recommended -to provide an out-of-band way for users to discover what operators from the Simple CQL class are implemented. After OAFeat CQL is -finalized, implementations will be expected to provide correct conformance classes with respect to their implementation of CQL. +The STAC API Filter Extension reuses the definitions of several conformance classes defined in OAFeat CQL, but with a prefix of +`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/` instead of `https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/`. -An additional change that may be made to the Simple CQL conformance class is to only require support of expressions with a property -name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes to -will support arbitrary uses of properties and literals in expressions on either side. The primary motivation for this is to allow -implementations that use datastores that do not easily support right-hand side properties to implement Simple CQL -(e.g., Elasticsearch). Implementers should feel free to only implement `property operand literal` expressions at this time. +The implementation must support both of these conformance classes: -The Filter extension **requires** these two conformance classes from OAFeat Part 3: +- Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter`) defines the Queryables mechanism and + parameters `filter-lang`, `filter-crs`, and `filter`. +- Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql`) defines the query language used + for the `filter` parameter defined by Filter. -- Filter (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter`) - defines the Queryables mechanism and - parameters `filter-lang`, `filter-crs`, and `filter` -- Simple CQL (`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql`) - defines the query language used - for the `filter` parameter defined by Filter +In place of the OAFeat CQL Features Filter class that binds Filter and Simple CQL to the Features resource +(`/collections/{cid}/items`), the Filter Extension instead defines this conformance class: -Additionally, it is **recommended** that the OAFeat Part 3 Features Filter conformance class -(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter`) be implemented, which "binds" the Filter and Simple CQL -conformance classes to the OAFeat Part 1 Features endpoint (`/collections/{collectionId}/items`). Note that POST with a JSON body -to the Features resource is not supported, as POST is used by the -[Transaction Extension](../../ogcapi-features/extensions/transaction/README.md) for creating items. +- Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/item-search-filter`) binds the Filter and Simple CQL + conformance classes to apply to the Item Search endpoint (`/search`). + +The implementation must support at least one of the "CQL Text" or "CQL JSON" conformance classes that define the CQL format +used in the filter parameter: -This STAC Filter extension operates similarly to OAFeat Part 3 Features Filter, in that it binds the OAFeat Filter and Simple CQL -conformance classes to the STAC Item Search resource (`/search`). +- CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text`) defines that the CQL Text format is supported by Item Search. +- CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json`) defines that the CQL JSON format is supported by Item Search -Additionally, the implementation must support at least one of "CQL Text" -(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text`) or "CQL JSON" -(`http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json`). If both are advertised as being supported, it is only +If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. It is recommended that clients use CQL Text in GET requests and CQL JSON in POST requests. -The Filter extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, +The Filter Extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance classes and their operations is both allowed and encouraged. Implementation of these is often limited by the operations supported by the implementation's datastore, for example, Elasticsearch does not support the spatial operations required by the Enhanced Spatial Operators. +Additionally, if an API implements the OGC API Features endpoint, it is **recommended** that the OAFeat Part 3 Filter, +Features Filter, and Simple CQL conformance classes be implemented, which allow use of CQL filters against the +OAFeat Part 1 Features endpoint (`/collections/{collectionId}/items`). Note that POST with a JSON body +to the Features resource is not supported, as POST is used by the +[Transaction Extension](../../ogcapi-features/extensions/transaction/README.md) for creating items. + +It is likely that the OAFeat "Simple CQL" conformance class will be decomposed into several smaller conformance classes, +as described [here](https://github.com/opengeospatial/ogcapi-features/issues/579). Until the OAFeat CQL reaches final, it is +considered compliant within a STAC API to advertise the Simple CQL conformance class but only partially-implement it, e.g., to +only implement the logical and comparison operators. It is recommended +to provide an out-of-band way for users to discover what operators from the Simple CQL class are implemented. After OAFeat CQL is +finalized, implementations will be expected to provide correct conformance classes with respect to their implementation of CQL. + +An additional change that may be made to the Simple CQL conformance class is to only require support of expressions with a property +name of the left hand side and a literal on the right hand side (e.g., `eo:cloud_cover <= 10`), and additional conformance classes to +will support arbitrary uses of properties and literals in expressions on either side. The primary motivation for this is to allow +implementations that use datastores that do not easily support right-hand side properties to implement Simple CQL +(e.g., Elasticsearch). Implementers should feel free to only implement `property operand literal` expressions at this time. + ## Grammar and schemas - The [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) includes an ABNF for cql-text and both JSON Schema and @@ -252,11 +268,11 @@ Landing Page (`/`) returns: "http://www.opengis.net/spec/ogcapi_common-2/1.0/req/collections", - "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/filter", - "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/features-filter", - "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/simple-cql", - "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-text", - "http://www.opengis.net/spec/ogcapi-features-3/1.0/req/cql-json", + "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter", + "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/features-filter", + "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql", + "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text", + "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json", "http://stacspec.org/spec/api/1.0.0-beta.1/core", "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-search", From 820a18ecec18fcbcd8869551900d784baf51b690 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 19 May 2021 21:24:29 -0400 Subject: [PATCH 46/61] change conformance class names back to item-search# --- fragments/filter/README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 00fd3c27..0ff1cc3d 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -2,11 +2,11 @@ - **OpenAPI specification:** [openapi.yaml](openapi.yaml) - **Conformance Classes:** - - Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter`) - - Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql`) - - Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/item-search-filter`) - - CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text`) - - CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json`) + - Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter`) + - Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql`) + - Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:item-search-filter`) + - CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text`) + - CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json`) - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) @@ -115,26 +115,26 @@ their underlying datastore, e.g., Elasticsearch does not support the spatial pre Enhanced Spatial Operators conformance class. The STAC API Filter Extension reuses the definitions of several conformance classes defined in OAFeat CQL, but with a prefix of -`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/` instead of `https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/`. +`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:` instead of `http://www.opengis.net/spec/ogcapi-features-3/1.0/req/`. The implementation must support both of these conformance classes: -- Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter`) defines the Queryables mechanism and +- Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter`) defines the Queryables mechanism and parameters `filter-lang`, `filter-crs`, and `filter`. -- Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql`) defines the query language used +- Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql`) defines the query language used for the `filter` parameter defined by Filter. In place of the OAFeat CQL Features Filter class that binds Filter and Simple CQL to the Features resource (`/collections/{cid}/items`), the Filter Extension instead defines this conformance class: -- Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/item-search-filter`) binds the Filter and Simple CQL +- Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:item-search-filter`) binds the Filter and Simple CQL conformance classes to apply to the Item Search endpoint (`/search`). The implementation must support at least one of the "CQL Text" or "CQL JSON" conformance classes that define the CQL format used in the filter parameter: -- CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text`) defines that the CQL Text format is supported by Item Search. -- CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json`) defines that the CQL JSON format is supported by Item Search +- CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text`) defines that the CQL Text format is supported by Item Search. +- CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json`) defines that the CQL JSON format is supported by Item Search If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. @@ -268,11 +268,11 @@ Landing Page (`/`) returns: "http://www.opengis.net/spec/ogcapi_common-2/1.0/req/collections", - "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/filter", - "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/features-filter", - "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/simple-cql", - "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-text", - "https://api.stacspec.org/v1.0.0-beta.1/extensions/filter/cql-json", + "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter", + "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:features-filter", + "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql", + "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text", + "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json", "http://stacspec.org/spec/api/1.0.0-beta.1/core", "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-search", From c201898117ae726d0486935783c89340c30c1477 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 00:27:39 -0400 Subject: [PATCH 47/61] tweak language around proprietary extensions in queryables, add note about cross-collection use of 'collection' in filter --- fragments/filter/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 0ff1cc3d..bfc4e8bb 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -404,7 +404,10 @@ POST /search ### Example 2 -This example uses the queryables definition in (Interaction with Endpoints)(#interaction-with-endpoints). +This example uses the queryables definition in [Interaction with Endpoints](#interaction-with-endpoints). + +Note that filtering on the `collection` field is relevant in Item Search, since the queries are cross-collection, whereas +OGC API Features filters only operate against a single collection already. #### Example 2: GET with cql-text @@ -538,18 +541,24 @@ The queryables defined are as follows: "description" : "Cloud Cover", "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" }, - "sentinel:data_coverage" : { - "description" : "Sentinel Data Coverage", + "acme:data_coverage" : { + "description" : "Acme Sat Data Coverage", "type": "integer", "minimum": 0, "maximum": 100 + }, + "acme:grid_id" : { + "description" : "Acme Sat Grid ID", + "type": "string" } } } ``` -Note that `sentinel:data_coverage` is a proprietary extension (e.g., not defined in a formal, public way), and must -be defined directly within the schema. +Note that `acme:data_coverage` and `acme:grid_id` are properties that are not defined in an extension schema, and are intended to +represent vendor-specific properties. Because of this, they are fully specified directly in the JSON Schema. However, it is +recommended that vendor-specific properties be published as part of a well-defined extension schema, so these only represent ones +that have not followed that recommendation. A sample STAC Item (excluding `assets`) is: From 96696c4d49d2be0233f136f53fd03c4faeba156d Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 00:34:59 -0400 Subject: [PATCH 48/61] update list of conformance class uris in extension page --- extensions.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/extensions.md b/extensions.md index 1b9ff87a..efe5276a 100644 --- a/extensions.md +++ b/extensions.md @@ -65,15 +65,16 @@ This is the list of all extensions that are contained in the stac-api-spec repos Each extension has its own conformance URI, which is used in the `conformsTo` response of the landing page to let clients know what capabilities the service supports. This are listed at the top of each extension description, but the full table is given here for ease of reference. -| Extension Name | Conformance URI | -|-----------------------------------------------------------------------------------|----------------------------------------------------------------------------| -| [Fields](item-search/README.md#fields) | | -| [Filter](item-search/README.md#filter) | | -| [Context](item-search/README.md#context) | | -| [Sort](item-search/README.md#sort) | | -| [Transaction](ogcapi-features/extensions/transaction/README.md) | | -| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | | -| [Query](item-search/README.md#query) | | +| Extension Name | Conformance Class URIs | +|---------------|-------------------------| +| [Fields](item-search/README.md#fields) | | +| [Filter](item-search/README.md#filter) |



| +| [Context](item-search/README.md#context) | | +| [Sort](item-search/README.md#sort) | | +| [Transaction](ogcapi-features/extensions/transaction/README.md) | | +| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | | +| [Query](item-search/README.md#query) | | + ## Third-party / vendor extensions From 90c88da3c6d14c7532fca9f4d8e39dd0456fbc9e Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 00:40:44 -0400 Subject: [PATCH 49/61] add conformance class field to each extension --- fragments/context/README.md | 1 + fragments/fields/README.md | 1 + fragments/query/README.md | 1 + fragments/sort/README.md | 1 + 4 files changed, 4 insertions(+) diff --git a/fragments/context/README.md b/fragments/context/README.md index 22c9cf6a..189617e4 100644 --- a/fragments/context/README.md +++ b/fragments/context/README.md @@ -1,6 +1,7 @@ # STAC API - Context Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) diff --git a/fragments/fields/README.md b/fragments/fields/README.md index a3d54ccc..52087fa1 100644 --- a/fragments/fields/README.md +++ b/fragments/fields/README.md @@ -1,6 +1,7 @@ # STAC API - Fields Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) diff --git a/fragments/query/README.md b/fragments/query/README.md index 466b4a86..4a7bcacf 100644 --- a/fragments/query/README.md +++ b/fragments/query/README.md @@ -1,6 +1,7 @@ # STAC API - Query Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Conformance Class:** - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Deprecated in favor of the [Filter Extension](../filter/README.md) using [CQL](http://docs.opengeospatial.org/DRAFTS/19-079.html). - **Dependents:** diff --git a/fragments/sort/README.md b/fragments/sort/README.md index 8eaf5e5c..6fd1c948 100644 --- a/fragments/sort/README.md +++ b/fragments/sort/README.md @@ -1,6 +1,7 @@ # STAC API - Sort Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) From 61a1f363f90f7ff5f76b8fa5d82f8779421dc429 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 00:43:36 -0400 Subject: [PATCH 50/61] fix formatting of filter conformance classes --- fragments/filter/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index bfc4e8bb..ef6b5583 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -2,11 +2,11 @@ - **OpenAPI specification:** [openapi.yaml](openapi.yaml) - **Conformance Classes:** - - Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter`) - - Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql`) - - Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:item-search-filter`) - - CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text`) - - CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json`) + - Filter: + - Simple CQL: + - Item Search Filter: + - CQL Text: + - CQL JSON: - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) From 1fb71cb1eb199168baae8bba2b5bc24865e01df9 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 00:45:22 -0400 Subject: [PATCH 51/61] fix formatting typo --- fragments/filter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index ef6b5583..35933ba8 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -91,7 +91,7 @@ This extension expands the capabilities of Item Search and the OAFeat Items reso [OAFeat Part 3 CQL](https://portal.ogc.org/files/96288) by providing an expressive query language to construct more complex filter predicates. The operators are similar to those provided by SQL. The Simple CQL conformance class requires the logical operators `AND`, `OR`, and `NOT`; -the comparison operators '=', `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, and `IN`; the spatial operator +the comparison operators `=`, `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, and `IN`; the spatial operator `INTERSECTS`; and the temporal operator `ANYINTERACTS`. The ANYINTERACTS operator has effectively the same semantics as the existing `datetime` filter. From 9acf92d395778bc08ee89bea59391d8ff02daaa3 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 01:14:42 -0400 Subject: [PATCH 52/61] clarify queryables --- fragments/filter/README.md | 77 ++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 35933ba8..0c75fc9d 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -7,6 +7,11 @@ - Item Search Filter: - CQL Text: - CQL JSON: + - Enhanced Spatial Operators: + - Enhanced Temporal Operators: + - Functions: + - Arithmetic: + - Arrays: - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) @@ -117,18 +122,15 @@ Enhanced Spatial Operators conformance class. The STAC API Filter Extension reuses the definitions of several conformance classes defined in OAFeat CQL, but with a prefix of `https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:` instead of `http://www.opengis.net/spec/ogcapi-features-3/1.0/req/`. -The implementation must support both of these conformance classes: +The implementation must support these conformance classes: - Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter`) defines the Queryables mechanism and parameters `filter-lang`, `filter-crs`, and `filter`. - Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql`) defines the query language used for the `filter` parameter defined by Filter. - -In place of the OAFeat CQL Features Filter class that binds Filter and Simple CQL to the Features resource -(`/collections/{cid}/items`), the Filter Extension instead defines this conformance class: - - Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:item-search-filter`) binds the Filter and Simple CQL - conformance classes to apply to the Item Search endpoint (`/search`). + conformance classes to apply to the Item Search endpoint (`/search`). This class is the correlate of the OAFeat CQL Features + Filter class that binds Filter and Simple CQL to the Features resource (`/collections/{cid}/items`). The implementation must support at least one of the "CQL Text" or "CQL JSON" conformance classes that define the CQL format used in the filter parameter: @@ -140,11 +142,12 @@ If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. It is recommended that clients use CQL Text in GET requests and CQL JSON in POST requests. -The Filter Extension does **not** require support for the Enhanced Spatial Operators, Enhanced Temporal Operators, -Functions, Arithmetic Expressions, or Arrays conformance classes, but implementing these additional conformance -classes and their operations is both allowed and encouraged. Implementation of these is often limited by the +The Filter Extension defines support for the Enhanced Spatial Operators, Enhanced Temporal Operators, +Functions, Arithmetic Expressions, or Arrays conformance classes defined in OAFeat CQL. Implementing these conformance +classes and their operations is encouraged but not required. Implementation of these is often limited by the operations supported by the implementation's datastore, for example, Elasticsearch does not support the spatial -operations required by the Enhanced Spatial Operators. +operations required by the Enhanced Spatial Operators. If implemented for Item Search, the conformance class URI should follow +the same pattern relative to OAFeat CQL. Additionally, if an API implements the OGC API Features endpoint, it is **recommended** that the OAFeat Part 3 Filter, Features Filter, and Simple CQL conformance classes be implemented, which allow use of CQL filters against the @@ -189,8 +192,8 @@ fully-qualified property names not advertised in queryables (e.g., `properties.eo:cloud_cover`, in anticipation that there be some mechanism to explicitly allow or advertise this in the future. -Queryables are advertised via a JSON Schema document retrieved from the `/queryables` endpoint. An example that advertises -that only the `eo:cloud_cover` property may be used in filter expressions looks like: +Queryables are advertised via a JSON Schema document retrieved from the `/queryables` endpoint. A basic Queryables +definitions for STAC Items should include at least the fields id, collection, geometry, and datetime. ```json { @@ -200,16 +203,28 @@ that only the `eo:cloud_cover` property may be used in filter expressions looks "title" : "Queryables for Example STAC API", "description" : "Queryable names for the example STAC API Item Search filter.", "properties" : { - "eo:cloud_cover" : { - "description" : "Cloud Cover", - "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/properties/eo:cloud_cover" + "id" : { + "description" : "ID", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id" + }, + "collection" : { + "description" : "Collection", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection" + }, + "geometry" : { + "description" : "Geometry", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry" + }, + "datetime" : { + "description" : "Datetime", + "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime" } } } ``` -Implementers should add queryables for all root Item fields (e.g., id, collection, geometry) with those names. Fields in Item -Properties should also be exposed with their names, and not require expressions to prefix them with `properties`. +Fields in Item Properties should be exposed with their un-prefixed names, and not require expressions to prefix them +with `properties`, e.g., `eo:cloud_cover` instead of `properties.eo:cloud_cover`. There may also be support for finding what queryables are available for a subset of collections, e.g., `/queryables?collections=c1,c3`) as described in [this issue](https://github.com/opengeospatial/ogcapi-features/issues/576). @@ -223,7 +238,7 @@ This response is defined in JSON Schema because it is a well-specified typed sch document derived from it. This schema defines the variables that may be used in a CQL filter. These queryable variables are mapped by the service to filter Items. For example, the service may define a queryable with the -name "cloud_cover" that can be used in a CQL expression like `cloud_cover <= 10`, with the semantics that only Items where the +name "eo:cloud_cover" that can be used in a CQL expression like `eo:cloud_cover <= 10`, with the semantics that only Items where the `properties.eo:cloud_cover` value was <= 10 would match the filter. The server would then translate this into an appropriate query for the data within its datastore. @@ -240,6 +255,22 @@ filtered with the CQL expression `'nir' in assets_bands`. Implementations would appropriate query against its datastore. (TBD if this will actually work or not. This is also related to the upcoming restriction on property/literal comparisons) +An implementation may also choose not to advertise any queryables, and provide the user with out-of-band information or +simply let them try querying against fields. While this is not allowed according to the OGC CQL Queryable spec, it is allowed +in STAC API by the Filter Extension. In this case, the queryables endpoint (`/queryables`) would return this document: + +```json +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "$id" : "https://example.org/queryables", + "type" : "object", + "title" : "Queryables for Example STAC API", + "description" : "Queryable names for the example STAC API Item Search filter.", + "properties" : { + } +} +``` + ## GET Query Parameters and POST JSON fields This extension adds three GET query parameters or POST JSON fields to an Item Search request: @@ -256,7 +287,7 @@ supported, the server must return a 400 error if `filter-lang=cql-text`. ## Interaction with Endpoints -Landing Page (`/`) returns: +In an implementation that supports Simple CQL, the Landing Page (`/`) should return a document including at least these values: ```json { @@ -297,9 +328,9 @@ Landing Page (`/`) returns: } ``` -Client can use the link with `"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables"` to retrieve the queryables. +A client can use the link with `"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables"` to retrieve the queryables. -Queryables endpoint (`/queryables`) returns: +The Queryables endpoint (`/queryables`) returns something like the following: ```json { @@ -355,9 +386,7 @@ and we would still know it filtered on the EO extension `eo:cloud_cover` field. While these do seem quite complex to write and understand, keep in mind that query construction will likely be done with a more ergonomic SDK, and query parsing will be done with the help of a ABNF grammar and OpenAPI schema. -From the Queryables above, a client could then form the following example filter expressions. - -These parameters/fields must be supported by the Item Search endpoint and Items resource (`/collections/$collectionId/items`). +These parameters/fields must be supported by the STAC Item Search endpoint and OAFeat Features resource (`/collections/$collectionId/items`). ## Examples From b9b7c833996dc81fbfc5f2dd1519f13a545da946 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Thu, 20 May 2021 09:15:19 -0400 Subject: [PATCH 53/61] add a getting started section --- fragments/filter/README.md | 59 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 0c75fc9d..b2a84a5c 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -21,7 +21,7 @@ - [Limitations of Item Search](#limitations-of-item-search) - [Filter expressiveness](#filter-expressiveness) - [Conformance Classes](#conformance-classes) - - [Grammar and schemas](#grammar-and-schemas) + - [Getting Started with Implementation](#getting-started-with-implementation) - [Queryables](#queryables) - [GET Query Parameters and POST JSON fields](#get-query-parameters-and-post-json-fields) - [Interaction with Endpoints](#interaction-with-endpoints) @@ -47,25 +47,22 @@ - [Example 6: Spatial](#example-6-spatial) - [Example 6: INTERSECTS cql-text (GET)](#example-6-intersects-cql-text-get) - [Example 6: INTERSECTS cql-json (POST)](#example-6-intersects-cql-json-post) - - [Implementations](#implementations) ## Overview The Filter extension provides an expressive mechanism for searching based on Item attributes. -This extension uses several conformance classes defined in the +This extension references behavior defined in the [OGC API - Features - Part 3: Filtering and the Common Query Language (CQL)](https://portal.ogc.org/files/96288) specification. As of May 2020, this specification is in draft status. The only anticipated change before final is to the division of behavior among conformance classes, as described further -in [OAFeat Part 3 Conformance Classes](#oafeat-part-3-conformance-classes). +in the [Conformance Classes](#conformance-classes) section. OAFeat Part 3 CQL formally defines syntax for both a text format (cql-text) as an ABNF grammar (largely similar to the BNF grammar -in the General Model) and a JSON format (cql-json) as an OpenAPI schema, and provides a precise natural language description of -the declarative semantics. -The CQL Text format has long-standing use within +in the General Model) and a JSON format (cql-json) as a JSON Schema and OpenAPI schema, and provides a precise natural +language description of the declarative semantics. The CQL Text format has long-standing use within geospatial software (e.g., GeoServer), is expected not to change before final. -OGC CQL Text has been previously described -in [OGC Filter Encoding](https://www.ogc.org/standards/filter) and +OGC CQL Text has been previously described in [OGC Filter Encoding](https://www.ogc.org/standards/filter) and [OGC Catalogue Services 3.0 - General Model](http://docs.opengeospatial.org/is/12-168r6/12-168r6.html#62) (including a BNF grammar in Annex B). The CQL JSON format is newly-defined, but also not expected to change before final. @@ -114,8 +111,9 @@ predicates. ## Conformance Classes OAFeat CQL defines several conformance classes that allow implementers to create compositions of -functionality that support whatever expressiveness they need. Implementers many choose not to incur the cost of -implementing functionality they do not need, or may not be able to implement functionality that is not supported by +functionality that support whatever expressiveness they need. This allows implementers to incrementally support CQL +syntax, without needing to implement a huge spec all at once. Some implementers choose not to incur the cost of +implementing functionality they do not need or may not be able to implement functionality that is not supported by their underlying datastore, e.g., Elasticsearch does not support the spatial predicates required by the Enhanced Spatial Operators conformance class. @@ -168,7 +166,20 @@ will support arbitrary uses of properties and literals in expressions on either implementations that use datastores that do not easily support right-hand side properties to implement Simple CQL (e.g., Elasticsearch). Implementers should feel free to only implement `property operand literal` expressions at this time. -## Grammar and schemas +## Getting Started with Implementation + +It recommended that implementers start with fully implementing only a subset of functionality. As stated previously, +until the OAFeat CQL spec is finalized, it is legal in a STAC API implementation to advertise that the Simple CQL conformance +class is implemented, but that only a subset of that functionality is implemented. + +A good place to start is +implementing only the logical and simple comparison operators (`=`, `<`, `<=`, `>`, `>=`), defining a static Queryables +schema with no queryables advertised, and only implementing CQL Text. Following from that can be support for +INTERSECTS and ANYINTERACTS, defining a static Queryables schema with only the basic Item properties, and also +implementing CQL JSON. From there, other comparison operators can be implemented and a more +dynamic Queryables schema (if desired). + +Formal definitions and grammars for CQL can be found here: - The [OAFeat (CQL) spec](https://portal.ogc.org/files/96288) includes an ABNF for cql-text and both JSON Schema and OpenAPI specifications for cql-json. The standalone files are: @@ -179,6 +190,18 @@ implementations that use datastores that do not easily support right-hand side p - A OpenAPI specification for only the parts of the CQL JSON encoding required by this extension is [here](cql.yml) - xtraplatform-spatial has a CQL [ANTLR 4 grammer](https://github.com/interactive-instruments/xtraplatform-spatial/tree/master/xtraplatform-cql/src/main/antlr/de/ii/xtraplatform/cql/infra) +These projects have or are developing CQL support: + +- [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the + [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi +- [Franklin](https://github.com/azavea/franklin) is working on it in [this PR](https://github.com/azavea/franklin/pull/750). +- [Geotools](https://github.com/geotools/geotools) has support for [CQL text](https://github.com/geotools/geotools/tree/main/modules/library/cql/src/main/java/org/geotools/filter/text/cql2) + +Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not +OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. +[Stacatto](https://github.com/planetlabs/staccato) uses this for their query language implementation, but it is +not compliant with this extension. + ## Queryables The Queryables mechanism allows a client to discover what variable terms are available for use when writing filter @@ -761,15 +784,3 @@ GeoJSON geometries. }, } ``` - -## Implementations - -- [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the - [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi -- [Franklin](https://github.com/azavea/franklin) is working on it in [this PR](https://github.com/azavea/franklin/pull/750). -- [Geotools](https://github.com/geotools/geotools) has support for [CQL text](https://github.com/geotools/geotools/tree/main/modules/library/cql/src/main/java/org/geotools/filter/text/cql2) - -Note that the [xbib CQL library (JVM)](https://github.com/xbib/cql) is the OASIS Contextual Query Language, not -OGC CQL, and should not be used to implement this extension, as they are significantly different query languages. -[Stacatto](https://github.com/planetlabs/staccato) uses this for their query language implementation, but it is -not compliant with this extension. From d45649de32e9e0454e75c919290b1fce9abec351 Mon Sep 17 00:00:00 2001 From: Frederico Liporace Date: Thu, 20 May 2021 18:29:29 -0300 Subject: [PATCH 54/61] typo; escaping * in MD; stac_version in ItemCollection; fixing reference --- item-search/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/item-search/README.md b/item-search/README.md index b33a23f3..19758b46 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -87,8 +87,8 @@ The core parameters for STAC search are defined by OAFeat, and STAC adds a few p | Parameter | Type | Source API | Description | | ----------- | ---------------- | ------------ | ----------- | | limit | integer | OAFeat | The maximum number of results to return (page size). Defaults to 10 | -| bbox | \[number] | OAFeat | Requested bounding box. Represented using either 2D or 3D geometries. The length of the array must be 2*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum elevation in meters and the elevation of the northeasterly most extent is the maximum. | -| datetime | string | OAFeat | Single date+time, or a range ('/' seperator), formatted to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). Use double dots `..` for open date ranges. | +| bbox | \[number] | OAFeat | Requested bounding box. Represented using either 2D or 3D geometries. The length of the array must be 2\*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum elevation in meters and the elevation of the northeasterly most extent is the maximum. | +| datetime | string | OAFeat | Single date+time, or a range ('/' separator), formatted to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). Use double dots `..` for open date ranges. | | intersects | GeoJSON Geometry | STAC | Searches items by performing intersection between their geometry and provided GeoJSON geometry. All GeoJSON geometry types must be supported. | | ids | \[string] | STAC | Array of Item ids to return. | | collections | \[string] | STAC | Array of one or more Collection IDs that each matching Item must be in. | @@ -99,7 +99,7 @@ should be returned. See [examples](examples.md) to see sample requests. ## Response The response to a request (GET or POST) to the search endpoint should always be an -`[ItemCollection](../core/itemcollection-spec.md)` object - a valid [GeoJSON +[ItemCollection](../fragments/itemcollection/README.md) object - a valid [GeoJSON FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) that consists entirely of STAC [Item](../stac-spec/item-spec/item-spec.md) objects. @@ -112,6 +112,7 @@ parameter name is defined by the implementor and is not necessarily part of the ```json { + "stac_version": "1.0.0-beta.2", "type": "FeatureCollection", "features": [], "links": [ From 8e08794ad6859891828f1b3936f39e7a5c84d874 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 24 May 2021 09:26:33 -0400 Subject: [PATCH 55/61] modify text based on comments from Rob --- fragments/filter/README.md | 34 +++++++++++++++++++++------------- item-search/README.md | 3 +++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index b2a84a5c..21c9112e 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -96,7 +96,8 @@ those provided by SQL. The Simple CQL conformance class requires the logical ope the comparison operators `=`, `<`, `<=`, `>`, `>=`, `LIKE`, `IS NULL`, `BETWEEN`, and `IN`; the spatial operator `INTERSECTS`; and the temporal operator `ANYINTERACTS`. -The ANYINTERACTS operator has effectively the same semantics as the existing `datetime` filter. +The ANYINTERACTS operator has effectively the same semantics as the `datetime` parameter +in Item Search. CQL enables these types of queries: - Use of Item Property values in predicates (e.g., `item.properties.eo:cloud_cover`), using comparison operators @@ -194,6 +195,7 @@ These projects have or are developing CQL support: - [GeoPython PyCQL](https://github.com/geopython/pycql/tree/master/pycql), and the [Bitner fork](https://github.com/bitner/pycql) to be used in stac-fastapi +- [pygeofilter](https://github.com/geopython/pygeofilter) has support for ECQL (a superset of CQL Text) and CQL JSON - [Franklin](https://github.com/azavea/franklin) is working on it in [this PR](https://github.com/azavea/franklin/pull/750). - [Geotools](https://github.com/geotools/geotools) has support for [CQL text](https://github.com/geotools/geotools/tree/main/modules/library/cql/src/main/java/org/geotools/filter/text/cql2) @@ -215,7 +217,12 @@ fully-qualified property names not advertised in queryables (e.g., `properties.eo:cloud_cover`, in anticipation that there be some mechanism to explicitly allow or advertise this in the future. -Queryables are advertised via a JSON Schema document retrieved from the `/queryables` endpoint. A basic Queryables +Queryables are advertised via a JSON Schema document retrieved from the `/queryables` endpoint. This endpoint at the root +retrieves queryables that apply to all collections. When used as a subresource of the collection resource +(e.g. /collections/collection1/queryables), it returns queryables pertaining only to that single collection. + +It is required to implement both of these endpoints, but for a STAC API, this may simply be a static document of the +STAC-specific fields. A basic Queryables definitions for STAC Items should include at least the fields id, collection, geometry, and datetime. ```json @@ -404,7 +411,8 @@ Schema `$ref` mechanism to refer to a STAC schema definition. This not only allo but also binds an arbitrarily-named Queryable to a specific STAC field. For example, in the above we know that the `eo:cloud_cover` field is referring to the field of the same name in the EO Extension not because they happen to have the same name, but rather because the `$ref` indicates it. The field could just as well be named "cloud_cover", "CloudCover", or "cc", -and we would still know it filtered on the EO extension `eo:cloud_cover` field. +and we would still know it filtered on the EO extension `eo:cloud_cover` field. For example, if the queryable was named +"CloudCover", a CQL expression using that queryable would look like `CloudCover <= 10`. While these do seem quite complex to write and understand, keep in mind that query construction will likely be done with a more ergonomic SDK, and query parsing will be done with the help of a ABNF grammar and OpenAPI schema. @@ -427,7 +435,7 @@ This example uses the queryables definition in (Interaction with Endpoints)(#int Note that `filter-lang` defaults to `cql-text` in this case. The parameter `filter-crs` defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` for a STAC API. -```http +```javascript GET /search?filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp' ``` @@ -436,7 +444,7 @@ GET /search?filter=id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collec Note that `filter-lang` defaults to `cql-json` in this case. The parameter `filter-crs` defaults to `http://www.opengis.net/def/crs/OGC/1.3/CRS84` for a STAC API. -```http +```javascript POST /search { "filter": { @@ -463,7 +471,7 @@ OGC API Features filters only operate against a single collection already. #### Example 2: GET with cql-text -```http +```javascript GET /search?filter=collection = 'landsat8_l1tp' AND gsd <= 30 AND eo:cloud_cover <= 10 @@ -473,7 +481,7 @@ GET /search?filter=collection = 'landsat8_l1tp' #### Example 2: POST with cql-json -```http +```javascript POST /search { "filter-lang": "cql-json", @@ -547,13 +555,13 @@ This queryables JSON Schema is used in these examples: #### Example 3: GET with cql-text -```http +```javascript GET /search?filter=prop1 = prop2 ``` #### Example 3: POST with cql-json -```http +```javascript POST /search { "filter-lang": "cql-json", @@ -660,7 +668,7 @@ a tiny sliver of data. #### Example 4: AND cql-text (GET) -```http +```javascript /search?filter=sentinel:data_coverage > 50 AND eo:cloud_cover < 10 ``` @@ -700,7 +708,7 @@ This uses the same queryables as Example 4. #### Example 5: OR cql-text (GET) -```http +```javascript /search?filter=sentinel:data_coverage > 50 OR eo:cloud_cover < 10 ``` @@ -736,7 +744,7 @@ The only temporal operator required is `ANYINTERACTS`, which follows the same se #### Example 6: ANYINTERACTS cql-text (GET) -```http +```javascript /search?filter=datetime ANYINTERACTS 2020-11-11 ``` @@ -761,7 +769,7 @@ GeoJSON geometries. #### Example 6: INTERSECTS cql-text (GET) -```http +```javascript /search?filter=INTERSECTS(geometry,POLYGON((-77.0824 38.7886,-77.0189 38.7886,-77.0189 38.8351,-77.0824 38.8351,-77.0824 38.7886))) ``` diff --git a/item-search/README.md b/item-search/README.md index 0f9fa999..2c6fea69 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -313,6 +313,9 @@ The full description and examples of this are found in the [context fragment](.. - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Deprecated - **Definition**: [STAC API - Query Fragment](../fragments/query/) +**Note** - the Query Extension is deprecated as of 1.0.0. Implementers +are encouraged to use the Filter Extension instead. + The STAC search endpoint, `/search`, by default only accepts a limited set of parameters to limit the results by properties. The Query extension adds a new parameter, `query`, that can take a number of comparison operators to match predicates between the fields requested and the values of Item objects. It can be used with both GET and POST, though From b8685acbe1b77a957a96f51ad76045aac3cb8452 Mon Sep 17 00:00:00 2001 From: Frederico Liporace Date: Tue, 25 May 2021 15:18:48 -0300 Subject: [PATCH 56/61] Removing stac_version from example --- item-search/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/item-search/README.md b/item-search/README.md index 19758b46..be46c727 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -112,7 +112,6 @@ parameter name is defined by the implementor and is not necessarily part of the ```json { - "stac_version": "1.0.0-beta.2", "type": "FeatureCollection", "features": [], "links": [ From f88fab87debba7be537b520dd7da7fa928458cc0 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Jun 2021 10:40:43 -0400 Subject: [PATCH 57/61] update stac_version references to 1.0.0 and stac api references to 1.0.0-beta.2 --- CHANGELOG.md | 17 ++++++- README.md | 2 +- build/swagger-config.yaml | 2 +- core/README.md | 8 +-- core/commons.yaml | 40 +++++++-------- core/openapi.yaml | 8 +-- extensions.md | 17 +++---- fragments/context/README.md | 14 +++--- fragments/context/examples/example.json | 2 +- fragments/context/openapi.yaml | 2 +- fragments/fields/README.md | 2 +- fragments/fields/openapi.yaml | 2 +- fragments/filter/README.md | 50 +++++++++---------- fragments/filter/openapi.yaml | 24 ++++----- .../examples/itemcollection-sample-full.json | 4 +- .../itemcollection-sample-minimal.json | 2 +- fragments/itemcollection/openapi.yaml | 4 +- fragments/query/README.md | 2 +- fragments/query/openapi.yaml | 8 +-- fragments/sort/README.md | 2 +- fragments/sort/openapi.yaml | 4 +- item-search/README.md | 20 ++++---- item-search/openapi.yaml | 28 +++++------ ogcapi-features/README.md | 28 +++++------ .../extensions/transaction/README.md | 12 ++--- .../extensions/transaction/openapi.yaml | 10 ++-- ogcapi-features/extensions/version/README.md | 24 ++++----- .../extensions/version/openapi.yaml | 4 +- ogcapi-features/openapi.yaml | 20 ++++---- overview.md | 22 ++++---- package.json | 2 +- 31 files changed, 199 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0816a5ab..c761ae72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +## [v1.0.0-beta.2] - 2020-06-01 + ### Added - Catalog and Collection definitions now have required field "type" - Added recommendation to enable CORS for public APIs ### Changed +- Updated all STAC versions to 1.0.0 - Added Filter extension to integrate OAFeat Part 3 CQL - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) - The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) ### Deprecated -- Query extension is now deprecated. Replaced by the Filter extension using OGC CQL +- Query extension is now deprecated. Replaced by the Filter extension using OGC CQL. ### Removed @@ -58,4 +71,4 @@ See the [stac-spec CHANGELOG](https://github.com/radiantearth/stac-spec/blob/v0. for STAC API releases prior to or equal to version 0.9.0. [Unreleased]: -[v1.0.0-beta.1]: +[v1.0.0-beta.2]: diff --git a/README.md b/README.md index b018c04a..62a31443 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ to search STAC catalogs, where the features returned are STAC [Item](stac-spec/i that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets. The specification for STAC API is provided as files that follow the [OpenAPI](http://openapis.org/) 3.0 specification, -rendered online into HTML at , in addition to human-readable documentation. +rendered online into HTML at , in addition to human-readable documentation. ## Stability Note diff --git a/build/swagger-config.yaml b/build/swagger-config.yaml index 81bda00e..707dd08c 100644 --- a/build/swagger-config.yaml +++ b/build/swagger-config.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: STAC API - Complete - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 apis: - url: 'build/core/openapi.yaml' paths: diff --git a/core/README.md b/core/README.md index 118ec657..9b890e4c 100644 --- a/core/README.md +++ b/core/README.md @@ -4,9 +4,9 @@ - [Recommended Link Relations at `/`](#recommended-link-relations-at-) - [Example Landing Page for STAC API - Core](#example-landing-page-for-stac-api---core) -- **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/core)), +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) describes the core endpoints ([rendered version](https://api.stacspec.org/v1.0.0-beta.2/core)), and [commons.yaml](commons.yaml) is the OpenAPI version of the core [STAC spec](../stac-spec) JSON Schemas. -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Dependencies**: None @@ -70,12 +70,12 @@ the [overview](../overview.md#example-landing-page) document. ```json { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "conformsTo" : [ - "https://api.stacspec.org/v1.0.0-beta.1/core" + "https://api.stacspec.org/v1.0.0-beta.2/core" ], "links": [ { diff --git a/core/commons.yaml b/core/commons.yaml index f0c0b9f1..e12b2ce6 100644 --- a/core/commons.yaml +++ b/core/commons.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Commons description: This is the OpenAPI version of the core STAC spec JSON Schemas. - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: responses: @@ -100,7 +100,7 @@ components: license: $ref: '#/components/schemas/license' extent: - $ref: "#/components/schemas/extent" + $ref: '#/components/schemas/extent' providers: $ref: '#/components/schemas/providers' links: @@ -148,7 +148,7 @@ components: - type: string - type: number example: - stac_version: 1.0.0-beta.2 + stac_version: 1.0.0 stac_extensions: [] type: Collection id: Sentinel-2 @@ -272,7 +272,7 @@ components: bbox: description: |- One or more bounding boxes that describe the spatial extent of the dataset. - + The first bounding box describes the overall spatial extent of the data. All subsequent bounding boxes describe more precise bounding boxes, e.g., to identify clusters of data. @@ -437,7 +437,7 @@ components: `proprietary` if the license is not on the SPDX license list or `various` if multiple licenses apply. In these two cases links to the license texts SHOULD be added, see the `license` link relation type. - + Non-SPDX licenses SHOULD add a link to the license text with the `license` relation in the links section. The license text MUST NOT be provided as a value of this field. If there is no public license URL @@ -556,7 +556,7 @@ components: stac_version: title: STAC version type: string - example: 1.0.0-beta.2 + example: 1.0.0 stac_extensions: title: STAC extensions type: array @@ -569,7 +569,7 @@ components: - title: Reference to a core extension type: string item: - description: A GeoJSON Feature augmented with foreign members that contain values relevant to a STAC entity + description: A GeoJSON Feature augmented with foreign members that contain values relevant to a STAC entity type: object required: - stac_version @@ -590,7 +590,7 @@ components: bbox: $ref: '#/components/schemas/bbox' geometry: - $ref: "#/components/schemas/geometryGeoJSON" + $ref: '#/components/schemas/geometryGeoJSON' type: $ref: '#/components/schemas/itemType' links: @@ -713,16 +713,16 @@ components: type: string description: Purposes of the asset example: - - thumbnail + - thumbnail geometryGeoJSON: oneOf: - - $ref: "#/components/schemas/pointGeoJSON" - - $ref: "#/components/schemas/multipointGeoJSON" - - $ref: "#/components/schemas/linestringGeoJSON" - - $ref: "#/components/schemas/multilinestringGeoJSON" - - $ref: "#/components/schemas/polygonGeoJSON" - - $ref: "#/components/schemas/multipolygonGeoJSON" - - $ref: "#/components/schemas/geometrycollectionGeoJSON" + - $ref: '#/components/schemas/pointGeoJSON' + - $ref: '#/components/schemas/multipointGeoJSON' + - $ref: '#/components/schemas/linestringGeoJSON' + - $ref: '#/components/schemas/multilinestringGeoJSON' + - $ref: '#/components/schemas/polygonGeoJSON' + - $ref: '#/components/schemas/multipolygonGeoJSON' + - $ref: '#/components/schemas/geometrycollectionGeoJSON' geometrycollectionGeoJSON: type: object required: @@ -736,7 +736,7 @@ components: geometries: type: array items: - $ref: "#/components/schemas/geometryGeoJSON" + $ref: '#/components/schemas/geometryGeoJSON' linestringGeoJSON: type: object required: @@ -862,7 +862,7 @@ components: features: type: array items: - $ref: "#/components/schemas/featureGeoJSON" + $ref: '#/components/schemas/featureGeoJSON' featureGeoJSON: type: object required: @@ -875,7 +875,7 @@ components: enum: - Feature geometry: - $ref: "#/components/schemas/geometryGeoJSON" + $ref: '#/components/schemas/geometryGeoJSON' properties: type: object - nullable: true \ No newline at end of file + nullable: true diff --git a/core/openapi.yaml b/core/openapi.yaml index 096f49f8..926f7123 100644 --- a/core/openapi.yaml +++ b/core/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Core - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 description: >- This is an OpenAPI definition of the core SpatioTemporal Asset Catalog API specification. Any service that implements this endpoint to allow discovery of @@ -58,12 +58,12 @@ components: $ref: '#/components/schemas/landingPage' example: type: Catalog - stac_version: 1.0.0-beta.1 + stac_version: 1.0.0 id: sentinel title: Copernicus Sentinel Imagery description: Catalog of Copernicus Sentinel 1 and 2 imagery. conformsTo: - - 'https://api.stacspec.org/v1.0.0-beta.1/core' + - 'https://api.stacspec.org/v1.0.0-beta.2/core' links: - href: 'http://data.example.org/' rel: self @@ -84,4 +84,4 @@ components: - href: 'http://data.example.org/sentinel-2' rel: child type: application/json - title: Sentinel 2 Catalog \ No newline at end of file + title: Sentinel 2 Catalog diff --git a/extensions.md b/extensions.md index efe5276a..7d9944ed 100644 --- a/extensions.md +++ b/extensions.md @@ -22,7 +22,7 @@ on the extension. | Pilot | 1 | Idea is fleshed out, with examples and a JSON schema, and implemented in one or more catalogs. Additional implementations encouraged to help give feedback | Approaching stability - breaking changes are not anticipated but can easily come from additional feedback | | Candidate | 3 | A number of implementers are using it and are standing behind it as a solid extension. Can generally count on an extension at this maturity level | Mostly stable, breaking changes require a new version and minor changes are unlikely. | | Stable | 6 | Highest current level of maturity. The community of extension maintainers commits to a STAC review process for any changes, which are not made lightly. | Completely stable, all changes require a new version number and review process. | -| Deprecated | N/A | A previous extension that has likely been superceded by a newer one or did not work out for some reason. | DO NOT USE, is not supported | +| Deprecated | N/A | A previous extension that has likely been superseded by a newer one or did not work out for some reason. | DO NOT USE, is not supported | Maturity mostly comes through diverse implementations, so the minimum number of implementations column is the main gating function for an extension to mature. But extension authors can also @@ -67,14 +67,13 @@ the service supports. This are listed at the top of each extension description, | Extension Name | Conformance Class URIs | |---------------|-------------------------| -| [Fields](item-search/README.md#fields) | | -| [Filter](item-search/README.md#filter) |



| -| [Context](item-search/README.md#context) | | -| [Sort](item-search/README.md#sort) | | -| [Transaction](ogcapi-features/extensions/transaction/README.md) | | -| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | | -| [Query](item-search/README.md#query) | | - +| [Fields](item-search/README.md#fields) | | +| [Filter](item-search/README.md#filter) |



| +| [Context](item-search/README.md#context) | | +| [Sort](item-search/README.md#sort) | | +| [Transaction](ogcapi-features/extensions/transaction/README.md) | | +| [Items and Collections API Version](ogcapi-features/extensions/version/README.md) | | +| [Query](item-search/README.md#query) | | ## Third-party / vendor extensions diff --git a/fragments/context/README.md b/fragments/context/README.md index 189617e4..57b723c5 100644 --- a/fragments/context/README.md +++ b/fragments/context/README.md @@ -1,7 +1,7 @@ # STAC API - Context Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Conformance Class:** +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) @@ -20,16 +20,16 @@ implementing OGC API - Features.* ## ItemCollection fields -| Element | Type | Description | -| --------- | --------------------------------- | ----------- | +| Element | Type | Description | +| --------- | --------------------------------- | ------------------------------------------------------------------------------------------------ | | `context` | [Context Object](#context-object) | **REQUIRED.** The search-related metadata for the [ItemCollection](../itemcollection/README.md). | ## Context Object -| Element | Type | Description | -| -------- | --------------- | ----------- | -| returned | integer | **REQUIRED** The count of results returned by this response. Equal to the cardinality of features array. | -| limit | integer \| null | The maximum number of results to which the result was limited. | +| Element | Type | Description | +| -------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| returned | integer | **REQUIRED** The count of results returned by this response. Equal to the cardinality of features array. | +| limit | integer \| null | The maximum number of results to which the result was limited. | | matched | integer | The count of total number of results that match for this query, possibly estimated, particularly in the context of NoSQL data stores. | The default sort of query results should be stable, but may not be depending on the data store's sorting performance. diff --git a/fragments/context/examples/example.json b/fragments/context/examples/example.json index 71c6941e..fcb6809f 100644 --- a/fragments/context/examples/example.json +++ b/fragments/context/examples/example.json @@ -7,7 +7,7 @@ }, "features": [ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "CS3-20160503_132131_05", diff --git a/fragments/context/openapi.yaml b/fragments/context/openapi.yaml index f5c1f7a4..1dab1fdf 100644 --- a/fragments/context/openapi.yaml +++ b/fragments/context/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Context description: Adds search related metadata (context) to GeoJSON Responses. - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: schemas: diff --git a/fragments/fields/README.md b/fragments/fields/README.md index 52087fa1..f26cbb29 100644 --- a/fragments/fields/README.md +++ b/fragments/fields/README.md @@ -1,7 +1,7 @@ # STAC API - Fields Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Conformance Class:** +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) diff --git a/fragments/fields/openapi.yaml b/fragments/fields/openapi.yaml index b08dff4f..dde11909 100644 --- a/fragments/fields/openapi.yaml +++ b/fragments/fields/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Fields description: Adds parameter to control which fields are returned in the response. - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: parameters: diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 21c9112e..98920309 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -2,16 +2,16 @@ - **OpenAPI specification:** [openapi.yaml](openapi.yaml) - **Conformance Classes:** - - Filter: - - Simple CQL: - - Item Search Filter: - - CQL Text: - - CQL JSON: - - Enhanced Spatial Operators: - - Enhanced Temporal Operators: - - Functions: - - Arithmetic: - - Arrays: + - Filter: + - Simple CQL: + - Item Search Filter: + - CQL Text: + - CQL JSON: + - Enhanced Spatial Operators: + - Enhanced Temporal Operators: + - Functions: + - Arithmetic: + - Arrays: - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) @@ -119,23 +119,23 @@ their underlying datastore, e.g., Elasticsearch does not support the spatial pre Enhanced Spatial Operators conformance class. The STAC API Filter Extension reuses the definitions of several conformance classes defined in OAFeat CQL, but with a prefix of -`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:` instead of `http://www.opengis.net/spec/ogcapi-features-3/1.0/req/`. +`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:` instead of `http://www.opengis.net/spec/ogcapi-features-3/1.0/req/`. The implementation must support these conformance classes: -- Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter`) defines the Queryables mechanism and +- Filter (`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:filter`) defines the Queryables mechanism and parameters `filter-lang`, `filter-crs`, and `filter`. -- Simple CQL (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql`) defines the query language used +- Simple CQL (`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:simple-cql`) defines the query language used for the `filter` parameter defined by Filter. -- Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:item-search-filter`) binds the Filter and Simple CQL +- Item Search Filter (`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:item-search-filter`) binds the Filter and Simple CQL conformance classes to apply to the Item Search endpoint (`/search`). This class is the correlate of the OAFeat CQL Features Filter class that binds Filter and Simple CQL to the Features resource (`/collections/{cid}/items`). The implementation must support at least one of the "CQL Text" or "CQL JSON" conformance classes that define the CQL format used in the filter parameter: -- CQL Text (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text`) defines that the CQL Text format is supported by Item Search. -- CQL JSON (`https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json`) defines that the CQL JSON format is supported by Item Search +- CQL Text (`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-text`) defines that the CQL Text format is supported by Item Search. +- CQL JSON (`https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-json`) defines that the CQL JSON format is supported by Item Search If both are advertised as being supported, it is only required that both be supported for GET query parameters, and that only that CQL JSON be supported for POST JSON requests. @@ -329,15 +329,15 @@ In an implementation that supports Simple CQL, the Landing Page (`/`) should ret "http://www.opengis.net/spec/ogcapi_common-2/1.0/req/collections", - "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:filter", - "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:features-filter", - "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:simple-cql", - "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-text", - "https://api.stacspec.org/v1.0.0-beta.1/item-search#filter:cql-json", + "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:filter", + "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:features-filter", + "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:simple-cql", + "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-text", + "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-json", - "http://stacspec.org/spec/api/1.0.0-beta.1/core", - "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-search", - "http://stacspec.org/spec/api/1.0.0-beta.1/req/stac-response" + "http://stacspec.org/spec/api/1.0.0-beta.2/core", + "http://stacspec.org/spec/api/1.0.0-beta.2/req/stac-search", + "http://stacspec.org/spec/api/1.0.0-beta.2/req/stac-response" ], "links": [ { @@ -625,7 +625,7 @@ A sample STAC Item (excluding `assets`) is: ```json { "type": "Feature", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "stac_extensions": [ "eo", "view", diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index 16872695..98af69e5 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -2,10 +2,10 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Filter description: Adds parameters to compare properties and only return the items that match - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: - "/": + '/': description: Landing Page get: responses: @@ -29,7 +29,7 @@ paths: - Queryables # parameters: # todo: may have collections parameter in the future - responses: + responses: '200': description: A JSON Schema defining the Queryables allowed in CQL expressions content: @@ -47,7 +47,7 @@ paths: queryables: operationId: getQueryables description: |- - A link with rel=queryables for queryables to only apply to this collection. + A link with rel=queryables for queryables to only apply to this collection. /collections/{collectionId}/queryables: get: summary: Get the JSON Schema defining the list of variable terms that can be used in CQL expressions. @@ -58,7 +58,7 @@ paths: Common Query Language (CQL) specification. tags: - Queryables - responses: + responses: '200': description: A JSON Schema defining the Queryables allowed in CQL expressions filtering only that collection content: @@ -75,7 +75,7 @@ components: in: query description: |- **Extension:** Filter - + A CQL filter expression for filtering items. required: true schema: @@ -88,18 +88,18 @@ components: in: query description: |- **Extension:** Filter - + The CQL filter encoding that the 'filter' value uses. Must be one of 'cql-text' or 'cql-json'. required: false schema: - $ref: '#/components/schemas/filter-lang' + $ref: '#/components/schemas/filter-lang' filter-crs: name: filter-crs x-stac-api-fragment: filter in: query description: |- **Extension:** Filter - + The CRS used by spatial predicates in the filter parameter. In STAC API, only value that must be accepted is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'. required: false @@ -112,7 +112,7 @@ components: description: |- **Optional Extension:** Filter - A filter for properties in Items. + A filter for properties in Items. properties: filter: $ref: '#/components/schemas/filter-cql-json' @@ -132,7 +132,7 @@ components: description: | The CQL filter encoding that the 'filter' value uses. type: string - enum: + enum: - 'cql-text' - 'cql-json' filter-crs: @@ -140,4 +140,4 @@ components: The coordinate reference system (CRS) used by spatial literals in the 'filter' value. The only value that STAC APIs must accept is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'. type: string - format: uri \ No newline at end of file + format: uri diff --git a/fragments/itemcollection/examples/itemcollection-sample-full.json b/fragments/itemcollection/examples/itemcollection-sample-full.json index ca60fa52..a9aefbff 100644 --- a/fragments/itemcollection/examples/itemcollection-sample-full.json +++ b/fragments/itemcollection/examples/itemcollection-sample-full.json @@ -1,10 +1,10 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "FeatureCollection", "features": [ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "CS3-20160503_132131_05", diff --git a/fragments/itemcollection/examples/itemcollection-sample-minimal.json b/fragments/itemcollection/examples/itemcollection-sample-minimal.json index c9ca68d0..63a5d5b5 100644 --- a/fragments/itemcollection/examples/itemcollection-sample-minimal.json +++ b/fragments/itemcollection/examples/itemcollection-sample-minimal.json @@ -1,5 +1,5 @@ { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "FeatureCollection", "features": [] diff --git a/fragments/itemcollection/openapi.yaml b/fragments/itemcollection/openapi.yaml index 2b62baf1..07347eca 100644 --- a/fragments/itemcollection/openapi.yaml +++ b/fragments/itemcollection/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Item Collection description: The specification for a set of items, e.g. returned by a search. - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: schemas: @@ -37,4 +37,4 @@ components: example: - rel: next href: >- - http://api.cool-sat.com/search?next=ANsXtp9mrqN0yrKWhf-y2PUpHRLQb1GT-mtxNcXou8TwkXhi1Jbk \ No newline at end of file + http://api.cool-sat.com/search?next=ANsXtp9mrqN0yrKWhf-y2PUpHRLQb1GT-mtxNcXou8TwkXhi1Jbk diff --git a/fragments/query/README.md b/fragments/query/README.md index 4a7bcacf..8fa76d3e 100644 --- a/fragments/query/README.md +++ b/fragments/query/README.md @@ -1,7 +1,7 @@ # STAC API - Query Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Conformance Class:** +- **Conformance Class:** - **Extension [Maturity Classification](../../extensions.md#extension-maturity):** Deprecated in favor of the [Filter Extension](../filter/README.md) using [CQL](http://docs.opengeospatial.org/DRAFTS/19-079.html). - **Dependents:** diff --git a/fragments/query/openapi.yaml b/fragments/query/openapi.yaml index 8085bdc5..c3e64111 100644 --- a/fragments/query/openapi.yaml +++ b/fragments/query/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Query description: Adds parameter to compare properties and only return the items that match - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: parameters: @@ -12,7 +12,7 @@ components: in: query description: |- **Optional Extension:** Query - + Query for properties in items. Use the JSON form of the query used in POST. required: false @@ -24,7 +24,7 @@ components: x-stac-api-fragment: query description: |- **Optional Extension:** Query - + Allows users to query properties for specific values properties: query: @@ -114,4 +114,4 @@ components: items: oneOf: - type: string - - type: number \ No newline at end of file + - type: number diff --git a/fragments/sort/README.md b/fragments/sort/README.md index 6fd1c948..1e34304c 100644 --- a/fragments/sort/README.md +++ b/fragments/sort/README.md @@ -1,7 +1,7 @@ # STAC API - Sort Fragment - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Conformance Class:** +- **Conformance Class:** - **Fragment [Maturity Classification](../../extensions.md#extension-maturity):** Pilot - **Dependents:** - [Item Search](../../item-search) diff --git a/fragments/sort/openapi.yaml b/fragments/sort/openapi.yaml index 34f75189..ecc59fc5 100644 --- a/fragments/sort/openapi.yaml +++ b/fragments/sort/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Sort description: Adds Parameter to control sorting of returns results. - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 paths: {} components: parameters: @@ -27,7 +27,7 @@ components: x-stac-api-fragment: sort description: |- **Optional Extension:** Sort - + Sort the results. properties: sortby: diff --git a/item-search/README.md b/item-search/README.md index 15e1d2ed..a6ef733d 100644 --- a/item-search/README.md +++ b/item-search/README.md @@ -19,8 +19,8 @@ - [Context](#context) - [Query](#query) -- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/item-search)) -- **Conformance URI:** +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.2/item-search)) +- **Conformance URI:** - **Dependencies**: [STAC API - Core](../core) - **Examples**: [examples.md](examples.md) @@ -215,13 +215,13 @@ the [overview](../overview.md#example-landing-page) document. ```json { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "conformsTo" : [ - "https://api.stacspec.org/v1.0.0-beta.1/core", - "https://api.stacspec.org/v1.0.0-beta.1/item-search" + "https://api.stacspec.org/v1.0.0-beta.2/core", + "https://api.stacspec.org/v1.0.0-beta.2/item-search" ], "links": [ { @@ -262,7 +262,7 @@ the root (`/`) landing page, to indicate to clients that they will respond prope ### Fields -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Definition**: [STAC API - Fields Fragment](../fragments/fields/) @@ -274,7 +274,7 @@ through the use of a `fields` parameter. The full description of how this extens ### Filter -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Definition**: [STAC API - Filter Fragment](../fragments/filter/) @@ -286,7 +286,7 @@ fragment](../fragments/filter/). ### Sort -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Definition**: [STAC API - Sort Fragment](../fragments/sort/) @@ -299,7 +299,7 @@ of this extension can be found in the [sort fragment](../fragments/sort). ### Context -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Pilot - **Definition**: [STAC API - Context Fragment](../fragments/context/) @@ -309,7 +309,7 @@ The full description and examples of this are found in the [context fragment](.. ### Query -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../extensions.md#extension-maturity):** Deprecated - **Definition**: [STAC API - Query Fragment](../fragments/query/) diff --git a/item-search/openapi.yaml b/item-search/openapi.yaml index d364dda8..e236cdd2 100644 --- a/item-search/openapi.yaml +++ b/item-search/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Item Search - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 description: >- This is an OpenAPI definition of the SpatioTemporal Asset Catalog API Item Search specification. @@ -22,7 +22,7 @@ paths: description: |- Retrieve Items matching filters. Intended as a shorthand API for simple queries. - + This method is optional, but you MUST implement `POST /search` if you want to implement this method. @@ -50,9 +50,9 @@ paths: application/geo+json: schema: allOf: - - $ref: '../fragments/itemcollection/openapi.yaml#/components/schemas/itemCollection' - # extensions - - $ref: '../fragments/context/openapi.yaml#/components/schemas/itemCollection' + - $ref: '../fragments/itemcollection/openapi.yaml#/components/schemas/itemCollection' + # extensions + - $ref: '../fragments/context/openapi.yaml#/components/schemas/itemCollection' text/html: schema: type: string @@ -64,7 +64,7 @@ paths: description: |- retrieve items matching filters. Intended as the standard, full-featured query API. - + This method is mandatory to implement if `GET /search` is implemented. If this endpoint is implemented on a server, it is required to add a link referring to this endpoint with `rel` set to `search` and `method` @@ -76,11 +76,11 @@ paths: application/json: schema: allOf: - - $ref: '#/components/schemas/searchBody' - # extensions - - $ref: '../fragments/fields/openapi.yaml#/components/schemas/searchBody' - - $ref: '../fragments/filter/openapi.yaml#/components/schemas/searchBody' - - $ref: '../fragments/sort/openapi.yaml#/components/schemas/searchBody' + - $ref: '#/components/schemas/searchBody' + # extensions + - $ref: '../fragments/fields/openapi.yaml#/components/schemas/searchBody' + - $ref: '../fragments/filter/openapi.yaml#/components/schemas/searchBody' + - $ref: '../fragments/sort/openapi.yaml#/components/schemas/searchBody' responses: '200': description: A feature collection. @@ -209,10 +209,10 @@ components: in: query description: |- The optional intersects parameter filters the result Items in the same was as bbox, only with - a GeoJSON Geometry rather than a bbox. + a GeoJSON Geometry rather than a bbox. required: false schema: - $ref: "../core/commons.yaml#/components/schemas/geometryGeoJSON" + $ref: '../core/commons.yaml#/components/schemas/geometryGeoJSON' style: form explode: false schemas: @@ -263,7 +263,7 @@ components: description: Only returns items that intersect with the provided polygon. properties: intersects: - $ref: "../core/commons.yaml#/components/schemas/geometryGeoJSON" + $ref: '../core/commons.yaml#/components/schemas/geometryGeoJSON' limitFilter: type: object description: Only returns maximum number of results (page size) diff --git a/ogcapi-features/README.md b/ogcapi-features/README.md index 89c69cbf..f9c9114d 100644 --- a/ogcapi-features/README.md +++ b/ogcapi-features/README.md @@ -7,10 +7,10 @@ *based on [**OGC API - Features - Part 1: Core**](https://www.ogc.org/standards/ogcapi-features)* -- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features)) +- **OpenAPI specification:** [openapi.yaml](openapi.yaml) ([rendered version](https://api.stacspec.org/v1.0.0-beta.2/ogcapi-features)) uses all the OGC API - Features openapi fragments to describe returning STAC Item objects. - **Conformance URIs:** - - + - - - [Requirements Class Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#rc_core)) - - [Requirements Class OpenAPI 3.0](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#rc_oas30)) - - [Requirements Class GeoJSON](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_requirements_class_geojson)) @@ -33,15 +33,15 @@ with OAFeat clients. But specialized STAC clients will likely display results be The core OGC API - Features endpoints are shown below, with details provided in an [OpenAPI specification document](openapi.yaml). -| Endpoint | Returns | Description | -| ----------------------------------------------- | ---------------- | ----------- | -| `/` | [Catalog](../stac-spec/catalog-spec/README.md) | Landing page, links to API capabilities | -| `/conformance` | JSON | Info about standards to which the API conforms | -| `/collections` | JSON | Object containing an array of Collection objects in the Catalog, and Link relations | -| `/collections/{collectionId}` | [Collection](../stac-spec/collection-spec/README.md) | Returns single Collection JSON | -| `/collections/{collectionId}/items` | [ItemCollection](../fragments/itemcollection/README.md) | GeoJSON FeatureCollection-conformant entity of Item objects in collection | -| `/collections/{collectionId}/items/{featureId}` | [Item](../stac-spec/item-spec/README.md) | Returns single Item (GeoJSON Feature) | -| `/api` | OpenAPI 3.0 JSON | Returns an OpenAPI description of the service from the `service-desc` link `rel` - not required to be `/api`, but the document is required | +| Endpoint | Returns | Description | +| ----------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `/` | [Catalog](../stac-spec/catalog-spec/README.md) | Landing page, links to API capabilities | +| `/conformance` | JSON | Info about standards to which the API conforms | +| `/collections` | JSON | Object containing an array of Collection objects in the Catalog, and Link relations | +| `/collections/{collectionId}` | [Collection](../stac-spec/collection-spec/README.md) | Returns single Collection JSON | +| `/collections/{collectionId}/items` | [ItemCollection](../fragments/itemcollection/README.md) | GeoJSON FeatureCollection-conformant entity of Item objects in collection | +| `/collections/{collectionId}/items/{featureId}` | [Item](../stac-spec/item-spec/README.md) | Returns single Item (GeoJSON Feature) | +| `/api` | OpenAPI 3.0 JSON | Returns an OpenAPI description of the service from the `service-desc` link `rel` - not required to be `/api`, but the document is required | The OGC API - Features is a standard API that represents collections of geospatial data. It defines the RESTful interface to query geospatial data, with GeoJSON as a main return type. With OAFeat you can return any `Feature`, which is a geometry @@ -114,13 +114,13 @@ the [overview](../overview.md#example-landing-page) document. ```json { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "conformsTo" : [ - "https://api.stacspec.org/v1.0.0-beta.1/core", - "https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features", + "https://api.stacspec.org/v1.0.0-beta.2/core", + "https://api.stacspec.org/v1.0.0-beta.2/ogcapi-features", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" diff --git a/ogcapi-features/extensions/transaction/README.md b/ogcapi-features/extensions/transaction/README.md index 537f5fb7..6971f3c4 100644 --- a/ogcapi-features/extensions/transaction/README.md +++ b/ogcapi-features/extensions/transaction/README.md @@ -4,7 +4,7 @@ - **OpenAPI specification:** [openapi.yaml](openapi.yaml) - **Conformance URIs:** - - + - - - **Extension [Maturity Classification](../../../extensions.md#extension-maturity):** Pilot - **Dependencies**: [STAC API - Features](../../README.md) @@ -29,9 +29,9 @@ work to get it incorporated. ## Methods -| Path | Content-Type Header | Description | -| ------------------------------------------------------ | ------------------- | ----------- | -| `POST /collections/{collectionID}/items` | `application/json` | Adds a new item to a collection. | -| `PUT /collections/{collectionId}/items/{featureId}` | `application/json` | Updates an existing item by ID using a complete item description. | +| Path | Content-Type Header | Description | +| ------------------------------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `POST /collections/{collectionID}/items` | `application/json` | Adds a new item to a collection. | +| `PUT /collections/{collectionId}/items/{featureId}` | `application/json` | Updates an existing item by ID using a complete item description. | | `PATCH /collections/{collectionId}/items/{featureId}` | `application/json` | Updates an existing item by ID using a partial item description, compliant with [RFC 7386](https://tools.ietf.org/html/rfc7386). | -| `DELETE /collections/{collectionID}/items/{featureId}` | n/a | Deletes an existing item by ID. | +| `DELETE /collections/{collectionID}/items/{featureId}` | n/a | Deletes an existing item by ID. | diff --git a/ogcapi-features/extensions/transaction/openapi.yaml b/ogcapi-features/extensions/transaction/openapi.yaml index 03761d08..5cfe8a67 100644 --- a/ogcapi-features/extensions/transaction/openapi.yaml +++ b/ogcapi-features/extensions/transaction/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.1 info: title: The SpatioTemporal Asset Catalog API - Transaction - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 description: >- This is an OpenAPI definition of the SpatioTemporal Asset Catalog API Transaction specification. @@ -30,9 +30,9 @@ paths: content: application/json: schema: - oneOf: - - $ref: '../../../core/commons.yaml#/components/schemas/item' - - $ref: '../../../fragments/itemcollection/openapi.yaml#/components/schemas/itemCollection' + oneOf: + - $ref: '../../../core/commons.yaml#/components/schemas/item' + - $ref: '../../../fragments/itemcollection/openapi.yaml#/components/schemas/itemCollection' responses: '201': description: Status of the create request. @@ -243,4 +243,4 @@ components: content: application/json: schema: - $ref: '../../../core/commons.yaml#/components/schemas/exception' \ No newline at end of file + $ref: '../../../core/commons.yaml#/components/schemas/exception' diff --git a/ogcapi-features/extensions/version/README.md b/ogcapi-features/extensions/version/README.md index 2b506e7d..33fca1b5 100644 --- a/ogcapi-features/extensions/version/README.md +++ b/ogcapi-features/extensions/version/README.md @@ -1,7 +1,7 @@ # Items and Collections API Version Extension - **OpenAPI specification:** [openapi.yaml](openapi.yaml) -- **Conformance URI:** +- **Conformance URI:** - **Extension [Maturity Classification](../../../extensions.md#extension-maturity):** Proposal - **Dependencies**: [STAC API - Features](../../README.md) @@ -12,12 +12,12 @@ incubated in STAC. ## Methods -| Path | Content-Type Header | Description | -| ------------------------------------------------------------------------ | ------------------- | ----------- | +| Path | Content-Type Header | Description | +| ------------------------------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------- | | `GET /collections/{collectionID}/versions` | `application/json` | Returns a catalog response with links to all versions of a given collection. | -| `GET /collections/{collectionID}/versions/{versionId}` | `application/json` | Returns a collection record. | -| `GET /collections/{collectionID}/items/{featureId}/versions` | `application/json` | Returns a catalog response with links to all versions of a given item. | -| `GET /collections/{collectionID}/items/{featureId}/versions/{versionId}` | `application/json` | Returns an item record. | +| `GET /collections/{collectionID}/versions/{versionId}` | `application/json` | Returns a collection record. | +| `GET /collections/{collectionID}/items/{featureId}/versions` | `application/json` | Returns a catalog response with links to all versions of a given item. | +| `GET /collections/{collectionID}/items/{featureId}/versions/{versionId}` | `application/json` | Returns an item record. | ## How It Works @@ -50,12 +50,12 @@ There are many options for a versioning schema including: The extension uses [RFC5829](https://tools.ietf.org/html/rfc5829) rel types to link to different versions: -| Type | Description | -| ------------------- | ----------- | -| latest-version | Points to the latest version of the record. | -| version-history | Points to the list of versions. | -| predecessor-version | Points to the previous version of the document. | -| successor-version | Points to the successor version in the version history. | +| Type | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| latest-version | Points to the latest version of the record. | +| version-history | Points to the list of versions. | +| predecessor-version | Points to the previous version of the document. | +| successor-version | Points to the successor version in the version history. | | permalink | Points to the permanent location of the record. This location points to a specific version, remains accessible and will not change even when there are future versions of the record. | ## Example diff --git a/ogcapi-features/extensions/version/openapi.yaml b/ogcapi-features/extensions/version/openapi.yaml index cbb8bfa2..87acaa56 100644 --- a/ogcapi-features/extensions/version/openapi.yaml +++ b/ogcapi-features/extensions/version/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: The SpatioTemporal Asset Catalog API - Item Search - version: 1.0.0-beta.1 + version: 1.0.0-beta.2 description: >- This is an OpenAPI definition of the SpatioTemporal Asset Catalog API Item Search specification. @@ -83,4 +83,4 @@ components: description: local identifier of a version required: true schema: - type: string \ No newline at end of file + type: string diff --git a/ogcapi-features/openapi.yaml b/ogcapi-features/openapi.yaml index 1c9ed6d1..38e2586d 100644 --- a/ogcapi-features/openapi.yaml +++ b/ogcapi-features/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.2 info: - title: "STAC API - Features (extends OGC API - Features - Part 1: Core)" - version: '1.0.0-beta.1' + title: 'STAC API - Features (extends OGC API - Features - Part 1: Core)' + version: '1.0.0-beta.2' description: >- This is an OpenAPI definition of the SpatioTemporal Asset Catalog API Item Search specification. @@ -38,13 +38,13 @@ paths: schema: $ref: '../core/openapi.yaml#/components/schemas/landingPage' example: - stac_version: 1.0.0-beta.1 + stac_version: 1.0.0-beta.2 type: Catalog id: sentinel title: Copernicus Sentinel Imagery description: Catalog of Copernicus Sentinel 1 and 2 imagery. conformsTo: - - 'https://api.stacspec.org/v1.0.0-beta.1/core' + - 'https://api.stacspec.org/v1.0.0-beta.2/core' - 'http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core' - 'http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30' - 'http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson' @@ -291,15 +291,15 @@ components: features: type: array items: - $ref: "../core/commons.yaml#/components/schemas/item" + $ref: '../core/commons.yaml#/components/schemas/item' links: $ref: '../core/commons.yaml#/components/schemas/links' timeStamp: - $ref: "#/components/schemas/timeStamp" + $ref: '#/components/schemas/timeStamp' numberMatched: - $ref: "#/components/schemas/numberMatched" + $ref: '#/components/schemas/numberMatched' numberReturned: - $ref: "#/components/schemas/numberReturned" + $ref: '#/components/schemas/numberReturned' numberMatched: description: |- The number of features of the feature type that match the selection @@ -413,7 +413,7 @@ components: content: application/geo+json: schema: - $ref: "../core/commons.yaml#/components/schemas/item" + $ref: '../core/commons.yaml#/components/schemas/item' InvalidParameter: description: |- A query parameter has an invalid value. @@ -430,4 +430,4 @@ components: content: application/json: schema: - $ref: '../core/commons.yaml#/components/schemas/exception' \ No newline at end of file + $ref: '../core/commons.yaml#/components/schemas/exception' diff --git a/overview.md b/overview.md index eaee28f9..568dc386 100644 --- a/overview.md +++ b/overview.md @@ -20,7 +20,7 @@ off point for the more powerful capabilities - it contains a list of URL's, each 'relationships' (`rel`) to indicate their functionality. Note that the [STAC Core specification](stac-spec) provides most all the content of API responses - the STAC API is primarily concerned with the return of STAC [Item](stac-spec/item-spec/README.md) and [Collection](stac-spec/collection-spec/README.md) objects via a -RESTful web API. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.1/core) for more details. +RESTful web API. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.2/core) for more details. There are then two major sets of functionality that build on the core, which are designed to be complementary, letting implementations choose which parts they want to utilize. Most every STAC API implements at least one, and many follow @@ -33,7 +33,7 @@ located at a `/search` endpoint. It re-uses all of the OAFeat [query parameters](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_items_) specified in their 'core', and adds a couple more. It does not require a full implementation of OAFeat, it is instead a simplified construct that can run a search across any set of indexed STAC [`Item`](stac-spec/item-spec/README.md) objects. See the [rendered OpenAPI -document](https://api.stacspec.org/v1.0.0-beta.1/item-spec) for more details. +document](https://api.stacspec.org/v1.0.0-beta.2/item-spec) for more details. ### OGC API - Features @@ -49,7 +49,7 @@ is always in GeoJSON and OpenAPI is used to specify STAC API. Full compliance in individual `/collections/{collectionId}/items` endpoints that expose querying single collections, as OAFeat does not currently allow cross-collection search. And it adds a few other requirements, which are highlighted in the [features description](ogcapi-features/), in order to help STAC implementors understand OAFeat without having to -read the full spec from scratch. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features) +read the full spec from scratch. See the [rendered OpenAPI document](https://api.stacspec.org/v1.0.0-beta.2/ogcapi-features) for more details. ### Extensions & Fragments @@ -106,7 +106,7 @@ column is more of an example in some cases. OGC API makes some endpoint location STAC API is evolving to utilize OAFeat's '[Conformance](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_declaration_of_conformance_classes)' JSON structure. For -STAC API 1.0.0-beta.1 we declare new STAC Conformance classes, with the core ones detailed in the table below. [STAC +STAC API 1.0.0-beta.2 we declare new STAC Conformance classes, with the core ones detailed in the table below. [STAC Features](ogcapi-features) requires the core OAFeat conformance classes, and declares that those endpoints return STAC Collection and Feature objects. The core STAC conformance classes communicate the conformance JSON only in the root (`/`) document, while OGC API @@ -122,9 +122,9 @@ have the URI's for conformance to actually resolve to machine-readable informati | **Name** | **Specified in** | **Conformance URI** | **Description** | |---------------------------|----------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| STAC Core | [Core](core) | | Specifies the STAC Landing page `/`, communicating conformance and available endpoints. | -| Item Search | [Item Search](item-search) | | Enables search of all STAC Item objects on the server, with the STAC `[/search](#stac-api-endpoints)` endpoint. | -| STAC Features | [STAC API - Features](ogcapi-features) | | Specifies the use of OGC API - Features to serve STAC Item and Collection objects | +| STAC Core | [Core](core) | | Specifies the STAC Landing page `/`, communicating conformance and available endpoints. | +| Item Search | [Item Search](item-search) | | Enables search of all STAC Item objects on the server, with the STAC `[/search](#stac-api-endpoints)` endpoint. | +| STAC Features | [STAC API - Features](ogcapi-features) | | Specifies the use of OGC API - Features to serve STAC Item and Collection objects | Additional conformance classes are specified in the [STAC Extensions](extensions.md#Conformance-classes-of-extensions). @@ -140,14 +140,14 @@ The Landing Page will at least have the following `conformsTo` and `links`: ```json { - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "conformsTo" : [ - "https://api.stacspec.org/v1.0.0-beta.1/core", - "https://api.stacspec.org/v1.0.0-beta.1/item-search", - "https://api.stacspec.org/v1.0.0-beta.1/ogcapi-features", + "https://api.stacspec.org/v1.0.0-beta.2/core", + "https://api.stacspec.org/v1.0.0-beta.2/item-search", + "https://api.stacspec.org/v1.0.0-beta.2/ogcapi-features", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" diff --git a/package.json b/package.json index 426441e5..b4fac66f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-spec", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "STAC API helpers to generate, serve and check the API spec.", "repository": "https://github.com/radiantearth/stac-api-spec", "license": "Apache-2.0", From a0bbf8fa649e0a8e0cdaebadf2b93e9ef230a116 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Jun 2021 10:45:30 -0400 Subject: [PATCH 58/61] fix link in changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c761ae72..5543bcc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,4 +71,5 @@ See the [stac-spec CHANGELOG](https://github.com/radiantearth/stac-spec/blob/v0. for STAC API releases prior to or equal to version 0.9.0. [Unreleased]: -[v1.0.0-beta.2]: +[v1.0.0-beta.1]: +[v1.0.0-beta.2]: \ No newline at end of file From 467afd7a5de70ae062e61bab90ea4b819fa0c327 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Wed, 2 Jun 2021 09:16:01 -0400 Subject: [PATCH 59/61] fix changelog and fragments --- CHANGELOG.md | 2 +- fragments/filter/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5543bcc0..826f3164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v1.0.0-beta.2] - 2020-06-01 ### Added +- Added Filter extension to integrate OAFeat Part 3 CQL - Catalog and Collection definitions now have required field "type" - Added recommendation to enable CORS for public APIs ### Changed - Updated all STAC versions to 1.0.0 -- Added Filter extension to integrate OAFeat Part 3 CQL - Passing the `ids` parameter to an item search does not deactivate other query parameters [#125](https://github.com/radiantearth/stac-api-spec/pull/125) - The first extent in a Collection is always the overall extent, followed by more specific extents. [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520) diff --git a/fragments/filter/README.md b/fragments/filter/README.md index 98920309..8f83c539 100644 --- a/fragments/filter/README.md +++ b/fragments/filter/README.md @@ -335,9 +335,9 @@ In an implementation that supports Simple CQL, the Landing Page (`/`) should ret "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-text", "https://api.stacspec.org/v1.0.0-beta.2/item-search#filter:cql-json", - "http://stacspec.org/spec/api/1.0.0-beta.2/core", - "http://stacspec.org/spec/api/1.0.0-beta.2/req/stac-search", - "http://stacspec.org/spec/api/1.0.0-beta.2/req/stac-response" + "http://api.stacspec.org/v1.0.0-beta.2/core", + "http://api.stacspec.org/v1.0.0-beta.2/req/stac-search", + "http://api.stacspec.org/v1.0.0-beta.2/req/stac-response" ], "links": [ { From 38862153e6e88521f61be6585d2d4362fef68775 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Fri, 4 Jun 2021 09:36:51 -0400 Subject: [PATCH 60/61] Squashed 'stac-spec/' changes from ebd16753..789e90fb 789e90fb Merge pull request #1139 from radiantearth/dev 76d1560d Merge pull request #1140 from radiantearth/stability-note-update 19959672 removed stability note and moved key info for 1.0.0 e5ec6047 Merge pull request #1135 from radiantearth/versions-to-1_0_0 ca90fa47 Merge branch 'dev' into versions-to-1_0_0 5d017a4b Merge pull request #1138 from radiantearth/itemcollection-heuristic c40ced1b Merge pull request #1137 from radiantearth/python_ci_validation 036f2918 CHANGELOG entry added 2e60c61e Remove ci job from workflow 1b2fefb8 Removed ItemCollection from STAC detection heuristic, may get invalid soon. https://github.com/radiantearth/stac-api-spec/issues/141 9badaa78 Comment out python ci validation d584c282 updated versions and changelog to 1.0.0 517c5fb2 Merge pull request #1134 from radiantearth/updated_uml 59434f32 updated UML allowing collection children in catalogs ef679f4f update UML to remove summaries from catalogs c197acb3 Merge pull request #1133 from radiantearth/title-rec 45d0789f Merge pull request #1131 from jbants/dev 24ec4a28 added best practice to include title, updated examples 8b14d60d Added clarity around CI tests d811ae24 Ci validation (#2) 4ecbabcc Merge pull request #1128 from radiantearth/dev 73638442 Merge pull request #1129 from radiantearth/changelog-fixes 8f35589c minor changelog fixes 09f00194 Merge pull request #1119 from radiantearth/version-rc4 21e12d53 Updated CHANGELOG e7d008f2 Merge remote-tracking branch 'origin/dev' into version-rc4 00243b3e Update date in changelog f45da2c0 Merge remote-tracking branch 'origin/dev' into version-rc4 a553f116 Merge pull request #1127 from radiantearth/issue-1125 ba9df2eb Update collection-spec.md b43d16b5 Merge pull request #1123 from lossyrob/clarification/rde/collection-extension 90a2cbd6 line break for linting bf02447c updates from PR review 0a6da1ac Collection: Open intervals for temporal extents #1125 c9323e41 line break for linting b24066e1 update from PR review ea3ee26b reorg and edits 59a8cf4a Update CHANGELOG cd08d4a5 Fix TOC ordering in extensions d87813dd Clarify when to include extensions in stac_extensions 507a813c update version numbers bb7cb80c merged dev 7be086cb Merge pull request #1122 from radiantearth/catalog-summaries-removal 9fa29ce5 Update catalog-spec/json-schema/catalog.json 9fb18e73 Merge pull request #1121 from radiantearth/remove-core-ext-schema 42753fc5 Catalogs don't support summaries any more 7406ed7e Merge remote-tracking branch 'origin/dev' into remove-core-ext-schema 50d12f6c JSON Schemas don't allow "shortcuts" for core extensions any longer 56205ea4 updated version numbers / changelog 08fe31dc Merge pull request #1116 from jjrom/dev 4b77ef8b updated changelog 3f9d9acb Merge pull request #1114 from radiantearth/emmanuelmathot-patch-1 34a7c0e4 fixed schema to reflect intent, update language 2f81fcc3 Allow empty catalogs 5c48425e add missing projection declaration f81324f7 Merge pull request #1113 from radiantearth/dev 2f0a35a9 Merge pull request #1112 from radiantearth/version-changes-rc3 094b90d6 fixed linking issue d6103b82 update versions, changelog edits b3985367 Merge pull request #1111 from radiantearth/foundations-overview 06aea1c5 less characters in a line dfeaf64c more fixes from PR feedback 17c14932 updates from PR review 3a3cc099 updated changelog with pr link 20f7e716 new overview section 311a9a3b Merge pull request #1110 from radiantearth/bp-clarification 431e0af5 Update best-practices.md c5d96a8e Merge pull request #1109 from radiantearth/catalog-json-schema c3977670 best practices: some clarifications #1108 9abb6e61 Catalog: Require child / item in JSON Schema #1100 89923bf0 Merge pull request #1107 from fredliporace/summaries 13bec824 Update collection-spec/collection-spec.md 6181a350 Merge branch 'dev' into summaries 31e9f91c Apply suggestions from code review 5b5a8bd5 Merge pull request #1093 from radiantearth/json-schema-summaries a6f838ae Merge pull request #1101 from radiantearth/root-parent 3e3677c6 fix from PR review e5060dc9 Update collection-spec/collection-spec.md 4e4a7f7a Filled out 3rd option for summaries d6062ffd Merge pull request #1105 from radiantearth/gsd-tweaks 91a435e4 Merge pull request #1104 from radiantearth/no-scientific-1093 7762cca3 Fixing .md link 16b4b428 Changelog for issue #1106 457a5d57 Rephrase summaries; exception for summary schema; typo 476e1796 Update CHANGELOG.md ab58068f Update CHANGELOG.md 6fb42d9b update changelog 5890f7ec clarifications on using gsd as an asset field 7d3a94a8 Merge branch 'dev' into json-schema-summaries 0d8916ff remove scientific extension ee2ca2e4 Merge pull request #1102 from bradh/common_metadata_2021-04-25 ea23b295 minor editorial cleanup for common-metadata 314cfa13 include parent and root rel types, even for same file d42e3c15 Merge pull request #1092 from radiantearth/preview-rel-type 59ad837e Merge branch 'dev' into preview-rel-type b8d8b5e2 Merge pull request #1099 from radiantearth/utc 14fec945 More emphasis on UTC requirement 1f3b72b8 All timestamps in UTC #1095 dfb2300b Merge pull request #1091 from radiantearth/clarify-root b74d5b8c Better integrate example e2579282 Couple of changes for better alignment between the specs + list formatting 54d7be49 updates from review comments b388c25f Merge branch 'dev' into clarify-root 091af689 Merge pull request #1097 from bradh/patch-1 59df72d0 Some more details 233b757c Minor change to Collection usage wording eb7b9d29 Attempt to provide the main text ee8022b2 Example (WIP), more details 40ab9549 Update collection-spec/collection-spec.md 6755d41c First try at supporting JSON Schema in summaries #1045 6b24f7c9 put note up top 60bf0bcc more updates 5a994b7d Update overview.md 04664cbe Add preview relation type #1090 2be5e040 got rid of old changes that snuck in d0336de8 updates to make clear root can be catalog or collection bb15ca66 Merge pull request #1080 from radiantearth/fix_examples 16e80e96 Update examples/collection.json 1f9c4b06 Merge pull request #1086 from radiantearth/markdown 2daab18f Fix CI 77577a1b Merge branch 'dev' into fix_examples 2142d4b5 Merge pull request #1078 from radiantearth/rephrase-stac-extensions 6405e0d0 Merge branch 'dev' into fix_examples 9b438b64 Merge branch 'dev' into rephrase-stac-extensions 9f866c03 Merge branch 'dev' into markdown 40dfb8bf Merge pull request #1085 from radiantearth/extent-clarification 6a5a1535 Merge pull request #1087 from radiantearth/validate 93765a55 Made requirement for collection fields / rels clearer 82d3a229 Merge pull request #1089 from radiantearth/fix-collectionless-item 840a4cfc Remove collection field 9c6be4b5 Collectionless item has collection field & link #1081 6c8df300 Item: Validate that collection property is set if link with collection rel type is available #1088 90f8d37b Update STAC Node Validator and fix examples 6f572989 Fix Markdown be40027d Clarification on multiple bounding boxes in an extent. #1064 https://github.com/opengeospatial/ogcapi-features/pull/520 6b1bd4c3 fix complete 8617240e Update catalog-spec/catalog-spec.md 21ecb16d Update collection-spec/collection-spec.md 53686bdc simple item has no title 50d48628 stac_extensions for summarized fields 1ad7d1bc updated changelog a8354e12 datetime extent in collection 0684c3aa align spatio-temporal extents bcb5d962 removed duplicate the d73b43f9 Clarified that stac_extensions should also list extensions that are used in Collection summaries git-subtree-dir: stac-spec git-subtree-split: 789e90fba4c7119bc925d26f75f47bacf44c91e6 --- .circleci/config.yml | 17 +- .circleci/rc.yaml | 21 +- .github/pull_request_template.md | 6 +- .remarkignore | 1 - CHANGELOG.md | 146 ++++++--- CODE_OF_CONDUCT.md | 20 +- CONTRIBUTING.md | 14 +- README.md | 59 ++-- STAC-UML.drawio | 2 +- STAC-UML.pdf | Bin 46890 -> 78979 bytes best-practices.md | 185 +++++++----- catalog-spec/README.md | 14 +- catalog-spec/catalog-spec.md | 20 +- catalog-spec/json-schema/catalog-core.json | 139 --------- catalog-spec/json-schema/catalog.json | 77 ++++- collection-spec/README.md | 7 +- collection-spec/collection-spec.md | 190 ++++++++---- collection-spec/json-schema/collection.json | 140 +++++++-- examples/README.md | 4 +- examples/catalog.json | 17 +- .../collection-with-schemas.json | 277 ++++++++++++++++++ examples/collection-only/collection.json | 19 +- examples/collection.json | 55 ++-- examples/collectionless-item.json | 27 +- examples/core-item.json | 16 +- examples/extended-item.json | 22 +- .../extensions-collection/collection.json | 16 +- .../proj-example/proj-example.json | 15 +- examples/simple-item.json | 11 +- extensions/README.md | 87 ++++-- item-spec/common-metadata.md | 69 +++-- item-spec/item-spec.md | 80 +++-- item-spec/json-schema/basics.json | 2 +- item-spec/json-schema/datetime.json | 17 +- item-spec/json-schema/instrument.json | 2 +- item-spec/json-schema/item.json | 60 ++-- item-spec/json-schema/licensing.json | 2 +- item-spec/json-schema/provider.json | 2 +- overview.md | 59 +++- package.json | 4 +- principles.md | 20 +- process.md | 53 ++-- schema.json | 0 43 files changed, 1346 insertions(+), 648 deletions(-) delete mode 100644 .remarkignore delete mode 100644 catalog-spec/json-schema/catalog-core.json create mode 100644 examples/collection-only/collection-with-schemas.json delete mode 100644 schema.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 7095d1c8..60cd9efe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2 jobs: - test_examples: + test_examples_node: working_directory: ~/stac docker: - image: circleci/node:12 @@ -12,6 +12,18 @@ jobs: - run: name: validate command: npm run check-examples + # test_examples_python: + # working_directory: ~/stac + # docker: + # - image: circleci/python:3.8 + # steps: + # - checkout + # - run: + # name: install + # command: pip install stac-validator + # - run: + # name: validate + # command: find ./examples -type f -name "*.json" | xargs -L1 stac_validator test_docs: working_directory: ~/stac docker: @@ -43,7 +55,8 @@ workflows: version: 2 ci: jobs: - - test_examples + - test_examples_node + # - test_examples_python - test_docs - publish_schemas: filters: diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index 625a856a..b0dd538c 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -4,17 +4,15 @@ plugins: # Apply some recommended defaults for consistency - remark-preset-lint-consistent - remark-preset-lint-recommended -# No HTML for security - can't activate yet due to STAC logo in README.md -# - lint-no-html + - lint-no-html # General formatting -# - - remark-lint-emphasis-marker -# - '*' + - - remark-lint-emphasis-marker + - '*' - remark-lint-hard-break-spaces - remark-lint-blockquote-indentation - remark-lint-no-consecutive-blank-lines -# Detect overly long lines - be liberal for now and don't restrict to 80 yet -# - - remark-lint-maximum-line-length -# - 150 + - - remark-lint-maximum-line-length + - 150 # Code - remark-lint-fenced-code-flag - remark-lint-fenced-code-marker @@ -23,24 +21,23 @@ plugins: - 'fenced' # Headings - remark-lint-heading-increment - - remark-lint-no-duplicate-headings - remark-lint-no-multiple-toplevel-headings - remark-lint-no-heading-punctuation - - remark-lint-maximum-heading-length - 70 - - remark-lint-heading-style - atx + - - remark-lint-no-shortcut-reference-link + - false # Lists - remark-lint-list-item-bullet-indent - remark-lint-ordered-list-marker-style - remark-lint-ordered-list-marker-value - remark-lint-checkbox-character-style -# - - remark-lint-unordered-list-marker-style -# - '-' + - - remark-lint-unordered-list-marker-style + - '-' - - remark-lint-list-item-indent - space # Tables - remark-lint-table-pipes -# - remark-lint-table-pipe-alignment # Wait for https://github.com/remarkjs/remark-lint/issues/226 -# Urls - remark-lint-no-literal-urls \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e26fc2c9..bd3b2f95 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,5 +10,7 @@ - [ ] This PR is made against the dev branch (all proposed changes except releases should be against dev, not master). - [ ] This PR has **no** breaking changes. -- [ ] I have added my changes to the [CHANGELOG](https://github.com/radiantearth/stac-spec/blob/dev/CHANGELOG.md) **or** a CHANGELOG entry is not required. -- [ ] This PR affects the [STAC API spec](https://github.com/radiantearth/stac-api-spec), and I have opened issue/PR #XXX to track the change. +- [ ] I have added my changes to the [CHANGELOG](https://github.com/radiantearth/stac-spec/blob/dev/CHANGELOG.md) + **or** a CHANGELOG entry is not required. +- [ ] This PR affects the [STAC API spec](https://github.com/radiantearth/stac-api-spec), + and I have opened issue/PR #XXX to track the change. diff --git a/.remarkignore b/.remarkignore deleted file mode 100644 index 32524460..00000000 --- a/.remarkignore +++ /dev/null @@ -1 +0,0 @@ -/CHANGELOG.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8027a1..47bf6e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ + # Changelog All notable changes to this project will be documented in this file. @@ -6,6 +7,56 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.0.0] - 2021-05-25 + +### Added + +- Updated best practices to add a recommendation to include title in links where possible. ([#1133](https://github.com/radiantearth/stac-spec/pull/1133)) + +### Changed + +- Updated version numbers for 1.0.0 release. +- Final UML tweaks for latest changes. ([#1134](https://github.com/radiantearth/stac-spec/pull/1134)) +- Removed ItemCollection from the STAC detection heuristic in Best Practices. It can't easily be differentiated from GeoJSON FeatureCollections any longer. ([API#141](https://github.com/radiantearth/stac-api-spec/issues/141)) + +## [v1.0.0-rc.4] - 2021-05-11 + +### Changed + +- An empty Catalog is now allowed, removed the requirement that it must have a child or item link. ([#1115](https://github.com/radiantearth/stac-spec/issues/1115)) +- An open date range to both sides is now allowed in the Collection's temporal extents. ([#1125](https://github.com/radiantearth/stac-spec/issues/1125)) +- Catalog and Collection JSON Schemas don't have a common JSON Schema any more. ([#1122](https://github.com/radiantearth/stac-spec/pull/1122)) + +### Removed + +- Catalogs don't support summaries any more. ([#1122](https://github.com/radiantearth/stac-spec/pull/1122)) + +### Fixed + +- Added clarification around when an extension should be included in `stac_extensions`. ([#1123](https://github.com/radiantearth/stac-spec/pull/1123)) +- JSON Schemas don't allow "shortcuts" for core extensions any longer. ([#1121](https://github.com/radiantearth/stac-spec/pull/1121)) +- Various examples fixes. + +## [v1.0.0-rc.3] - 2021-04-29 + +### Added + +- Summaries are allowed to specify JSON Schema in addition to ranges and sets of values. ([#1045](https://github.com/radiantearth/stac-spec/issues/1045)) +- Added `preview` relation type for interoperable thumbnails to best practices. ([#1090](https://github.com/radiantearth/stac-spec/issues/1090)) +- Recommendation to include both `root` and `parent` relation types when they point at the same file. ([#1098](https://github.com/radiantearth/stac-spec/issues/1098)) +- Overview section linking to various foundational standards. ([#1111](https://github.com/radiantearth/stac-spec/pull/1111)) + +### Changed + +- The first extent in a Collection is always the overall extent, followed by more specific extents. ([#1064](https://github.com/radiantearth/stac-spec/issues/1064), [opengeospatial/ogcapi-features#520](https://github.com/opengeospatial/ogcapi-features/pull/520)) +- Updated examples for automatic collection creation from code and validation ([#1080](https://github.com/radiantearth/stac-spec/pull/1080)) +- Clarified that stac_extensions should also list extensions that are used in Collection summaries. ([#1077](https://github.com/radiantearth/stac-spec/issues/1077)) +- The Stats Object for Summaries has been renamed to Range Object (no functional change). ([#1093](https://github.com/radiantearth/stac-spec/pull/1093)) +- `changed`, `created` (common metadata) and temporal extents (collections): Timestamps must be always in UTC ([#1095](https://github.com/radiantearth/stac-spec/issues/1095)) +- Clarified that collection summaries do not require that all property fields are summarized. ([#1106](https://github.com/radiantearth/stac-spec/issues/1106)) +- Clarified that gsd should only be used on an asset to represent the sensor, not just different processing. ([#1105](https://github.com/radiantearth/stac-spec/pull/1105)) +- Clarified that leaving a field out is not equivalent to setting it as null. ([#1111](https://github.com/radiantearth/stac-spec/pull/1111)) + ## [v1.0.0-rc.2] - 2021-03-30 ### Changed @@ -19,6 +70,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed +- Examples - Collection Assets were specified as required (only in written text, not in JSON Schema), but that was incorrectly copied over from the former `collection-assets` extension. Collection Assets are not required. - Clarified that the values in summaries (both for ranges and sets of values) must follow the data type of the property they summarize. ([#1069](https://github.com/radiantearth/stac-spec/pull/1069)) @@ -38,7 +90,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- The [Stats Object](collection-spec/collection-spec.md#stats-object) for Collection `summaries` changed `min` to `minimum` and `max` to `maximum` to align with JSON Schema. ([#967](https://github.com/radiantearth/stac-spec/pull/967)) +- The [Stats Object](collection-spec/collection-spec.md#range-object) for Collection `summaries` changed `min` to `minimum` and `max` to `maximum` to align with JSON Schema. ([#967](https://github.com/radiantearth/stac-spec/pull/967)) - URIs (usually found int properties like `href`, `url`) are now validated using the `iri-reference` format in JSON Schema (allows international characters in URIs) ([#953](https://github.com/radiantearth/stac-spec/pull/953)) - Enhanced the way the spec talks about ID's to encourage more global uniqueness. ([#883](https://github.com/radiantearth/stac-spec/pull/883)) - Clarified how collection-level asset object properties do not remove the need for item-level asset object properties in the `item-assets` extension ([#880](https://github.com/radiantearth/stac-spec/pull/880)) @@ -81,7 +133,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The API portion of STAC has been split off into a [new repository: stac-api-spec](https://github.com/radiantearth/stac-api-spec) and will start being versioned and released separately than the core STAC spec. - proj4 string from proj extension - Various warnings about how the spec is very early and likely will change. -- implementations.md (migrated to https://stacspec.org) and how-to-help.md (migrated to https://github.com/stac-utils/stac-ecosystem). +- implementations.md (migrated to ) and how-to-help.md (migrated to ). - `commons` extension completely removed: Items should contain all properties and not default to a common set at the Collection level - ItemCollection removed from stac-spec core repo, will migrate to [stac-api-spec](https://github.com/radiantearth/stac-api-spec) as that is the only place it is used. @@ -96,17 +148,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Instructions on how to run check-markdown locally - Timestamps extensions (adds fields `published`, `expires` and `unpublished`) - `created` and `updated` can be used in the assets to specify the creation / update times of the assets. -- [Tiled Assets extension](extensions/tiled-assets/README.md), for representing data that has been split into tiles +- [Tiled Assets extension](https://github.com/stac-extensions/tiled-assets/blob/main/README.md), for representing data that has been split into tiles ### Changed -- [Label extension](extensions/label/README.md) types were clarified and types in README and JSON schema were brought into alignment +- [Label extension](https://github.com/stac-extensions/label/blob/main/README.md) types were clarified and types in README and JSON schema were brought into alignment - Moved item recommendations to best practices, and added a bit more in item spec about 'search' - Moved `eo:gsd` from `eo` extension to core `gsd` field in Item common metadata - `asset` extension renamed to `item-assets` and renamed `assets` field in Collections to `item_assets` - `item-assets` extension only requires any two fields to be available, not the two specific fields `title` and `type` - `datetime` allows `null` as value, but requires `start_datetime` and `end_datetime` then - Extensions `sat`, `scientific` and `view`: At least one field is required to be specified. -- [Single File STAC extension](extensions/single-file-stac/README.md) changed to be a complete STAC catalog + GeoJSON FeatureCollection that contains collections and items. +- [Single File STAC extension](https://github.com/stac-extensions/single-file-stac/blob/main/README.md) changed to be a complete STAC catalog + GeoJSON FeatureCollection that contains collections and items. - Improved several JSON Schemas ### Fixed @@ -119,11 +171,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A `description` field has been added to Item assets (also Asset definitions extension) - Field `mission` to [Common Metadata fields](item-spec/common-metadata.md) - Extensions: - - [Version Indicators extension](extensions/version/README.md), new `version` and `deprecated` fields in STAC Items and Collections + - [Version Indicators extension](https://github.com/stac-extensions/version/blob/main/README.md), new `version` and `deprecated` fields in STAC Items and Collections - Data Cube extension can be used in Collections, added new field `description` - - [Asset Extension](extensions/asset/README.md): new `description` and `roles` fields - - New [Projection Extension](extensions/projection/README.md) to describe Items with Assets that have an associated geospatial projection - - New [View Geometry Extension](extensions/view/README.md) + - [Asset Extension](https://github.com/stac-extensions/item-assets/blob/main/README.md): new `description` and `roles` fields + - New [Projection Extension](https://github.com/stac-extensions/projection/blob/main/README.md) to describe Items with Assets that have an associated geospatial projection + - New [View Geometry Extension](https://github.com/stac-extensions/view/blob/main/README.md) - STAC API: - Added the [Item and Collection API Version extension](https://github.com/radiantearth/stac-api-spec/tree/master/extensions/version/README.md) to support versioning in the API specification - Run `npm run serve` or `npm run serve-ext` to quickly render development versions of the OpenAPI spec in the browser @@ -153,8 +205,8 @@ Item properties - `eo:sun_elevation` -> `view:sun_elevation` - Extensions: - Data Cube extension: Changed allowed formats (removed PROJ string, added PROJJSON / WKT2) for reference systems - - [Checksum extension](extensions/checksum/README.md) is now using self-identifiable hashes ([Multihash](https://github.com/multiformats/multihash)) - - Changed `sar:type` to `sar:product_type` and `sar:polarization` to `sar:polarizations` in the [SAR extension](extensions/sar/README.md) + - [Checksum extension](https://github.com/stac-extensions/checksum/blob/main/README.md) is now using self-identifiable hashes ([Multihash](https://github.com/multiformats/multihash)) + - Changed `sar:type` to `sar:product_type` and `sar:polarization` to `sar:polarizations` in the [SAR extension](https://github.com/stac-extensions/sar/blob/main/README.md) - STAC API: - The endpoint `/stac` has been merged with `/` - The endpoint `/stac/search` is now called `/search` @@ -170,16 +222,16 @@ fields. No change is required for STAC Items. - Clarified how `/search` links must be added to `/` and changed that links to both GET and POST must be provided now that the method can be specified in links ### Removed -- `version` field in STAC Collections. Use [Version Extension](extensions/version/README.md) instead +- `version` field in STAC Collections. Use [Version Extension](https://github.com/stac-extensions/version/blob/main/README.md) instead - `summaries` field from Catalogs. Use Collections instead - Asset Types (pre-defined values for the keys of individual assets, *not* media types) in Items. Use the asset's `roles` instead - `license` field doesn't allow SPDX expressions any longer. Use `various` and links instead - Extensions: - - `eo:platform`, `eo:instrument`, `eo:constellation` from EO extension, and `sar:platform`, `sar:instrument`, `sar:constellation` from the [SAR extension](extensions/sar/README.md) + - `eo:platform`, `eo:instrument`, `eo:constellation` from EO extension, and `sar:platform`, `sar:instrument`, `sar:constellation` from the [SAR extension](https://github.com/stac-extensions/sar/blob/main/README.md) - Removed from EO extension field `eo:epsg` in favor of `proj:epsg` - - `gsd` and `accuracy` from `eo:bands` in the [EO extension](extensions/eo/README.md) - - `sar:absolute_orbit` and `sar:center_wavelength` fields from the [SAR extension](extensions/sar/README.md) - - `data_type` and `unit` from the `sar:bands` object in the [SAR extension](extensions/sar/README.md) + - `gsd` and `accuracy` from `eo:bands` in the [EO extension](https://github.com/stac-extensions/eo/blob/main/README.md) + - `sar:absolute_orbit` and `sar:center_wavelength` fields from the [SAR extension](https://github.com/stac-extensions/sar/blob/main/README.md) + - `data_type` and `unit` from the `sar:bands` object in the [SAR extension](https://github.com/stac-extensions/sar/blob/main/README.md) - Datetime Range (`dtr`) extension. Use the [Common Metadata fields](item-spec/common-metadata.md) instead - STAC API: - `next` from the search metadata and query parameter @@ -213,11 +265,11 @@ fields. No change is required for STAC Items. ### Changed - Updated specification to base on WFS3 draft 2 (OGC API - Features - Part 1: Core, v1.0.0-draft.2). This leads to many changes in the API and one change in STAC collections, notably: - - The structure of the field `extent` in STAC and WFS Collections changed. - - Query parameter `time` was renamed to `datetime` and accepts slightly different values. - - WFS links have additional fields `hreflang` and `length`. - - WFS Collections have additional fields `crs` and `itemType`. - - `time` API parameter changed to `datetime` + - The structure of the field `extent` in STAC and WFS Collections changed. + - Query parameter `time` was renamed to `datetime` and accepts slightly different values. + - WFS links have additional fields `hreflang` and `length`. + - WFS Collections have additional fields `crs` and `itemType`. + - `time` API parameter changed to `datetime` - The API intersects parameter now accepts a GeoJSON Geometry (any type) *instead* of a GeoJSON Feature. - API: Clarification on `include` and `exclude` parameters in the field extension and notes on default values. - API: queries should contain either `bbox` or `intersects`. @@ -235,21 +287,21 @@ fields. No change is required for STAC Items. - Property `summaries` have been added to catalogs and collections. - API Transaction extension supports optimistic locking through use of the ETag header. - Asset Definition Extension added to Collections to allow specifying details about Assets that may appear in member Items. -- [Single File Catalog extension](extensions/single-file-stac/README.md) added as a format to have a set of Collections and Items in a single file. -- [Label extension](extensions/label/README.md) added with additional fields for describing labeled data, such as used for training data or from the output of a classification +- [Single File Catalog extension](https://github.com/stac-extensions/single-file-stac/blob/main/README.md) added as a format to have a set of Collections and Items in a single file. +- [Label extension](https://github.com/stac-extensions/label/blob/main/README.md) added with additional fields for describing labeled data, such as used for training data or from the output of a classification - Timestamp fields added to `Item`: `created` and `updated` to refer to the datetime the metadata file was created or updated. - Added Search Metadata API extension which adds fields to a response from a STAC API such as the number of items found and how many were returned. - ItemCollection class added to spec that is a GeoJSON FeatureCollection of Items, such as what would be returned from a search. Located in item directory. - `in` operator added to the query extension (to check if value is in a list of values) -- New bands added to the [common band names](extensions/eo/README.md#common-band-names) for the EO extension: yellow, rededge, and 2 narrow NIR bands -- [Scientific extension](extensions/scientific/README.md) can be used in Collections. +- New bands added to the [common band names](https://github.com/stac-extensions/eo/blob/main/README.md#common-band-names) for the EO extension: yellow, rededge, and 2 narrow NIR bands +- [Scientific extension](https://github.com/stac-extensions/scientific/blob/main/README.md) can be used in Collections. ### Fixed - Updated language, fixed typos and examples. - Renamed `pc:schema` to `pc:schemas` in the Point Cloud extension. ### Changes since 0.8.0rc1 -- [Label extension](extensions/label/README.md): +- [Label extension](https://github.com/stac-extensions/label/blob/main/README.md): - moved label:classes to be a list of Class Objects from a single Class Object in spec markdown and json schema (matching previous example JSON). - moved label:overview to be a list of Overview Objects from a single Overview Object in spec markdown and json schema (matching previous example JSON). - Renamed fields to use plural forms (`label:property` -> `label:properties`, `label:task` -> `label:tasks`, `label:method` -> `label:methods` and `label:overview` -> `label:overviews`) @@ -338,9 +390,9 @@ fields. No change is required for STAC Items. - **description**: Description fields now allow formatting with CommonMark. - **assets**: Fields changed names: `name` to `title` and `mime_type` to `type`. -### Removed: -* **provider**: Provider field in Items got removed. Use Collections or the Single Item extension instead. -* **license**: License field in Items got removed. Use Collections or the Single Item extension instead. +### Removed +- **provider**: Provider field in Items got removed. Use Collections or the Single Item extension instead. +- **license**: License field in Items got removed. Use Collections or the Single Item extension instead. ## [v0.5.2] - 2018-07-12 @@ -350,21 +402,21 @@ Minor bug fixes on 0.5.1 for the schema files. Thanks @francbartoli Minor bug fixes from 0.5.1 release -* [Update openapi / swagger specs for new 'links'](https://github.com/radiantearth/stac-spec/commit/480d4fb02b4a7e880c7ca01320fe2773260ba595) -* [minor fixes on collection extension](https://github.com/radiantearth/stac-spec/pull/124) - thanks @m-mohr -* [minor cbers example updates](https://github.com/radiantearth/stac-spec/pull/123) - thanks @fredliporace +- [Update openapi / swagger specs for new 'links'](https://github.com/radiantearth/stac-spec/commit/480d4fb02b4a7e880c7ca01320fe2773260ba595) +- [minor fixes on collection extension](https://github.com/radiantearth/stac-spec/pull/124) - thanks @m-mohr +- [minor cbers example updates](https://github.com/radiantearth/stac-spec/pull/123) - thanks @fredliporace ## [v0.5.0] - 2018-07-01 The 0.5.0 release of the STAC spec is an iteration forward on the spec, with a number of core improvements. Highlights include: -* **Links is now a dictionary** - This is the most core change done. It aligns the structure with the 'asset' change in 0.5.0, making it easier for clients to look up the link that they want more easily. The schema is updated to this (and actually checks assets better now, thanks @mojodna ) +- **Links is now a dictionary** - This is the most core change done. It aligns the structure with the 'asset' change in 0.5.0, making it easier for clients to look up the link that they want more easily. The schema is updated to this (and actually checks assets better now, thanks @mojodna ) -* **Transactions Extension** - There is now a transaction extension for the STAC API, thanks to @hgs-msmith and @hgs-trutherford +- **Transactions Extension** - There is now a transaction extension for the STAC API, thanks to @hgs-msmith and @hgs-trutherford -* **Collections iterations** @matthewhanson has evolved the collections extension, adding in some namespace type hints on it, and explaining it more clearly. +- **Collections iterations** @matthewhanson has evolved the collections extension, adding in some namespace type hints on it, and explaining it more clearly. -* **eo:crs to eo:epsg** In the EO profile @matthewhanson brought in a change to use EPSG code, instead of full Well Known Text, to make it easy to reference. +- **eo:crs to eo:epsg** In the EO profile @matthewhanson brought in a change to use EPSG code, instead of full Well Known Text, to make it easy to reference. Full list of issues and pull requests at @@ -372,10 +424,10 @@ Full list of issues and pull requests at +[v1.0.0]: +[v1.0.0-rc.4]: +[v1.0.0-rc.3]: [v1.0.0-rc.2]: [v1.0.0-rc.1]: [v1.0.0-beta.2]: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b3776240..984c77e6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,21 +14,21 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or +- The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fd9042d..5b9b2d7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,12 +24,12 @@ require you to switch from the default of 'master', which we keep so it displays Creating a Pull Request will show our PR template, which includes checkbox reminders for a number of things. -* Adding an entry the [CHANGELOG](CHANGELOG.md). If the change is more editorial and minor then this +- Adding an entry the [CHANGELOG](CHANGELOG.md). If the change is more editorial and minor then this is not required, but any change to the actual specification should definitely have one. -* Base the PR against dev, as mentioned above - even if the branch was made off of dev this reminds +- Base the PR against dev, as mentioned above - even if the branch was made off of dev this reminds you to change the base in GitHub's PR creation page. -* Make a ticket in the STAC API repo if anything here affects there. -* Highlight if the PR makes breaking changes to the specification (in beta there can still be +- Make a ticket in the STAC API repo if anything here affects there. +- Highlight if the PR makes breaking changes to the specification (in beta there can still be select breaking changes, but after 1.0 this will change) All pull requests should submit clean markdown, which is checked by the continuous integration @@ -37,7 +37,8 @@ system. Please use `npm run check` locally, as described in the [next section](# to ensure that the checks on the pull request succeed. If it does not then you can look at the mistakes online, which are the same as running `npm run check` locally would surface. -All pull requests that modify or create JSON schema files or examples should use [JSON formatter](https://jsonformatter.org/) to keep files consistent across the repo. +All pull requests that modify or create JSON schema files or examples should use +[JSON formatter](https://jsonformatter.org/) to keep files consistent across the repo. All pull requests additionally require a review of two STAC core team members. Releases are cut from dev to master (and require 3 approvals), see the [process](process.md) document for more details. @@ -45,7 +46,8 @@ from dev to master (and require 3 approvals), see the [process](process.md) docu ### Check files The same check-markdown and check-examples programs that runs as a check on PR's is part of the repo and can be run locally. -To install you'll need npm, which is a standard part of any [node.js installation](https://nodejs.org/en/download/). Alternatively, you can also use [yarn](https://yarnpkg.com/) instead of npm. In this case replace all occurrences of `npm` with `yarn` below. +To install you'll need npm, which is a standard part of any [node.js installation](https://nodejs.org/en/download/). +Alternatively, you can also use [yarn](https://yarnpkg.com/) instead of npm. In this case replace all occurrences of `npm` with `yarn` below. First you'll need to install everything with npm once. Just navigate to the root of the stac-spec repo and on your command line run: diff --git a/README.md b/README.md index cbe1c3e3..70ea3246 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + stac-logo [![CircleCI](https://circleci.com/gh/radiantearth/stac-spec.svg?style=svg)](https://circleci.com/gh/radiantearth/stac-spec) @@ -13,7 +14,9 @@ including sources such as aircraft and drone and data such as hyperspectral opti synthetic aperture radar (SAR), video, point clouds, lidar, digital elevation models (DEM), vector, machine learning labels, and composites like NDVI and mosaics. STAC is intentionally designed with a minimal core and flexible -extension mechanism to support a broad set of use cases. +extension mechanism to support a broad set of use cases. This specification +has matured over the past several years, and is used in [numerous production +deployments](https://stacindex.org/catalogs). This is advantageous to providers of geospatial data, as they can simply use a well-designed, standard format and API without needing to design their own proprietary one. @@ -42,22 +45,15 @@ with a well-defined set of additional attributes ("foreign members"). The **STAC extends the **[OGC API - Features - Part 1: Core](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html)** with additional web service endpoints and object attributes. -## Stability Note - -This specification has matured over the past several years, and is used in -[numerous production deployments](https://stacindex.org/catalogs). -With the 1.0.0 release, implementors should expect that most definitions will remain -stable. Our goal -is to maintain backwards-compatiblity within the core for a long time. -The STAC specification follows [Semantic Versioning](https://semver.org/), so once -1.0.0 is reached, any breaking change will require the spec to go to 2.0.0. - ## Current version and branches The [master branch](https://github.com/radiantearth/stac-spec/tree/master) is the 'stable' -version of the spec. It is currently version **1.0.0** of the specification. The -[dev](https://github.com/radiantearth/stac-spec/tree/dev) branch is where active development takes place, -and may have inconsistent examples. Whenever dev stabilizes, a release is cut and we +version of the spec. It is currently version **1.0.0** of the specification. The STAC specification +follows [Semantic Versioning](https://semver.org/), so any breaking change will require the spec to +go to 2.0.0. + +The [dev](https://github.com/radiantearth/stac-spec/tree/dev) branch is where active development +takes place, and may have inconsistent examples. Whenever dev stabilizes, a release is cut and we merge `dev` in to `master`. So `master` should be stable at any given time. More information on how the STAC development process works can be found in [process.md](process.md). @@ -83,23 +79,24 @@ that enable clients to search for Item objects that match their filtering criter The **Item**, **Catalog**, **Collection**, and **STAC API** specifications are intended to be used together, but are designed so each piece is small, self-contained, and reusable in other contexts. -* **[Overview](overview.md)** describes the three core object type specifications and how they relate to one another. -* **[Item Specification](item-spec/)** defines a STAC **Item**, which is a [GeoJSON](http://geojson.org) **Feature** -with additional fields ("foreign members") for attributes like time and links to related entities and assets -(including thumbnails). This is the core entity that describes the data to be discovered. -* **[Catalog Specification](catalog-spec/)** specifies a structure to link various STAC Items together to be crawled or browsed. It is a -simple, flexible JSON file of links to Items, Catalogs or Collections that can be used in a variety of ways. -* **[Collection Specification](collection-spec/)** provides additional information about a spatio-temporal collection of data. -In the context of STAC it is most likely a related group of STAC Items that is made available by a data provider. -It includes things like the spatial and temporal extent of the data, the license, keywords, etc. -It enables discovery at a higher level than individual Item objects, providing a simple way to describe sets of data. -* **[Examples](examples/):** The *[examples/](examples/)* folder contains examples for all three specifications, linked together to form two -complete examples. Each spec and extension links in to highlight particular files that demonstrate key concepts. -* **[Extensions](extensions/README.md)** describe how STAC can use extensions that extend the functionality of the core spec or -add fields for specific domains. Extensions can be published anywhere, although the preferred location for public extensions is in the [GitHub `stac-extensions` organization](https://github.com/stac-extensions). -* **Additional documents:** The supporting documents include a complementary [best practices](best-practices.md) -document, and information on contributing (links in the next section). We also maintain a [changelog](CHANGELOG.md) of -what was modified in each version. +- **[Overview](overview.md)** describes the three core object type specifications and how they relate to one another. +- **[Item Specification](item-spec/)** defines a STAC **Item**, which is a [GeoJSON](http://geojson.org) **Feature** + with additional fields ("foreign members") for attributes like time and links to related entities and assets + (including thumbnails). This is the core entity that describes the data to be discovered. +- **[Catalog Specification](catalog-spec/)** specifies a structure to link various STAC Items together to be crawled or browsed. It is a + simple, flexible JSON file of links to Items, Catalogs or Collections that can be used in a variety of ways. +- **[Collection Specification](collection-spec/)** provides additional information about a spatio-temporal collection of data. + In the context of STAC it is most likely a related group of STAC Items that is made available by a data provider. + It includes things like the spatial and temporal extent of the data, the license, keywords, etc. + It enables discovery at a higher level than individual Item objects, providing a simple way to describe sets of data. +- **[Examples](examples/):** The *[examples/](examples/)* folder contains examples for all three specifications, linked together to form two + complete examples. Each spec and extension links in to highlight particular files that demonstrate key concepts. +- **[Extensions](extensions/README.md)** describe how STAC can use extensions that extend the functionality of the core spec or + add fields for specific domains. Extensions can be published anywhere, + although the preferred location for public extensions is in the [GitHub `stac-extensions` organization](https://github.com/stac-extensions). +- **Additional documents:** The supporting documents include a complementary [best practices](best-practices.md) + document, and information on contributing (links in the next section). We also maintain a [changelog](CHANGELOG.md) of + what was modified in each version. ## Contributing diff --git a/STAC-UML.drawio b/STAC-UML.drawio index fca34d35..1fcde668 100644 --- a/STAC-UML.drawio +++ b/STAC-UML.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/STAC-UML.pdf b/STAC-UML.pdf index c3e02556007c08643fd135354dab0dc088fd2f07..37e7dff225f90311ada0c7231935a9da7532cc67 100644 GIT binary patch literal 78979 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.json`. -4. Sub-Catalogs should be stored in subdirectories of their parent (and only 1 subdirectory deeper than a document's parent) (e.g. `.../sample/sub1/catalog.json`). -5. Items should be stored in subdirectories of their parent Catalog. -This means that each Item and its assets are contained in a unique subdirectory. -6. Limit the number of Items in a Catalog or sub-Catalog, grouping / partitioning as relevant to the dataset. -7. Use structural elements (Catalog and Collection) consistently across each 'level' of your hierarchy. For example, if levels 2 and 4 of the hierarchy only contain Collections, -don't add a Catalog at levels 2 and 4. +4. Sub-Catalogs or sub-Collections should be stored in subdirectories of their parent + (and only 1 subdirectory deeper than a document's parent, e.g. `.../sample/sub1/catalog.json`). +5. Items should be stored in subdirectories of their parent Catalog or Collection. + This means that each Item and its assets are contained in a unique subdirectory. +6. Limit the number of Items in a Catalog or Collection, grouping / partitioning as relevant to the dataset. +7. Use structural elements (Catalog and Collection) consistently across each 'level' of your hierarchy. + For example, if levels 2 and 4 of the hierarchy only contain Collections, + don't add a Catalog at levels 2 and 4. + +One further recommendation to help tools is to always include the 'title' field when including a link, especially in the +`item`, `child`, `parent` and `root` links, even if it repeats several times. This should be the same as the 'title' in the +link destination. Having this enables clients to display a nice human readable name of the link without having to open the +link destination. #### Dynamic Catalog Layout @@ -466,7 +499,7 @@ different sub-catalog organization structures. For example one catalog could div by providers, and users could browse down to both. The leaf Items should just be linked to in a single canonical location (or at least use a rel link that indicates the location of the canonical one). It is recommended that dynamic catalogs provide multiple 'views' to allow users to navigate in a way that makes sense to them, providing multiple 'sub-catalogs' -from the root Catalog that enable different paths to browse (country/state, date/time, constellation/satellite, etc). But the +from the root that enable different paths to browse (country/state, date/time, constellation/satellite, etc). But the canonical 'rel' link should be used to designate the primary location of the Item to search engine crawlers. #### Mixing STAC Versions @@ -487,31 +520,34 @@ a bit of a 'curated' view. Some general thinking on what to summarize is as follows: -* Any field that is a range of data (like numbers or dates) is a great candidate to summarize, to give people a sense what values +- Any field that is a range of data (like numbers or dates) is a great candidate to summarize, to give people a sense what values the data might be. For example in overhead imagery, a -[`view:off_nadir`](https://github.com/stac-extensions/view/blob/main/README.md#item-properties-and-item-asset-fields) with a range of 0 to 3 would tell people this -imagery is all pretty much straight down, while a value of 15 to 40 would tell them that it's oblique imagery, or 0 to 60 that it's +[`view:off_nadir`](https://github.com/stac-extensions/view/blob/main/README.md#item-properties-and-item-asset-fields) +with a range of 0 to 3 would tell people this imagery is all pretty much straight down, +while a value of 15 to 40 would tell them that it's oblique imagery, or 0 to 60 that it's a Collection with lots of different look angles. -* Fields that have only one or a handful of values are also great to summarize. Collections with a single satellite may +- Fields that have only one or a handful of values are also great to summarize. Collections with a single satellite may use a single [`gsd`](item-spec/common-metadata.md#instrument) field in the summary, and it's quite useful for users to know that all data is going to be the same resolution. Similarly it's useful to know the names of all the [`platform` values](item-spec/common-metadata.md#instrument) that are used in the Collection. -* It is less useful to summarize fields that have numerous different discrete values that can't easily be represented +- It is less useful to summarize fields that have numerous different discrete values that can't easily be represented in a range. These will mostly be string values, when there aren't just a handful of options. For example if you had a 'location' field that gave 3 levels of administrative region (like 'San Francisco, California, United States') to help people understand more intuitively where a shot was taken. If your Collection has millions of Items, or even hundreds, you don't want to include all the different location string values in a summary. -* Fields that consist of arrays are more of a judgement call. For example [`instruments`](item-spec/common-metadata.md#instrument) +- Fields that consist of arrays are more of a judgement call. For example [`instruments`](item-spec/common-metadata.md#instrument) is straightforward and recommended, as the elements of the array are a discrete set of options. On the other hand -[`proj:transform`](https://github.com/stac-extensions/projection/blob/main/README.md#projtransform) makes no sense to summarize, as the union of all the values +[`proj:transform`](https://github.com/stac-extensions/projection/blob/main/README.md#projtransform) +makes no sense to summarize, as the union of all the values in the array are meaningless, as each Item is describing its transform, so combining them would just be a bunch of random numbers. So if the values contained in the array are independently meaningful (not interconnected) and there aren't hundreds of potential values then it is likely a good candidate to summarize. -We do highly recommend including an [`eo:bands`](https://github.com/stac-extensions/eo/blob/main/README.md#eobands) summary if your Items implement `eo:bands`, +We do highly recommend including an [`eo:bands`](https://github.com/stac-extensions/eo/blob/main/README.md#eobands) +summary if your Items implement `eo:bands`, especially if it represents just one satellite or constellation. This should be a union of all the potential bands that you have in assets. It is ok to only add the summary at the Collection level without putting an explicit `eo:bands` summary at the `properties` level of an Item, since that is optional. This gives users of the Collection a sense of the sensor capabilities without @@ -537,7 +573,7 @@ able to use it on their local computer, so all links need to be relative. Or a t without knowing the final location that it will live at online, so it isn't possible to set absolute 'self' URL's. These use cases should utilize a catalog that follows the listed principles: -* **Only relative href's in structural `links`**: The full catalog structure of links down to sub-catalogs and Items, and their +- **Only relative href's in structural `links`**: The full catalog structure of links down to sub-catalogs and Items, and their links back to their parents and roots, should be done with relative URL's. The structural rel types include `root`, `parent`, `child`, `item`, and `collection`. Other links can be absolute, especially if they describe a resource that makes less sense in the catalog, like [sci:doi](https://github.com/stac-extensions/scientific/blob/main/README.md#item-and-collection-fields), @@ -545,7 +581,7 @@ the catalog, like [sci:doi](https://github.com/stac-extensions/scientific/blob/m online location which makes more sense to refer to directly). This enables the full catalog to be downloaded or copied to another location and to still be valid. This also implies no `self` link, as that link must be absolute. -* **Use Asset `href` links consistently**: The links to the actual assets are allowed to be either relative or absolute. There +- **Use Asset `href` links consistently**: The links to the actual assets are allowed to be either relative or absolute. There are two types of 'self-contained catalogs'. #### Self-contained Metadata Only @@ -564,7 +600,9 @@ and used in other contexts. That catalog could be used offline, or even publishe Self-contained catalogs are not just for offline use, however - they are designed to be able to be published online and to live on the cloud in object storage. They just aim to ease the burden of publishing, by not requiring lots of updating of links. -Adding a single `self` link at the root is recommended for online catalogs, turning it into a 'relative published catalog', as detailed below. This anchors it in an online location and enables provenance tracking. +Adding a single `self` link at the root is recommended for online catalogs, +turning it into a 'relative published catalog', as detailed below. +This anchors it in an online location and enables provenance tracking. #### Published Catalogs @@ -586,9 +624,9 @@ implement it. #### Relative Published Catalog This is a self-contained catalog as described above, except it includes an absolute `self` link at -the root catalog, to identify its online location. This is designed so that a self-contained catalog (of either type, with its +the root to identify its online location. This is designed so that a self-contained catalog (of either type, with its assets or just metadata) can be 'published' online -by just adding one field (the self link) to its root catalog. All the other links should remain the same. The resulting catalog +by just adding one field (the self link) to its root (Catalog or Collection). All the other links should remain the same. The resulting catalog is no longer compliant with the self-contained catalog recommendations, but instead transforms into a 'relative published catalog'. With this, a client may resolve Item and sub-catalog self links by traversing parent and root links, but requires reading multiple sources to achieve this. @@ -605,17 +643,23 @@ with the `type` field) to communicate the structure and content of related entit Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) as much as possible. The following table describes a number of the common official relations that are used in production STAC implementations. -| Type | Description | -| ------------ | ------------------------------------------------------------ | +| Type | Description | +| --------- | ------------------------------------------------------------ | | alternate | It is recommended that STAC Items are also available as HTML, and should use this rel with `"type" : "text/html"` to tell clients where they can get a version of the Item or Collection to view in a browser. See [STAC on the Web in Best Practices](#stac-on-the-web) for more information. | | canonical | The URL of the [canonical](https://en.wikipedia.org/wiki/Canonical_link_element) version of the Item or Collection. API responses and copies of catalogs should use this to inform users that they are direct copy of another STAC Item, using the canonical rel to refer back to the primary location. | | via | The URL of the source metadata that this STAC Item or Collection is created from. Used similarly to canonical, but refers back to a non-STAC record (Landsat MTL, Sentinel tileInfo.json, etc) | -| prev | Indicates that the link's context is a part of a series, and that the previous in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs. | -| next | Indicates that the link's context is a part of a series, and that the next in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs. | +| prev | Indicates that the link's context is a part of a series, and that the previous in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs/Collections. | +| next | Indicates that the link's context is a part of a series, and that the next in the series is the link target. Typically used in STAC by API's, to return smaller groups of Items or Catalogs/Collections. | +| preview | Refers to a resource that serves as a preview (see [RFC 6903, sec. 3](https://tools.ietf.org/html/rfc6903#section-3)), usually a lower resolution thumbnail. In STAC this would usually be the same URL as the [thumbnail](#thumbnail) asset, but adding it as a link in addition enables OGC API clients that can't read assets to make use of it. It also adds support for thumbnails to STAC Catalogs as they can't list assets. | + +Being liberal with the `links` also means that it's ok to have repeated links with the same `href`. For example the +`parent` and `root` relation types will point at the same file when the child is directly below the root, and it is +recommended to include both. ### Versioning for Catalogs -In the Item and Collection STAC JSON, versions and deprecation can be indicated with the [Versioning Indicators Extension](https://github.com/stac-extensions/version). +In the Item and Collection STAC JSON, versions and deprecation can be indicated with the +[Versioning Indicators Extension](https://github.com/stac-extensions/version). The [Items and Collections API Version Extension](https://github.com/stac-extensions/version/) provides endpoints and semantics for keeping and accessing previous versions of Collections and Items. The same semantics can be used in static @@ -634,12 +678,14 @@ cycle is repeated: #### Example -When the record `my_item.json` is created, a copy of it is also created. `my_item.json` includes `permalink` to `my_item_01.json`. The version suffix of the file name is taken from the version field of the record when it is available. +When the record `my_item.json` is created, a copy of it is also created. `my_item.json` includes `permalink` to `my_item_01.json`. +The version suffix of the file name is taken from the version field of the record when it is available. - `root / collections / example_collection / items / my_item / my_item.json` - `root / collections / example_collection / items / my_item / my_item_01.json` -When `my_item.json` is updated, the new `my_item.json` includes a link to `my_item_01.json` and is also copied to `my_item_02.json`. This ensures that `my_item_02.json` includes a link to `my_item_01.json` +When `my_item.json` is updated, the new `my_item.json` includes a link to `my_item_01.json` and is also copied to `my_item_02.json`. +This ensures that `my_item_02.json` includes a link to `my_item_01.json` - `root / collections / example_collection / items / my_item / my_item.json` - `root / collections / example_collection / items / my_item / my_item_01.json` @@ -667,9 +713,11 @@ as everything but the links should be the same. #### Keep catalogs in sync with cloud notification and queue services -There is a set of emerging practices to use services like Amazon's Simple Queue Service (SQS) and Simple Notification Service -(SNS) to keep catalogs in sync. There is a great [blog post on the CBERS STAC implementation on AWS](https://aws.amazon.com/blogs/publicsector/keeping-a-spatiotemporal-asset-catalog-stac-up-to-date-with-sns-sqs/). The core -idea is that a static catalog should emit a notification whenever it changes. The recommendation for SNS is to use the STAC +There is a set of emerging practices to use services like Amazon's Simple Queue Service (SQS) +and Simple Notification Service (SNS) to keep catalogs in sync. +There is a great [blog post](https://aws.amazon.com/blogs/publicsector/keeping-a-spatiotemporal-asset-catalog-stac-up-to-date-with-sns-sqs/) +on the CBERS STAC implementation on AWS. +The core idea is that a static catalog should emit a notification whenever it changes. The recommendation for SNS is to use the STAC Item JSON as the message body, with some fields such as a scene’s datetime and geographic bounding box that allows basic geographic filtering from listeners. @@ -680,8 +728,7 @@ database, but it could just as easily be a server-based process. ## How to Differentiate STAC Files Any tool that crawls a STAC implementation or encounters a STAC file in the wild needs a clear way to determine if it is an Item, -Collection, Catalog or [ItemCollection](https://github.com/radiantearth/stac-api-spec/tree/v1.0.0-beta.1/fragments/itemcollection) -(part of the [STAC API spec](https://github.com/radiantearth/stac-api-spec/tree/v1.0.0-beta.1)). As of 1.0.0 this is done primarily +Collection or Catalog. As of 1.0.0 this is done primarily with the `type` field, and secondarily in Items with `stac_version`, or optionally the `rel` of the link to it. ```shell @@ -691,8 +738,6 @@ else if type is 'Catalog' => Catalog else if type is 'Feature' and stac_version is defined => Item -else if type is 'FeatureCollection' and stac_version is defined - => ItemCollection else => Invalid (JSON) ``` diff --git a/catalog-spec/README.md b/catalog-spec/README.md index a2626d22..d70881b6 100644 --- a/catalog-spec/README.md +++ b/catalog-spec/README.md @@ -7,12 +7,20 @@ in the [STAC Catalog Specification](catalog-spec.md). For more information on how the parts of STAC fit together see the [overview](../overview.md) document. -A Catalog is typically the "entry point" into a STAC object hierarchy. For example, the root endpoint ("landing page") of a STAC API implementation is a Catalog. For many static STAC catalogs (e.g., those defined only by a set of files on disk or in a cloud object store), the root URL points to a Catalog that acts as the starting point to traverse the entire catalog of Catalog, Collection, and Item objects. +A Catalog is typically the "entry point" into a STAC object hierarchy. +For example, the root endpoint ("landing page") of a STAC API implementation is a Catalog. +For many static STAC catalogs (e.g., those defined only by a set of files on disk or in a cloud object store), +the root URL points to a Catalog that acts as the starting point to traverse the entire catalog of Catalog, Collection, and Item objects. -While STAC Catalogs mostly describe a structure of links and Items, a key related specification is the [STAC Collection Specification](../collection-spec/collection-spec.md), +While STAC Catalogs mostly describe a structure of links and Items, +a key related specification is the [STAC Collection Specification](../collection-spec/collection-spec.md), which contains fields that further describe the group of Items in a Catalog. -A STAC Catalog requires a subset of the fields required by a Collection. These are distinguished from one another by the `type` field, which will have the value `Catalog` or `Collection`. This means that a Collection can be changed to a Catalog simply by changing this `type` field. The parent-child relationships among Catalogs and Collections are for objects of these types, as there is no subtyping relationship between the Collection and Catalog types, even through they share field names. +A STAC Catalog requires a subset of the fields required by a Collection. +These are distinguished from one another by the `type` field, which will have the value `Catalog` or `Collection`. +This means that a Collection can be changed to a Catalog simply by changing this `type` field. +The parent-child relationships among Catalogs and Collections are for objects of these types, +as there is no subtyping relationship between the Collection and Catalog types, even through they share field names. Catalogs are designed so that a simple file server on the web or object store like Amazon S3 can store JSON that defines a full Catalog. More dynamic services can also return a Catalog structure, and the [STAC API](https://github.com/radiantearth/stac-api-spec) diff --git a/catalog-spec/catalog-spec.md b/catalog-spec/catalog-spec.md index 032d85a3..8b26dce0 100644 --- a/catalog-spec/catalog-spec.md +++ b/catalog-spec/catalog-spec.md @@ -36,8 +36,9 @@ and fields to be compliant. This Catalog specification primarily defines a structure for information to be discoverable. Any use that is publishing a set of related spatiotemporal assets is strongly recommended to also use the STAC Collection specification to provide additional information about the set of Items -contained in a Catalog, in order to give contextual information to aid in discovery. Every STAC Collection is -also a valid STAC Catalog. +contained in a Catalog, in order to give contextual information to aid in discovery. +STAC Collections all have the same fields as STAC Catalogs, but with different allowed +values for `type` and `stac_extensions`. ## Catalog fields @@ -49,7 +50,6 @@ also a valid STAC Catalog. | id | string | **REQUIRED.** Identifier for the Catalog. | | title | string | A short descriptive one-line title for the Catalog. | | description | string | **REQUIRED.** Detailed multi-line description to fully explain the Catalog. [CommonMark 0.29](http://commonmark.org/) syntax MAY be used for rich text representation. | -| summaries | Map | A map of property summaries, either a set of values or statistics such as a range. More info in the [Collection spec](../collection-spec/collection-spec.md#summaries). | | links | [[Link Object](#link-object)] | **REQUIRED.** A list of references to other documents. | ### Additional Field Information @@ -61,9 +61,10 @@ In general, STAC versions can be mixed, but please keep the [recommended best pr #### stac_extensions A list of extensions the Catalog implements. -This list must only contain extensions that extend the Catalog itself, see the the 'Scope' column in the list of -extensions. This does NOT declare the extensions of child Catalog, Collection, or Item -objects. The list contains URLs to the JSON Schema files it can be validated against. +The list consists of URLs to JSON Schema files that can be used for validation. +This list must only contain extensions that extend the Catalog specification itself, +see the 'Scope' for each of the extensions. +This must **not** declare the extensions that are only implemented in child Collection objects or child Item objects. ### Link Object @@ -88,11 +89,12 @@ The following types are commonly used as `rel` types in the Link Object of a STA | ------- | ----------- | | self | STRONGLY RECOMMENDED. *Absolute* URL to the location that the Catalog file can be found online, if available. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | | root | STRONGLY RECOMMENDED. URL to the root STAC Catalog or [Collection](../collection-spec/README.md). Catalogs should include a link to their root, even if it's the root and points to itself. | -| parent | URL to the parent STAC Catalog or Collection. Non-root Catalogs should include a link to their parent. | -| child | URL to a child STAC Catalog or Collection. | +| parent | URL to the parent STAC entity (Catalog or Collection). Non-root Catalogs should include a link to their parent. | +| child | URL to a child STAC entity (Catalog or Collection). | | item | URL to a STAC Item. | -**Note:** A link to at least one `item` or `child` Catalog is **REQUIRED**. +**Note:** A link to at least one `item` or `child` (Catalog or Collection) is **RECOMMENDED**, but empty catalogs are +allowed if there is an intent to populate it or its children were removed. There are additional `rel` types in the [Using Relation Types](../best-practices.md#using-relation-types) best practice, but as they are more typically used in Collections, as Catalogs tend to just be used to structure STAC organization, so tend to just use diff --git a/catalog-spec/json-schema/catalog-core.json b/catalog-spec/json-schema/catalog-core.json deleted file mode 100644 index 503d7a66..00000000 --- a/catalog-spec/json-schema/catalog-core.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog-core.json#", - "title": "Common STAC Catalog + Collection Fields", - "description": "This object represents the common fields shared by Catalogs and Collections", - "allOf": [ - { - "$ref": "#/definitions/catalogCore" - } - ], - "definitions": { - "catalogCore": { - "title": "Catalog Core Fields", - "type": "object", - "required": [ - "stac_version", - "id", - "description", - "links" - ], - "properties": { - "stac_version": { - "title": "STAC version", - "type": "string", - "const": "1.0.0-rc.2" - }, - "stac_extensions": { - "title": "STAC extensions", - "type": "array", - "uniqueItems": true, - "items": { - "anyOf": [ - { - "title": "Reference to a JSON Schema", - "type": "string", - "format": "iri" - }, - { - "title": "Reference to a core extension", - "type": "string" - } - ] - } - }, - "id": { - "title": "Identifier", - "type": "string", - "minLength": 1 - }, - "title": { - "title": "Title", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string", - "minLength": 1 - }, - "links": { - "title": "Links", - "type": "array", - "items": { - "$ref": "#/definitions/link" - } - }, - "summaries": { - "$ref": "#/definitions/summaries" - } - } - }, - "link": { - "type": "object", - "required": [ - "rel", - "href" - ], - "properties": { - "href": { - "title": "Link reference", - "type": "string", - "format": "iri-reference", - "minLength": 1 - }, - "rel": { - "title": "Link relation type", - "type": "string", - "minLength": 1 - }, - "type": { - "title": "Link type", - "type": "string" - }, - "title": { - "title": "Link title", - "type": "string" - } - } - }, - "summaries": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "title": "Stats", - "type": "object", - "required": [ - "minimum", - "maximum" - ], - "properties": { - "minimum": { - "title": "Minimum value", - "type": [ - "number", - "string" - ] - }, - "maximum": { - "title": "Maximum value", - "type": [ - "number", - "string" - ] - } - } - }, - { - "title": "Set of values", - "type": "array", - "minItems": 1, - "items": { - "description": "Any data type could occur." - } - } - ] - } - } - } -} \ No newline at end of file diff --git a/catalog-spec/json-schema/catalog.json b/catalog-spec/json-schema/catalog.json index 0f2c110f..31b2c8f8 100644 --- a/catalog-spec/json-schema/catalog.json +++ b/catalog-spec/json-schema/catalog.json @@ -1,27 +1,92 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/catalog-spec/json-schema/catalog.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/catalog-spec/json-schema/catalog.json#", "title": "STAC Catalog Specification", "description": "This object represents Catalogs in a SpatioTemporal Asset Catalog.", "allOf": [ - { - "$ref": "catalog-core.json" - }, { "$ref": "#/definitions/catalog" } ], "definitions": { "catalog": { - "title": "Catalog", + "title": "STAC Catalog", "type": "object", "required": [ - "type" + "stac_version", + "type", + "id", + "description", + "links" ], "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, "type": { "title": "Type of STAC entity", "const": "Catalog" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "title": { + "title": "Title", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "minLength": 1 + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + } + } + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" } } } diff --git a/collection-spec/README.md b/collection-spec/README.md index 6b5a0233..c94bfc31 100644 --- a/collection-spec/README.md +++ b/collection-spec/README.md @@ -28,7 +28,8 @@ document. ## In this directory -**Specification:** The main STAC Collection specification is in *[collection-spec.md](collection-spec.md)*. It includes an overview and in depth explanation of the +**Specification:** The main STAC Collection specification is in *[collection-spec.md](collection-spec.md)*. +It includes an overview and in depth explanation of the structures and fields. **Schemas:** The schemas to validate the STAC Collection definition are found in the @@ -37,7 +38,9 @@ structures and fields. ## Collection Flexibility STAC Collections are defined for flexibility. They only require a handful of fields, and -implementors are free to add most any JSON field or object that they want via extensions. This flexibility and extensibility is a design goal, so that it is quite easy to implement a collection and be able to adapt it to most any data model. +implementors are free to add most any JSON field or object that they want via extensions. +This flexibility and extensibility is a design goal, so that it is quite easy to implement +a collection and be able to adapt it to most any data model. Implementors are encouraged to do what makes sense for them, and to check out the [examples](../examples/) and [other implementations](https://stacindex.org/catalogs) for current best practices. diff --git a/collection-spec/collection-spec.md b/collection-spec/collection-spec.md index f781bb91..8b6f4f15 100644 --- a/collection-spec/collection-spec.md +++ b/collection-spec/collection-spec.md @@ -16,7 +16,8 @@ - [Link Object](#link-object) - [Relation types](#relation-types) - [Asset Object](#asset-object) - - [Stats Object](#stats-object) + - [Range Object](#range-object) + - [JSON Schema Object](#json-schema-object) - [Media Type for STAC Collections](#media-type-for-stac-collections) - [Standalone Collections](#standalone-collections) @@ -27,17 +28,18 @@ Collection Specification shares all fields with the STAC [Catalog Specification] values for `type` and `stac_extensions`) and adds fields to describe the whole dataset and the included set of Items. Collections can have both parent Catalogs and Collections and child Items, Catalogs and Collections. -A STAC Collection is represented in JSON format. Any JSON object that contains all the required fields is a valid STAC Collection and also a valid STAC Catalog. +A STAC Collection is represented in JSON format. +Any JSON object that contains all the required fields is a valid STAC Collection and also a valid STAC Catalog. STAC Collections are compatible with the [Collection](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#example_4) JSON specified in [*OGC API - Features*](https://ogcapi.ogc.org/features/), but they are extended with additional fields. -* [Examples](../examples/): - * Sentinel 2: A basic standalone example of a [Collection](../examples/collection-only/collection.json) without Items. - * Simple Example: A [Collection](../examples/collection.json) that links to 3 example Items. - * Extension Collection: An additional [Collection](../examples/extensions-collection/collection.json), which is used to highlight +- [Examples](../examples/): + - Sentinel 2: A basic standalone example of a [Collection](../examples/collection-only/collection.json) without Items. + - Simple Example: A [Collection](../examples/collection.json) that links to 3 example Items. + - Extension Collection: An additional [Collection](../examples/extensions-collection/collection.json), which is used to highlight various [extension](../extensions) functionality, but serves as another example. -* [JSON Schema](json-schema/collection.json) +- [JSON Schema](json-schema/collection.json) ## Collection fields @@ -53,7 +55,7 @@ specified in [*OGC API - Features*](https://ogcapi.ogc.org/features/), but they | license | string | **REQUIRED.** Collection's license(s), either a SPDX [License identifier](https://spdx.org/licenses/), `various` if multiple licenses apply or `proprietary` for all other cases. | | providers | \[[Provider Object](#provider-object)] | A list of providers, which may include all organizations capturing or processing the data or the hosting provider. Providers should be listed in chronological order with the most recent provider being the last element of the list. | | extent | [Extent Object](#extent-object) | **REQUIRED.** Spatial and temporal extents. | -| summaries | Map | STRONGLY RECOMMENDED. A map of property summaries, either a set of values or statistics such as a range. | +| summaries | Map | STRONGLY RECOMMENDED. A map of property summaries, either a set of values, a range of values or a [JSON Schema](https://json-schema.org). | | links | \[[Link Object](#link-object)] | **REQUIRED.** A list of references to other documents. | | assets | Map | Dictionary of asset objects that can be downloaded, each with a unique key. | @@ -65,16 +67,11 @@ In general, STAC versions can be mixed, but please keep the [recommended best pr #### stac_extensions -A list of extensions the Collection implements. -This list must only contain extensions that extend the Collection itself, see the the 'Scope' column in the list of -extensions. This does NOT declare the extensions of child Collection or Item -objects. The list contains URLs to the JSON Schema files it can be validated against. - -If an extension has influence on multiple parts -of the whole STAC structure, it must be listed in all affected parts (e.g. Collection and Item for the `datacube` extension). -If a structure, such as the summaries extension, provides fields in their JSON structure, these extensions must not be listed -here as they don't extend the Collection itself. For example, if a Collection includes the field `sat:platform` in the -summaries, the Collection should not list the `sat` extension in the `stac_extensions` field. +A list of extensions the Collection implements. +The list consists of URLs to JSON Schema files that can be used for validation. +This list must only contain extensions that extend the Collection specification itself, +see the the 'Scope' for each of the extensions. +This must **not** declare the extensions that are only implemented in child Collection objects or child Item objects. #### id @@ -84,24 +81,45 @@ it is a fairly unique name, or their name combined with the domain they operate #### license -Collection's license(s) as a SPDX [License identifier](https://spdx.org/licenses/). Alternatively, use `proprietary` (see below) if the license is not on the SPDX license list or `various` if multiple licenses apply. In all cases links to the license texts SHOULD be added, see the `license` link relation type. If no link to a license is included and the `license` field is set to `proprietary`, the Collection is private, and consumers have not been granted any explicit right to use the data. +Collection's license(s) as a SPDX [License identifier](https://spdx.org/licenses/). +Alternatively, use `proprietary` (see below) if the license is not on the SPDX license list or `various` if multiple licenses apply. +In all cases links to the license texts SHOULD be added, see the `license` link relation type. +If no link to a license is included and the `license` field is set to `proprietary`, the Collection is private, +and consumers have not been granted any explicit right to use the data. #### summaries Collections are *strongly recommended* to provide summaries of the values of fields that they can expect from the `properties` of STAC Items contained in this Collection. This enables users to get a good sense of what the ranges and potential values of -different fields in the Collection are, without to inspect a number of Items (or crawl them exhaustively to get a definitive answer). -Summaries help to fully define Collections, especially if they don't link to any Items. They also give clients enough information to -build tailored user interfaces for querying the data, by presenting the potential values that are available. Summaries should summarize all values in every Item underneath the collection, including in any nested sub-Catalogs. - -A summary for a field can be specified in two ways: - -1. A set of all distinct values in an array: The set of values must contain at least one element and it is strongly recommended to list all values. If the field summarizes an array (e.g. [`instruments`](../item-spec/common-metadata.md#instrument)), the field's array elements of each Item must be merged to a single array with unique elements. -2. Statistics in a [Stats Object](#stats-object): Statistics by default only specify the range (minimum and maximum values), but can optionally be accompanied by additional statistical values. The range specified by the `minimum` and `maximum` properties can specify the potential range of values, but it is recommended to be as precise as possible. - -All values must follow the schema of the property they summarize. So the values in the array or the values given for `minimum` and `maxmimum` must comply to the original data type and any further restrictions that apply for the property they summarize. For example, the `minimum` for `gsd` can't be lower than zero and the summaries for `platform` and `instruments` must each be an array of strings (or alternatively minimum and maximum values, but that's not very meaningful). - -It is recommended to list as many properties as reasonable so that consumers get a full overview about the properties included in the Items. Nevertheless, it is not very useful to list all potential `title` values of the Items. Also, a range for the `datetime` property may be better suited to be included in the STAC Collection's `extent` field. In general, properties that are covered by the Collection specification should not be repeated in the summaries. +different fields in the Collection are, without having to inspect a number of Items (or crawl them exhaustively to get a definitive answer). +Summaries are often used to give users a sense of the data in [Standalone Collections](#standalone-collections), +describing the potential values even when it can't be accessed as Items. They also give clients enough information to +build tailored user interfaces for querying the data, by presenting the potential values that are available. + Fields selected to be included in summaries should capture all the potential values of the + field that appear in every Item underneath the collection, including in any nested sub-Catalogs. + +A summary for a field can be specified in three ways: + +1. A set of all distinct values in an array: The set of values must contain at least one element and it is strongly recommended to list all values. + If the field summarizes an array (e.g. [`instruments`](../item-spec/common-metadata.md#instrument)), + the field's array elements of each Item must be merged to a single array with unique elements. +2. A Range in a [Range Object](#range-object): Statistics by default only specify the range (minimum and maximum values), + but can optionally be accompanied by additional statistical values. + The range specified by the `minimum` and `maximum` properties can specify the potential range of values, + but it is recommended to be as precise as possible. +3. Extensible JSON Schema definitions for fine-grained information, see the [JSON Schema Object](#json-schema-object) + section for more. + +All values must follow the schema of the property field they summarize, unless the field is an array as described in (1) above. +So the values in the array or the values given for `minimum` and `maximum` must comply to the original data type +and any further restrictions that apply for the property they summarize. +For example, the `minimum` for `gsd` can't be lower than zero and the summaries for `platform` and `instruments` +must each be an array of strings (or alternatively minimum and maximum values, but that's not very meaningful). + +It is recommended to list as many properties as reasonable so that consumers get a full overview about the properties included in the Items. +Nevertheless, it is not very useful to list all potential `title` values of the Items. +Also, a range for the `datetime` property may be better suited to be included in the STAC Collection's `extent` field. +In general, properties that are covered by the Collection specification should not be repeated in the summaries. See the [examples folder](../examples) for Collections with summaries to get a sense of how to use them. @@ -118,13 +136,16 @@ The definition provided here, at the Collection level, is the same as the There are a few guidelines for using the asset construct at the Collection level: -* Collection-level assets SHOULD NOT list any files also available in Items. -* If possible, item-level assets are always the preferable way to expose assets. +- Collection-level assets SHOULD NOT list any files also available in Items. +- If possible, item-level assets are always the preferable way to expose assets. Collection-level assets can be useful in some scenarios, for example: -1. Exposing additional data that applies Collection-wide and you don't want to expose it in each Item. This can be Collection-level metadata or a thumbnail for visualization purposes. -2. Individual Items can't properly be distinguished for some data structures, e.g. [Zarr](https://zarr.readthedocs.io/) as it's a data structure not contained in single files. -3. Exposing assets for "[Standalone Collections](https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md#standalone-collections)". +1. Exposing additional data that applies Collection-wide and you don't want to expose it in each Item. + This can be Collection-level metadata or a thumbnail for visualization purposes. +2. Individual Items can't properly be distinguished for some data structures, + e.g. [Zarr](https://zarr.readthedocs.io/) as it's a data structure not contained in single files. +3. Exposing assets for + "[Standalone Collections](https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md#standalone-collections)". Oftentimes it is possible to model data and assets with either a Collection or an Item. In those scenarios we *recommend* to use Items as much as is feasible, as they designed for assets. Using Collection-level assets should only be used if there is not another @@ -147,11 +168,24 @@ The object describes the spatial extents of the Collection. | ------- | ------------ | -------------------------------------------------------------------- | | bbox | \[\[number]] | **REQUIRED.** Potential *spatial extents* covered by the Collection. | -**bbox**: Bounding Boxes of the assets represented by this Collection using either 2D or 3D geometries. Each outer array element can be a separate bounding box, but it is recommended to only use multiple bounding boxes if a union of them would then include a large uncovered area (e.g. the union of Germany and Chile). +**bbox**: Each outer array element can be a separate spatial extent describing the bounding boxes +of the assets represented by this Collection using either 2D or 3D geometries. + +The first bounding box always describes the overall spatial extent of the data. All subsequent bounding boxes can be +used to provide a more precise description of the extent and identify clusters of data. +Clients only interested in the overall spatial extent will only need to access the first item in each array. +It is recommended to only use multiple bounding boxes if a union of them would then include +a large uncovered area (e.g. the union of Germany and Chile). -The length of the inner array must be 2*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters and the elevation of the northeasterly most extent is the maximum. +The length of the inner array must be 2*n where n is the number of dimensions. +The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in +Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). +When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters +and the elevation of the northeasterly most extent is the maximum. -The coordinate reference system of the values is WGS 84 longitude/latitude. Example that covers the whole Earth: `[[-180.0, -90.0, 180.0, 90.0]]`. Example that covers the whole earth with a depth of 100 meters to a height of 150 meters: `[[-180.0, -90.0, -100.0, 180.0, 90.0, 150.0]]`. +The coordinate reference system of the values is WGS 84 longitude/latitude. +Example that covers the whole Earth: `[[-180.0, -90.0, 180.0, 90.0]]`. +Example that covers the whole earth with a depth of 100 meters to a height of 150 meters: `[[-180.0, -90.0, -100.0, 180.0, 90.0, 150.0]]`. #### Temporal Extent Object @@ -161,15 +195,31 @@ The object describes the temporal extents of the Collection. | -------- | ------------------ | --------------------------------------------------------------------- | | interval | \[\[string\|null]] | **REQUIRED.** Potential *temporal extents* covered by the Collection. | -**interval**: Each outer array element can be a separate temporal extent, but it is recommended to only use multiple temporal extents if a union of them would then include a large uncovered time span (e.g. only having data for the years 2000, 2010 and 2020). +**interval**: Each outer array element can be a separate temporal extent. +The first time interval always describes the overall temporal extent of the data. All subsequent time intervals +can be used to provide a more precise description of the extent and identify clusters of data. +Clients only interested in the overall extent will only need to access the first item in each array. +It is recommended to only use multiple temporal extents if a union of them would then include a large +uncovered time span (e.g. only having data for the years 2000, 2010 and 2020). -Each inner array consists of exactly two dates and times. Each date and time MUST be formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). The temporal reference system is the Gregorian calendar. +Each inner array consists of exactly two elements, either a timestamp or `null`. -Open date ranges are supported by setting either the start or the end time to `null`. Example for data from the beginning of 2019 until now: `[["2009-01-01T00:00:00Z", null]]`. +Timestamps consist of a date and time in UTC and MUST be formatted according to +[RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). +The temporal reference system is the Gregorian calendar. + +Open date ranges are supported by setting the start and/or the end time to `null`. +Example for data from the beginning of 2019 until now: `[["2019-01-01T00:00:00Z", null]]`. +It is recommended to provide at least a rough guideline on the temporal extent and thus +it's not recommended to set both start and end time to `null`. Nevertheless, this is possible +if there's a strong use case for an open date range to both sides. ### Provider Object -The object provides information about a provider. A provider is any of the organizations that captures or processes the content of the Collection and therefore influences the data offered by this Collection. May also include information about the final storage provider hosting the data. +The object provides information about a provider. +A provider is any of the organizations that captures or processes the content of the Collection +and therefore influences the data offered by this Collection. +May also include information about the final storage provider hosting the data. | Field Name | Type | Description | | ----------- | --------- | ------------------------------------------------------------ | @@ -180,10 +230,11 @@ The object provides information about a provider. A provider is any of the organ **roles**: The provider's role(s) can be one or more of the following elements: -* *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. -* *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. -* *processor*: A processor is any provider who processed data to a derived product. -* *host*: The host is the actual provider offering the data on their storage. There should be no more than one host, specified as last element of the list. +- *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. +- *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. +- *processor*: A processor is any provider who processed data to a derived product. +- *host*: The host is the actual provider offering the data on their storage. + There should be no more than one host, specified as last element of the list. ### Link Object @@ -201,24 +252,30 @@ For a full discussion of the situations where relative and absolute links are re #### Relation types -STAC Collections use a variety of `rel` types in the link object, to describe the exact nature of the link between this Collection and the entity it is linking to. -It is recommended to use the official [IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. +STAC Collections use a variety of `rel` types in the link object, +to describe the exact nature of the link between this Collection and the entity it is linking to. +It is recommended to use the official +[IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. The following table explains places where custom STAC `rel` types are used for ollections. This is done where there is not a clear official option, or where STAC uses an official type but adds additional meaning for the STAC context. | Type | Description | | ------- | ------------------------------------------------------------ | | self | STRONGLY RECOMMENDED. *Absolute* URL to the location that the Collection file can be found online, if available. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | -| root | URL to the root STAC Catalog or Collection. Collections should include a link to their root, even if it's the root and points to itself. | -| parent | URL to the parent STAC Catalog or Collection. Non-root Collections should include a link to their parent. | -| child | URL to a child STAC Catalog or Collection. | +| root | URL to the root STAC entity (Catalog or Collection). Collections should include a link to their root, even if it's the root and points to itself. | +| parent | URL to the parent STAC entity (Catalog or Collection). Non-root Collections should include a link to their parent. | +| child | URL to a child STAC entity (Catalog or Collection). | | item | URL to a STAC Item. All Items linked from a Collection MUST refer back to its Collection with the [`collection` relation type](../item-spec/item-spec.md#relation-types). | | license | The license URL(s) for the Collection SHOULD be specified if the `license` field is set to `proprietary` or `various`. If there is no public license URL available, it is RECOMMENDED to put the license text in a separate file and link to this file. | | derived_from | URL to a STAC Collection that was used as input data in the creation of this Collection. See the note in [STAC Item](../item-spec/item-spec.md#derived_from) for more info. | -A more complete list of possible `rel` types and their meaning in STAC can be found in the [Using Relation Types](../best-practices.md#using-relation-types) best practice. +A more complete list of possible `rel` types and their meaning in STAC can be found in the +[Using Relation Types](../best-practices.md#using-relation-types) best practice. -**Note:** The STAC Catalog specification requires a link to at least one `item` or `child` Catalog. This is *not* a requirement for Collections, but *recommended*. In contrast to Catalogs, it is **REQUIRED** that Items linked from a Collection MUST refer back to its Collection with the [`collection` relation type](../item-spec/item-spec.md#relation-types). +**Note:** The STAC Catalog specification requires a link to at least one `item` or `child` Catalog. +This is *not* a requirement for Collections, but *recommended*. In contrast to Catalogs, +it is **REQUIRED** that Items linked from a Collection MUST refer back to its Collection +with the [`collection` relation type](../item-spec/item-spec.md#relation-types). ### Asset Object @@ -234,10 +291,12 @@ or streamed. The definition provided here, at the Collection level, is the same | type | string | [Media type](../item-spec/item-spec.md#asset-media-type) of the asset. See the [common media types](../best-practices.md#common-media-types-in-stac) in the best practice doc for commonly used asset types. | | roles | \[string] | The [semantic roles](../item-spec/item-spec.md#asset-role-types) of the asset, similar to the use of `rel` in links. | -### Stats Object +### Range Object -For a good understanding of the summarized field, statistics can be added. By default, only ranges with a minimum and a maximum value can be specified. -Ranges can be specified for [ordinal](https://en.wikipedia.org/wiki/Level_of_measurement#Ordinal_scale) values only, which means they need to have a rank order. +For summaries that would normally consist of a lot of continuous values, statistics can be added instead. +By default, only ranges with a minimum and a maximum value can be specified. +Ranges can be specified for [ordinal](https://en.wikipedia.org/wiki/Level_of_measurement#Ordinal_scale) values only, +which means they need to have a rank order. Therefore, ranges can only be specified for numbers and some special types of strings. Examples: grades (A to F), dates or times. Implementors are free to add other derived statistical values to the object, for example `mean` or `stddev`. @@ -246,6 +305,17 @@ Implementors are free to add other derived statistical values to the object, for | minimum | number\|string | **REQUIRED.** Minimum value. | | maximum | number\|string | **REQUIRED.** Maximum value. | +### JSON Schema Object + +For a full understanding of the summarized field, a JSON Schema can be added for each summarized field. +This allows very fine-grained information for each field and each value as JSON Schema is also extensible. +Each schema must be valid against all corresponding values available for the property in the sub-Items. + +It is recommended to use [JSON Schema draft-07](https://json-schema.org/specification-links.html#draft-7) +to align with the JSON Schemas provided by STAC. Empty schemas are not allowed. + +For an introduction to JSON Schema, see "[Learn JSON Schema](https://json-schema.org/learn/)". + ## Media Type for STAC Collections A STAC Collection is a JSON file ([RFC 8259](https://tools.ietf.org/html/rfc8259)), and thus should use the @@ -255,6 +325,8 @@ A STAC Collection is a JSON file ([RFC 8259](https://tools.ietf.org/html/rfc8259 ## Standalone Collections STAC Collections which don't link to any Item are called **standalone Collections**. -To describe them with more fields than the Collection fields has to offer, it is allowed to re-use the metadata fields defined by extensions for Items in the `summaries` field. -This makes much sense for fields such as `platform` or `proj:epsg`, which are often the same for a whole Collection, but doesn't make much sense for `eo:cloud_cover`, which usually varies heavily across a Collection. +To describe them with more fields than the Collection fields has to offer, +it is allowed to re-use the metadata fields defined by extensions for Items in the `summaries` field. +This makes much sense for fields such as `platform` or `proj:epsg`, which are often the same for a whole Collection, +but doesn't make much sense for `eo:cloud_cover`, which usually varies heavily across a Collection. The data provider is free to decide, which fields are reasonable to be used. diff --git a/collection-spec/json-schema/collection.json b/collection-spec/json-schema/collection.json index 422385f2..2ad20902 100644 --- a/collection-spec/json-schema/collection.json +++ b/collection-spec/json-schema/collection.json @@ -1,12 +1,9 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/collection-spec/json-schema/collection.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/collection-spec/json-schema/collection.json#", "title": "STAC Collection Specification", "description": "This object represents Collections in a SpatioTemporal Asset Catalog.", "allOf": [ - { - "$ref": "../../catalog-spec/json-schema/catalog-core.json" - }, { "$ref": "#/definitions/collection" } @@ -17,33 +14,48 @@ "description": "These are the fields specific to a STAC Collection. All other fields are inherited from STAC Catalog.", "type": "object", "required": [ + "stac_version", "type", + "id", + "description", "license", - "extent" + "extent", + "links" ], "properties": { - "type": { - "title": "Type of STAC entity", - "const": "Collection" + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0" }, "stac_extensions": { "title": "STAC extensions", "type": "array", "uniqueItems": true, "items": { - "anyOf": [ - { - "title": "Reference to a JSON Schema", - "type": "string", - "format": "iri" - }, - { - "title": "Reference to a core extension", - "type": "string" - } - ] + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" } }, + "type": { + "title": "Type of STAC entity", + "const": "Collection" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "title": { + "title": "Title", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "minLength": 1 + }, "keywords": { "title": "Keywords", "type": "array", @@ -153,7 +165,8 @@ "string", "null" ], - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" } } } @@ -163,8 +176,95 @@ }, "assets": { "$ref": "../../item-spec/json-schema/item.json#/definitions/assets" + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "summaries": { + "$ref": "#/definitions/summaries" } } + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + }, + "summaries": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "title": "JSON Schema", + "type": "object", + "minProperties": 1, + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema" + } + ] + }, + { + "title": "Range", + "type": "object", + "required": [ + "minimum", + "maximum" + ], + "properties": { + "minimum": { + "title": "Minimum value", + "type": [ + "number", + "string" + ] + }, + "maximum": { + "title": "Maximum value", + "type": [ + "number", + "string" + ] + } + } + }, + { + "title": "Set of values", + "type": "array", + "minItems": 1, + "items": { + "description": "For each field only the original data type of the property can occur (except for arrays), but we can't validate that in JSON Schema yet. See the sumamry description in the STAC specification for details." + } + } + ] + } } } } \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 85b5f5a1..a4d046a9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,8 @@ # 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 +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 diff --git a/examples/catalog.json b/examples/catalog.json index 1bac09f9..b5910be3 100644 --- a/examples/catalog.json +++ b/examples/catalog.json @@ -1,7 +1,8 @@ { "id": "examples", "type": "Catalog", - "stac_version": "1.0.0-rc.2", + "title": "Example Catalog", + "stac_version": "1.0.0", "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.", "links": [ { @@ -21,9 +22,21 @@ "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.2/examples/catalog.json", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/catalog.json", "type": "application/json" } ] diff --git a/examples/collection-only/collection-with-schemas.json b/examples/collection-only/collection-with-schemas.json new file mode 100644 index 00000000..e0023e23 --- /dev/null +++ b/examples/collection-only/collection-with-schemas.json @@ -0,0 +1,277 @@ +{ + "stac_version": "1.0.0", + "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", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "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/examples/collection-only/collection.json b/examples/collection-only/collection.json index 9a8da7c3..cc631b48 100644 --- a/examples/collection-only/collection.json +++ b/examples/collection-only/collection.json @@ -1,7 +1,11 @@ { "type": "Collection", - "stac_version": "1.0.0-rc.2", - "stac_extensions": [], + "stac_version": "1.0.0", + "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", @@ -78,9 +82,6 @@ "minimum": 6.78, "maximum": 89.9 }, - "sci:citation": [ - "Copernicus Sentinel data [Year]" - ], "gsd": [ 10, 30, @@ -213,11 +214,15 @@ "links": [ { "rel": "parent", - "href": "../catalog.json" + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" }, { "rel": "root", - "href": "../catalog.json" + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" }, { "rel": "license", diff --git a/examples/collection.json b/examples/collection.json index 4fadb641..07643b60 100644 --- a/examples/collection.json +++ b/examples/collection.json @@ -1,7 +1,12 @@ { "id": "simple-collection", "type": "Collection", - "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", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "stac_version": "1.0.0", "description": "A simple collection demonstrating core catalog fields with links to a couple of items", "title": "Simple Example Collection", "providers": [ @@ -19,18 +24,18 @@ "spatial": { "bbox": [ [ - 172.911, - 1.343, - 172.955, - 1.3691 + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 ] ] }, "temporal": { "interval": [ [ - "2020-12-11T09:06:43.312000Z", - "2020-12-14T18:02:31.437000Z" + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" ] ] } @@ -38,33 +43,47 @@ "license": "CC-BY-4.0", "summaries": { "platform": [ - "cool_sat2", - "cool_sat1" + "cool_sat1", + "cool_sat2" ], "constellation": [ "ion" ], "instruments": [ - "cool_sensor_v1" + "cool_sensor_v1", + "cool_sensor_v2" ], "gsd": { "minimum": 0.512, - "maximum": 0.7 + "maximum": 0.66 }, - "view:off_nadir": { - "minimum": 0, - "maximum": 15 + "eo:cloud_cover": { + "minimum": 1.2, + "maximum": 1.2 + }, + "proj:epsg": { + "minimum": 32659, + "maximum": 32659 }, "view:sun_elevation": { - "minimum": 6.78, - "maximum": 40 + "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" + "type": "application/json", + "title": "Simple Example Collection" }, { "rel": "item", @@ -86,7 +105,7 @@ }, { "rel": "self", - "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0-rc.2/examples/collection.json", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/collection.json", "type": "application/json" } ] diff --git a/examples/collectionless-item.json b/examples/collectionless-item.json index 250bb704..6e7beab9 100644 --- a/examples/collectionless-item.json +++ b/examples/collectionless-item.json @@ -1,5 +1,5 @@ { - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json" @@ -71,33 +71,30 @@ "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" + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" }, { - "rel": "root", - "href": "./collection.json", - "type": "application/json" + "rel": "parent", + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" }, { "rel": "alternate", "type": "text/html", - "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html" + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html", + "title": "HTML representation of this STAC Item" }, { "rel": "license", "type": "text/html", - "href": "http://remotedata.io/license.html" + "href": "http://remotedata.io/license.html", + "title": "Data License for Remote Data, Inc." } ], "assets": { diff --git a/examples/core-item.json b/examples/core-item.json index 0b453dd4..d07cc8df 100644 --- a/examples/core-item.json +++ b/examples/core-item.json @@ -1,5 +1,5 @@ { - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "20201211_223832_CS2", @@ -44,7 +44,7 @@ "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", + "platform": "cool_sat1", "instruments": [ "cool_sensor_v1" ], @@ -63,12 +63,20 @@ { "rel": "root", "href": "./collection.json", - "type": "application/json" + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" }, { "rel": "alternate", "type": "text/html", - "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" } ], "assets": { diff --git a/examples/extended-item.json b/examples/extended-item.json index 4012cb4a..59f2de1f 100644 --- a/examples/extended-item.json +++ b/examples/extended-item.json @@ -1,5 +1,5 @@ { - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/projection/v1.0.0/schema.json", @@ -45,12 +45,12 @@ "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", + "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_v1" + "cool_sensor_v2" ], "gsd": 0.66, "eo:cloud_cover": 1.2, @@ -91,12 +91,20 @@ { "rel": "root", "href": "./collection.json", - "type": "application/json" + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" }, { "rel": "alternate", "type": "text/html", - "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html" + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" } ], "assets": { diff --git a/examples/extensions-collection/collection.json b/examples/extensions-collection/collection.json index a79fabe3..bfbc3c1e 100644 --- a/examples/extensions-collection/collection.json +++ b/examples/extensions-collection/collection.json @@ -1,13 +1,20 @@ { "id": "extensions-collection", "type": "Collection", - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "description": "A heterogenous collection containing deeper examples of various extensions", "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, { "rel": "root", "href": "../catalog.json", - "type": "application/json" + "type": "application/json", + "title": "Example Catalog" }, { "rel": "item", @@ -18,11 +25,6 @@ "rel": "license", "href": "https://remotedata.io/license.html", "title": "Remote Data License Terms" - }, - { - "rel": "parent", - "href": "../catalog.json", - "type": "application/json" } ], "stac_extensions": [], diff --git a/examples/extensions-collection/proj-example/proj-example.json b/examples/extensions-collection/proj-example/proj-example.json index 91fa8130..09b7986d 100644 --- a/examples/extensions-collection/proj-example/proj-example.json +++ b/examples/extensions-collection/proj-example/proj-example.json @@ -1,6 +1,6 @@ { "type": "Feature", - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "id": "proj-example", "properties": { "datetime": "2018-10-01T01:08:32.033000Z", @@ -213,12 +213,20 @@ { "rel": "root", "href": "../../catalog.json", - "type": "application/json" + "type": "application/json", + "title": "Example Catalog" }, { "rel": "parent", "href": "../collection.json", - "type": "application/json" + "type": "application/json", + "title": "Collection of Extension Items" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json", + "title": "Collection of Extension Items" } ], "assets": { @@ -242,7 +250,6 @@ "eo:bands": [ { "name": "B8", - "common_name": "panchromatic", "center_wavelength": 0.59, "full_width_half_max": 0.18 } diff --git a/examples/simple-item.json b/examples/simple-item.json index ad4d526d..1e413c43 100644 --- a/examples/simple-item.json +++ b/examples/simple-item.json @@ -1,5 +1,5 @@ { - "stac_version": "1.0.0-rc.2", + "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "20201211_223832_CS2", @@ -50,7 +50,14 @@ { "rel": "root", "href": "./collection.json", - "type": "application/json" + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" } ], "assets": { diff --git a/extensions/README.md b/extensions/README.md index e864f3ce..151983d0 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -1,13 +1,15 @@ # Extensions - [Overview](#overview) -- [General Conventions](#general-conventions) +- [Using Extensions](#using-extensions) + - [Extension IDs in `stac_extensions`](#extension-ids-in-stac_extensions) - [Stable STAC Extensions](#stable-stac-extensions) - [Community Extensions](#community-extensions) - - [Extension Maturity](#extension-maturity) - [Proposed extensions](#proposed-extensions) - [Extending STAC](#extending-stac) + - [General Conventions](#general-conventions) - [Proposing new extensions](#proposing-new-extensions) + - [Extension Maturity](#extension-maturity) - [Prefixes](#prefixes) - [Use of arrays and objects](#use-of-arrays-and-objects) @@ -33,13 +35,38 @@ can be aware of it. Each extension has at least one *owner*. You can find extension owners in each extension's README. -## General Conventions - -1. Additional attributes relating to an [Item](../item-spec/item-spec.md) should be added into the Item Properties object, rather than directly in the Item object. -2. In general, additional attributes that apply to an Item Asset should also be allowed in Item Properties and vice-versa. -For example, the `eo:bands` attribute may be used in Item Properties to describe the aggregation of all bands available in -the Item Asset objects contained in the Item, but may also be used in an individual Item Asset to describe only the bands available in that asset. -3. Additional attributes relating to a [Catalog](../catalog-spec/catalog-spec.md) or [Collection](../collection-spec/collection-spec.md) should be added to the root of the object. +## Using Extensions + +When deciding how to model data in STAC it is highly recommended to first look at the [list of +extensions](https://stac-extensions.github.io/) and re-use fields there instead of creating your own version. This +increases interoperability, as users know that the meaning of your fields is the same as in other STAC +implementations. Many clients will also understand more mature extensions for better display and querying. + +To incorporate an extension in STAC the 'extension ID' of the extension must be added to the `stac_extensions` +array of the STAC [Catalog](../catalog-spec/catalog-spec.md#stac_extensions), +[Collection](../collection-spec/collection-spec.md#stac_extensions) or [Item](../item-spec/item-spec.md#stac_extensions) +object. This identifier is a link to the JSON Schema URL that validates the fields in the extension, so STAC validators +can fetch the Schema to validate that the STAC object properly follows the extension. These JSON Schema URLs also act as +identifiers for specific version of the extension that the STAC object implements. The extension ID can be +found listed as the 'identifier' in the second line of the README of any extension made with the [extension +template](https://github.com/stac-extensions/template), and new ones get published automatically with any release made +with the template. + +### Extension IDs in `stac_extensions` + +The logic for when an object should list an extension ID in its `stac_extension` array is as follows: + +- If the object directly implements the extension (by following the specified requirements - usually by including +fields, but occasionally implementing alternate behaviors), the `stac_extensions` of that object should contain the extension ID. +- If an Asset implements fields of the extension, then `stac_extensions` of the Item or Collection which holds that + Asset should contain the extension ID. +- If a Collection [summary](../collection-spec/collection-spec.md#summaries) contains Item fields that implement an extension, then + the `stac_extensions` array of that Collection should list the extension ID. For example, if a Collection `summaries` field + contains a summary of `eo:bands`, then that Collection should have the EO extension JSON Schema URL in the `stac_extensions` array. +- If an object implements an extension that results in fields from a separate extension to be referenced, then the latter extension + ID should be included in the `stac_extensions` array for that object. For example, if a Collection implements the + [item_assets](https://github.com/stac-extensions/item-assets) extension, and in the `item_assets` field there is an Asset Definition + which includes `eo:bands`, then the EO extension ID should be listed in that Collection's `stac_extensions`. ## Stable STAC Extensions @@ -77,9 +104,25 @@ with existing extensions as well as possible and may even re-use fields and thei into a new extension that combines commonly used fields across multiple extensions. Best practices for extension proposals are still emerging in this section. +### General Conventions + +Creating a new extension usually involves defining a set of logically grouped fields, and specifying what the allowed values +for those fields are. This should be done in the extension text and in JSON Schema, to provide validation. While one +can theoretically add fields anywhere in JSON there are some conventions as to where to add them in STAC objects. + +1. Additional attributes relating to an [Item](../item-spec/item-spec.md) should be added into the Item Properties object, + rather than directly in the Item object. +2. In general, additional attributes that apply to an Item Asset should also be allowed in Item Properties and vice-versa. + For example, the `eo:bands` attribute may be used in Item Properties to describe the aggregation of all bands available in + the Item Asset objects contained in the Item, but may also be used in an individual Item Asset to describe only the bands available in that asset. +3. Additional attributes relating to a [Catalog](../catalog-spec/catalog-spec.md) or + [Collection](../collection-spec/collection-spec.md) should be added to the root of the object. +4. Extensions may also extend other extensions, declaring that dependency in the text and JSON Schema. + ### Proposing new extensions -Extensions can be hosted anywhere, but should use the [extension template](https://github.com/stac-extensions/stac-extensions.github.io#using-the-stac-extensions-template) +Extensions can be hosted anywhere, but should use the +[extension template](https://github.com/stac-extensions/stac-extensions.github.io#using-the-stac-extensions-template) as a starting point. If you'd like to add a repository to the [stac-extensions](https://github.com/stac-extensions) GitHub organization, just ask on [Gitter](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby)! This is fine for work-in-progress extensions. You can also host the extension repository in your own GitHub account, and optionally @@ -163,10 +206,20 @@ For extensions, it is recommended to 1. Use arrays only as enumerations/lists (possibly sorted), without implying additional meaning (such as order) 2. To avoid using nested objects, in favor of multiple attributes with a similar naming scheme. -For example, if one would like to define an extension to contain a start and a end date, there are multiple options (tl;dr: option **3** is recommended): - -1. Define an object, for example: `"date_range": {"start": "2018-01-01", "end": "2018-01-31"}`. This is **discouraged** as it is more complex to search in objects. -2. Define an two-element array where the first element is the start date and the second element is the end date, for example `"date_range": ["2018-01-01", "2018-01-31"]`. This is **discouraged** as it would conflict with Collection `summaries`, which always considers arrays as true (potentially sorted) enumeration without any additional meaning. -3. Define two separate fields, e.g. `"date_range_start": "2018-01-01", "date_range_end": "2018-01-31"`. This is **recommended** as it avoids the conflicts above and is usually better displayed in software that only understands GeoJSON but has no clue about STAC. This is due to the fact that most legacy software can not display arrays or objects GeoJSON `properties` properly. - -This rules only applies to the fields defined directly for the Item's `properties`. For fields and structures defined on other levels (e.g. in the root of an Item or in an array), extension authors can freely define the structure. So an array of objects such as the `eo:bands` are fine to use, but keep in mind that the drawbacks mentioned above usually still apply. +For example, if one would like to define an extension to contain a start and a end date, +there are multiple options (tl;dr: option **3** is recommended): + +1. Define an object, for example: `"date_range": {"start": "2018-01-01", "end": "2018-01-31"}`. + This is **discouraged** as it is more complex to search in objects. +2. Define an two-element array where the first element is the start date and the second element is the end date, + for example `"date_range": ["2018-01-01", "2018-01-31"]`. + This is **discouraged** as it would conflict with Collection `summaries`, + which always considers arrays as true (potentially sorted) enumeration without any additional meaning. +3. Define two separate fields, e.g. `"date_range_start": "2018-01-01", "date_range_end": "2018-01-31"`. + This is **recommended** as it avoids the conflicts above and is usually better displayed in software that only understands GeoJSON + but has no clue about STAC. + This is due to the fact that most legacy software can not display arrays or objects GeoJSON `properties` properly. + +This rules only applies to the fields defined directly for the Item's `properties`. +For fields and structures defined on other levels (e.g. in the root of an Item or in an array), extension authors can freely define the structure. +So an array of objects such as the `eo:bands` are fine to use, but keep in mind that the drawbacks mentioned above usually still apply. diff --git a/item-spec/common-metadata.md b/item-spec/common-metadata.md index 2a0506ca..b9596c46 100644 --- a/item-spec/common-metadata.md +++ b/item-spec/common-metadata.md @@ -19,8 +19,8 @@ Various *examples* are available in the folder [`examples`](../examples/). *JSON Schemas* can be found in the folder [`json-schema`](json-schema/). By default, these fields are only included and validated against in the core [Item schema](json-schema/item.json). -Implementation of any of the fields is not required, -if the specifications allowing these fields to be used don't say differently. + +Implementation of any of the fields is not required, unless explicitly required by a specification using the field. For example, `datetime` is required in STAC Items. ## Basics @@ -43,13 +43,13 @@ Fields to provide additional temporal information such as ranges with a start an | Field Name | Type | Description | | ---------- | ------------ | ----------- | | datetime | string\|null | See the [Item Spec Fields](item-spec.md#properties-object) for more information. | -| created | string | Creation date and time of the corresponding data (see below). | -| updated | string | Date and time the corresponding data (see below) was updated last. | +| created | string | Creation date and time of the corresponding data (see below), in UTC. | +| updated | string | Date and time the corresponding data (see below) was updated last, in UTC. | All timestamps MUST be formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). **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. +If those fields are available in the Item `properties`, they identify 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. *NOTE: There are more date and time related fields available in the [Timestamps @@ -58,10 +58,12 @@ extension](https://github.com/stac-extensions/timestamps), which is not an offic ### Date and Time Range While a STAC Item can have a nominal datetime describing the capture, these properties allow an Item to have a range -of capture dates and times. An example of this is the [MODIS 16 day vegetation index product.](https://lpdaac.usgs.gov/products/mod13q1v006/). -The datetime property in a STAC Item and these fields are not mutually exclusive. +of capture dates and times. An example of this is the [MODIS 16 day vegetation index product](https://lpdaac.usgs.gov/products/mod13q1v006/). -**Important:** Using one of the fields REQUIRES to include the other field as well to enable a user to search STAC records by the provided times. So if you use `start_datetime` you need to add `end_datetime` and vice-versa. Both fields are also REQUIRED if the `datetime` field is set to `null`. +**Important:** Using one of the fields REQUIRES inclusion of the other field as well to enable a user to search STAC records by the provided times. +So if you use `start_datetime` you need to add `end_datetime` and vice-versa. +Both fields are also REQUIRED if the `datetime` field is set to `null`. +The datetime property in a STAC Item and these fields are not mutually exclusive. | Field Name | Type | Description | | -------------- | ------ | ------------------------------------------------------------ | @@ -104,7 +106,10 @@ Information about the organizations capturing, producing, processing, hosting or ### Provider Object -The object provides information about a provider. A provider is any of the organizations that captures or processes the content of the assets and therefore influences the data offered by the STAC implementation. May also include information about the final storage provider hosting the data. +The object provides information about a provider. +A provider is any of the organizations that captures or processes the content of the assets and +therefore influences the data offered by the STAC implementation. +May also include information about the final storage provider hosting the data. | Field Name | Type | Description | | ----------- | --------- | ------------------------------------------------------------ | @@ -117,14 +122,15 @@ The object provides information about a provider. A provider is any of the organ The provider's role(s) can be one or more of the following elements: -* *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. -* *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. -* *processor*: A processor is any provider who processed data to a derived product. -* *host*: The host is the actual provider offering the data on their storage. There should be no more than one host, specified as last element of the list. +- *licensor*: The organization that is licensing the dataset under the license specified in the Collection's `license` field. +- *producer*: The producer of the data is the provider that initially captured and processed the source data, e.g. ESA for Sentinel-2 data. +- *processor*: A processor is any provider who processed data to a derived product. +- *host*: The host is the actual provider offering the data on their storage. + There should be no more than one host, specified as the last element of the provider list. ## Instrument -Adds metadata specifying a platform and instrument used in a data collection mission. These fields will often be combined +Adds metadata specifying a platform and instrument used in a data collection mission. These fields will often be combined with domain-specific extensions that describe the actual data, such as the `eo` or `sar` extensions. - [JSON Schema](json-schema/instrument.json) @@ -141,10 +147,10 @@ with domain-specific extensions that describe the actual data, such as the `eo` #### platform -The unique name of the specific platform the instrument is attached to. For satellites this would -be the name of the satellite, whereas for drones this would be a unique name for the drone. Examples include -`landsat-8` (Landsat-8), `sentinel-2a` and `sentinel-2b` (Sentinel-2), `terra` and `aqua` (part of NASA EOS, -carrying the MODIS instruments), `mycorp-uav-034` (hypothetical drone name), and `worldview02` +The unique name of the specific platform the instrument is attached to. For satellites this would +be the name of the satellite, whereas for drones this would be a unique name for the drone. Examples include +`landsat-8` (Landsat-8), `sentinel-2a` and `sentinel-2b` (Sentinel-2), `terra` and `aqua` (part of NASA EOS, +carrying the MODIS instruments), `mycorp-uav-034` (hypothetical drone name), and `worldview02` (Maxar/DigitalGlobe WorldView-2). #### instruments @@ -156,15 +162,15 @@ specified as `['oli', 'tirs']`. Other instrument examples include `msi` (Sentine #### constellation -The name of a logical collection of one or more platforms that have similar payloads and have -their orbits arranged in a way to increase the temporal resolution of acquisitions of data with similar geometric and -radiometric characteristics. This field allows users to search for related data sets without the need to specify which -specific platform the data came from, for example, from either of the Sentinel-2 satellites. Examples include `landsat-8` +The name of a logical collection of one or more platforms that have similar payloads and have +their orbits arranged in a way to increase the temporal resolution of acquisitions of data with similar geometric and +radiometric characteristics. This field allows users to search for related data sets without the need to specify which +specific platform the data came from, for example, from either of the Sentinel-2 satellites. Examples include `landsat-8` (Landsat-8, a constellation consisting of a single platform), `sentinel-2` -([Sentinel-2](https://www.esa.int/Our_Activities/Observing_the_Earth/Copernicus/Sentinel-2/Satellite_constellation)), +([Sentinel-2](https://www.esa.int/Our_Activities/Observing_the_Earth/Copernicus/Sentinel-2/Satellite_constellation)), `rapideye` (operated by Planet Labs), and `modis` (NASA EOS satellites Aqua and Terra). In the case of `modis`, this -is technically referring to a pair of sensors on two different satellites, whose data is combined into a series of -related products. Additionally, the Aqua satellite is technically part of the A-Train constellation and Terra is not +is technically referring to a pair of sensors on two different satellites, whose data is combined into a series of +related products. Additionally, the Aqua satellite is technically part of the A-Train constellation and Terra is not part of a constellation, but these are combined to form the logical collection referred to as MODIS. #### mission @@ -178,9 +184,10 @@ data collection. The nominal Ground Sample Distance for the data, as measured in meters on the ground. There are many definitions of GSD. The value of this field should be related to the spatial resolution at the sensor, rather than the pixel size of images after orthorectification, pansharpening, or scaling. -The GSD of a sensor can vary depending on off-nadir and wavelength, so it is at the discretion of the implementer -to decide which value most accurately represents the GSD. For example, Landsat8 optical and short-wave IR bands -are all 30 meters, but the panchromatic band is 15 meters. The -`gsd` should be 30 meters in this case because that is nominal spatial resolution at the sensor. The Planet -PlanetScope Ortho Tile Product has an `gsd` of 3.7 (or 4 if rounding), even though the pixel size of the images is 3.125. For example, one might choose for WorldView-2 the -Multispectral 20° off-nadir value of 2.07 and for WorldView-3 the Multispectral 20° off-nadir value of 1.38. +The GSD of a sensor can vary depending on geometry (off-nadir / grazing angle) and wavelength, so it is at the +discretion of the implementer to decide which value most accurately represents the GSD. For example, Landsat8 +optical and short-wave IR bands are all 30 meters, but the panchromatic band is 15 meters. The +`gsd` should be 30 meters in this case because that is the nominal spatial resolution at the sensor. The Planet +PlanetScope Ortho Tile Product has an `gsd` of 3.7 (or 4 if rounding), even though the pixel size of the images is 3.125. +For example, one might choose for WorldView-2 the Multispectral 20° off-nadir value of 2.07 +and for WorldView-3 the Multispectral 20° off-nadir value of 1.38. diff --git a/item-spec/item-spec.md b/item-spec/item-spec.md index 84209294..48358145 100644 --- a/item-spec/item-spec.md +++ b/item-spec/item-spec.md @@ -42,8 +42,8 @@ Items are represented in JSON format and are very flexible. Any JSON object that required fields is a valid STAC Item. - Examples: - - See the [minimal example](../examples/simple-item.json), as well as a [more fleshed example](../examples/core-item.json) that contains a number of - current best practices. + - See the [minimal example](../examples/simple-item.json), + as well as a [more fleshed example](../examples/core-item.json) that contains a number of current best practices. - Real world [implementations](https://stacindex.org/catalogs) are also available. - [JSON Schema](json-schema/item.json) @@ -63,7 +63,7 @@ inherited from GeoJSON. | properties | [Properties Object](#properties-object) | **REQUIRED.** A dictionary of additional metadata for the Item. | | links | \[[Link Object](#link-object)] | **REQUIRED.** List of link objects to resources and related URLs. A link with the `rel` set to `self` is strongly recommended. | | assets | Map | **REQUIRED.** Dictionary of asset objects that can be downloaded, each with a unique key. | -| collection | string | The `id` of the STAC Collection this Item references to (see [`collection` relation type](#relation-types)). This field is *required* if such a relation type is present. This field provides an easy way for a user to search for any Items that belong in a specified Collection. Must be a non-empty string. | +| collection | string | The `id` of the STAC Collection this Item references to (see [`collection` relation type](#relation-types)). This field is *required* if such a relation type is present and is *not allowed* otherwise. This field provides an easy way for a user to search for any Items that belong in a specified Collection. Must be a non-empty string. | ### Additional Field Information @@ -73,23 +73,24 @@ In general, STAC versions can be mixed, but please keep the [recommended best pr #### stac_extensions -A list of extensions the Item implements. -This list must only contain extensions that extend the Item itself, see the the 'Scope' column in the list of -extensions. The list contains URLs to the JSON Schema files it can be validated against. -If an extension such as the `tiled-assets` extension has influence on multiple parts of the whole catalog -structure, it must be listed in all affected parts (e.g. Catalog, Collection and Item for the `tiled-assets` extension). +A list of extensions the Item implements. +The list consists of URLs to JSON Schema files that can be used for validation. +This list must only contain extensions that extend the Item specification itself, +see the the 'Scope' for each of the extensions. #### id It is important that an Item identifier is unique within a Collection, and that the [Collection identifier](../collection-spec/collection-spec.md#id) in turn is unique globally. Then the two can be combined to give a globally unique identifier. Items are *[strongly recommended](#collections)* to have Collections, and not having one makes -it more difficult to be used in the wider STAC ecosystem. If an Item does not have a Collection, then the Item identifier should be unique within its root Catalog. +it more difficult to be used in the wider STAC ecosystem. +If an Item does not have a Collection, then the Item identifier should be unique within its root Catalog or root Collection. As most geospatial assets are already uniquely defined by some -identification scheme from the data provider it is recommended to simply use that ID. Data providers are advised to include sufficient information to make their -IDs globally unique, including things like unique satellite IDs. See the [id section of best practices](../best-practices.md#field-and-id-formatting) for -additional recommendations. +identification scheme from the data provider it is recommended to simply use that ID. +Data providers are advised to include sufficient information to make their IDs globally unique, +including things like unique satellite IDs. +See the [id section of best practices](../best-practices.md#item-ids) for additional recommendations. #### assets @@ -108,7 +109,15 @@ Items that are linked to, but the best practices around this are still emerging. #### bbox -Bounding Box of the asset represented by this Item using either 2D or 3D geometries, formatted according to [RFC 7946, section 5](https://tools.ietf.org/html/rfc7946#section-5). The length of the array must be 2\*n where n is the number of dimensions. The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters and the elevation of the northeasterly most extent is the maximum. This field enables more naive clients to easily index and search geospatially. STAC compliant APIs are required to compute intersection operations with the Item's geometry field, not its bbox. +Bounding Box of the asset represented by this Item using either 2D or 3D geometries, +formatted according to [RFC 7946, section 5](https://tools.ietf.org/html/rfc7946#section-5). +The length of the array must be 2\*n where n is the number of dimensions. +The array contains all axes of the southwesterly most extent followed by all axes of the northeasterly most extent specified in +Longitude/Latitude or Longitude/Latitude/Elevation based on [WGS 84](http://www.opengis.net/def/crs/OGC/1.3/CRS84). +When using 3D geometries, the elevation of the southwesterly most extent is the minimum depth/height in meters +and the elevation of the northeasterly most extent is the maximum. +This field enables more naive clients to easily index and search geospatially. +STAC compliant APIs are required to compute intersection operations with the Item's geometry field, not its bbox. ### Properties Object @@ -118,7 +127,7 @@ resources below. | Field Name | Type | Description | | ---------- | ------------ | ------------------------------------------------------------ | -| datetime | string\|null | **REQUIRED.** The searchable date and time of the assets, in UTC. It is formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). `null` is allowed, but requires `start_datetime` and `end_datetime` from [common metadata](common-metadata.md#date-and-time-range) to be set. | +| datetime | string\|null | **REQUIRED.** The searchable date and time of the assets, which must be in UTC. It is formatted according to [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6). `null` is allowed, but requires `start_datetime` and `end_datetime` from [common metadata](common-metadata.md#date-and-time-range) to be set. | #### datetime @@ -127,7 +136,8 @@ or representative time in the case of assets that are combined together. Though complex thing to capture, for this purpose keep in mind the STAC spec is primarily searching for data, so use whatever single date and time is most useful for a user to search for. STAC content extensions may further specify the meaning of the main `datetime` field, and many will also add more -datetime fields. +datetime fields. **All times in STAC metadata should be in [Coordinated Universal +Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) (UTC).** If there's clearly no meaningful single 'nominal' time, it is allowed to use `null` instead. In this case it is **required** to specify a temporal interval with the fields `start_datetime` and `end_datetime` from [common metadata](common-metadata.md#date-and-time-range). For example, if @@ -143,11 +153,11 @@ to [select only those necessary for search](../best-practices.md#field-selection Where possible metadata fields should be mapped to the STAC Common Metadata and widely used extensions, to enable cross-catalog search on known fields. -* [STAC Common Metadata](common-metadata.md#stac-common-metadata) - A list of fields commonly used +- [STAC Common Metadata](common-metadata.md#stac-common-metadata) - A list of fields commonly used throughout all domains. These optional fields are included for STAC Items by default. -* [Extensions](../extensions/README.md) - Additional fields that are more specific, +- [Extensions](../extensions/README.md) - Additional fields that are more specific, such as [EO](https://github.com/stac-extensions/eo), [View](https://github.com/stac-extensions/view). -* [Custom Extensions](../extensions/README.md#extending-stac) - It is generally allowed to add custom +- [Custom Extensions](../extensions/README.md#extending-stac) - It is generally allowed to add custom fields but it is recommended to add multiple fields for related values instead of a nested object, e.g., two fields `view:azimuth` and `view:off_nadir` instead of a field `view` with an object value containing the two fields. The convention (as used within Extensions) is for related fields @@ -173,17 +183,19 @@ For a full discussion of the situations where relative and absolute links are re #### Relation types -STAC Items use a variety of `rel` types in the link object, to describe the exact nature of the link between this Item and the entity it is linking to. -It is recommended to use the official [IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. +STAC Items use a variety of `rel` types in the link object, +to describe the exact nature of the link between this Item and the entity it is linking to. +It is recommended to use the official +[IANA Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml) where possible. The following table explains places where STAC use custom `rel` types are used with Items. This happens where there is not a clear official option, or where STAC uses an official type but adds additional meaning for the STAC context. | Type | Description | | ------------ | ------------------------------------------------------------ | | self | STRONGLY RECOMMENDED. *Absolute* URL to the Item if it is available at a public URL. This is particularly useful when in a download package that includes metadata, so that the downstream user can know where the data has come from. | -| root | URL to the root STAC Catalog or Collection. | -| parent | URL to the parent STAC Catalog or Collection. | -| collection | STRONGLY RECOMMENDED. URL to a Collection. *Absolute* URLs should be used whenever possible. The referenced Collection is STRONGLY RECOMMENDED to implement the same STAC version as the Item. | +| root | URL to the root STAC entity (Catalog or Collection). | +| parent | URL to the parent STAC entity (Catalog or Collection). | +| collection | STRONGLY RECOMMENDED. URL to a Collection. *Absolute* URLs should be used whenever possible. The referenced Collection is STRONGLY RECOMMENDED to implement the same STAC version as the Item. A link with this `rel` type is *required* if the `collection` field in properties is present. | | derived_from | URL to a STAC Item that was used as input data in the creation of this Item. | A more complete list of potential `rel` types and their meaning in STAC can be found in the [Using Relation @@ -191,16 +203,20 @@ Types](../best-practices.md#using-relation-types) best practice. ##### derived_from -*Note regarding the type `derived_from`: A full provenance model is far beyond the scope of STAC, and the goal is to align with any good independent spec -that comes along for that. But the derived_from field is seen as a way to encourage fuller specs and at least start a linking +*Note regarding the type `derived_from`: A full provenance model is far beyond the scope of STAC, +and the goal is to align with any good independent spec that comes along for that. +But the derived_from field is seen as a way to encourage fuller specs and at least start a linking structure that can be used as a jumping off point for more experiments in provenance tracking* #### Collections -Items are *strongly recommended* to provide a link to a STAC Collection definition. It is important as Collections provide additional information about a set of items, for example the license, provider and other information +Items are *strongly recommended* to provide a link to a STAC Collection definition. +It is important as Collections provide additional information about a set of items, +for example the license, provider and other information giving context on the overall set of data that an individual Item is a part of. -If Items are part of a STAC Collection, the [STAC Collection spec *requires* Items to link back to the Collection](../collection-spec/collection-spec.md#relation-types). +If Items are part of a STAC Collection, the +[STAC Collection spec *requires* Items to link back to the Collection](../collection-spec/collection-spec.md#relation-types). Linking back must happen in two places: 1. The field `collection` in an Item must be filled (see section 'Item fields'). It is the `id` of a STAC Collection. @@ -255,8 +271,8 @@ Like the Link `rel` field, the `roles` field can be given any value, however her | metadata | A metadata sidecar file describing the data in this Item, for example the Landsat-8 MTL file. | It is STRONGLY RECOMMENDED to add to each STAC Item -* a thumbnail with the role `thumbnail` for preview purposes -* one or more data file although it doesn't need to use the suggested role `data` +- a thumbnail with the role `thumbnail` for preview purposes +- one or more data file although it doesn't need to use the suggested role `data` Note that multiple roles per asset are encouraged: pick all the ones that apply. So many should have the 'data' role, and then another role to describe how the data is used. For more information on how to use roles see the [Asset @@ -279,7 +295,8 @@ For example, `gsd` defined for an Item represents the best Ground Sample Distanc However, some assets may be lower resolution and thus have a higher `gsd`. The `eo:bands` field from the EO extension defines an array of spectral bands. However, it may be useful instead to specify the bands that are used in a particular asset. -For an example see the [sentinel2-sample](https://github.com/stac-utils/stac-examples/blob/main/sentinel2/sentinel2-sample.json). The Sentinel-2 overall `gsd` is 10m, because this is +For an example see the [sentinel2-sample](https://github.com/stac-utils/stac-examples/blob/main/sentinel2/sentinel2-sample.json). +The Sentinel-2 overall `gsd` is 10m, because this is the best spatial resolution among all the bands and is defined in Item properties so it can be searched on. In the example Band 5 and others have a `gsd` of 20m, so that asset specifies the `gsd` as well, which overrides the Item `gsd` for this one asset. The example also includes reduced resolution versions of files included as assets, using `gsd` to represent @@ -289,7 +306,8 @@ For `eo:bands`, it could be put in Item properties as an array of all the bands, the assets each define an array containing the spectral band information for that asset (in the order the bands appear in the file). -For examples of fields that this construct is recommended for, see the [section of STAC Best Practices](../best-practices.md#common-use-cases-of-additional-fields-for-assets) +For examples of fields that this construct is recommended for, +see the [section of STAC Best Practices](../best-practices.md#common-use-cases-of-additional-fields-for-assets) that talks about common use cases of additional fields for assets. ## Media Type for STAC Item diff --git a/item-spec/json-schema/basics.json b/item-spec/json-schema/basics.json index a5b26a3d..68e8f37a 100644 --- a/item-spec/json-schema/basics.json +++ b/item-spec/json-schema/basics.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/basics.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/basics.json#", "title": "Basic Descriptive Fields", "type": "object", "properties": { diff --git a/item-spec/json-schema/datetime.json b/item-spec/json-schema/datetime.json index 3a1480db..4c7a3a14 100644 --- a/item-spec/json-schema/datetime.json +++ b/item-spec/json-schema/datetime.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/datetime.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#", "title": "Date and Time Fields", "type": "object", "dependencies": { @@ -20,29 +20,34 @@ "title": "Date and Time", "description": "The searchable date/time of the assets, in UTC (Formatted in RFC 3339) ", "type": ["string", "null"], - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" }, "start_datetime": { "title": "Start Date and Time", "description": "The searchable start date/time of the assets, in UTC (Formatted in RFC 3339) ", "type": "string", - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" }, "end_datetime": { "title": "End Date and Time", "description": "The searchable end date/time of the assets, in UTC (Formatted in RFC 3339) ", "type": "string", - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" }, "created": { "title": "Creation Time", "type": "string", - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" }, "updated": { "title": "Last Update Time", "type": "string", - "format": "date-time" + "format": "date-time", + "pattern": "(\\+00:00|Z)$" } } } \ No newline at end of file diff --git a/item-spec/json-schema/instrument.json b/item-spec/json-schema/instrument.json index 78d836ba..688c4a49 100644 --- a/item-spec/json-schema/instrument.json +++ b/item-spec/json-schema/instrument.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/instrument.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#", "title": "Instrument Fields", "type": "object", "properties": { diff --git a/item-spec/json-schema/item.json b/item-spec/json-schema/item.json index 34d351f3..8d428678 100644 --- a/item-spec/json-schema/item.json +++ b/item-spec/json-schema/item.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/item.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#", "title": "STAC Item", "type": "object", "description": "This object represents the metadata for an item in a SpatioTemporal Asset Catalog.", @@ -93,24 +93,16 @@ "stac_version": { "title": "STAC version", "type": "string", - "const": "1.0.0-rc.2" + "const": "1.0.0" }, "stac_extensions": { "title": "STAC extensions", "type": "array", "uniqueItems": true, "items": { - "anyOf": [ - { - "title": "Reference to a JSON Schema", - "type": "string", - "format": "iri" - }, - { - "title": "Reference to a core extension", - "type": "string" - } - ] + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" } }, "id": { @@ -159,12 +151,42 @@ ] } ] - }, - "collection": { - "title": "Collection ID", - "description": "The ID of the STAC Collection this Item references to.", - "type": "string", - "minLength": 1 + } + }, + "if": { + "properties": { + "links": { + "contains": { + "required": [ + "rel" + ], + "properties": { + "rel": { + "const": "collection" + } + } + } + } + } + }, + "then": { + "required": [ + "collection" + ], + "properties": { + "collection": { + "title": "Collection ID", + "description": "The ID of the STAC Collection this Item references to.", + "type": "string", + "minLength": 1 + } + } + }, + "else": { + "properties": { + "collection": { + "not": {} + } } } } diff --git a/item-spec/json-schema/licensing.json b/item-spec/json-schema/licensing.json index a20dde60..ca0eed8b 100644 --- a/item-spec/json-schema/licensing.json +++ b/item-spec/json-schema/licensing.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/licensing.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/licensing.json#", "title": "Licensing Fields", "type": "object", "properties": { diff --git a/item-spec/json-schema/provider.json b/item-spec/json-schema/provider.json index b80c734e..01cfadce 100644 --- a/item-spec/json-schema/provider.json +++ b/item-spec/json-schema/provider.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.stacspec.org/v1.0.0-rc.2/item-spec/json-schema/provider.json#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/provider.json#", "title": "Provider Fields", "type": "object", "properties": { diff --git a/overview.md b/overview.md index 3d752aae..e87d43ec 100644 --- a/overview.md +++ b/overview.md @@ -3,7 +3,7 @@ There are three component specifications that together make up the core SpatioTemporal Asset Catalog specification. Each can be used alone, but they work best in concert with one another. The [STAC API specification](https://github.com/radiantearth/stac-api-spec) builds on top of that core, but is out of scope for this overview. An [Item](item-spec/item-spec.md) represents a -single [spatiotemporal asset](#what-is-a-spatiotemporal-asset) as GeoJSON so it can be searched. +single [spatiotemporal asset](#what-is-a-spatiotemporal-asset) as [GeoJSON](https://geojson.org/) so it can be searched. The [Catalog](catalog-spec/catalog-spec.md) specification provides structural elements, to group Items and [Collections](collection-spec/collection-spec.md). Collections *are* catalogs, that add more required metadata and describe a group of related Items. For more on the differences see the [section below](#catalogs-vs-collections). @@ -11,6 +11,29 @@ describe a group of related Items. For more on the differences see the [section A [UML diagram](https://en.wikipedia.org/wiki/Unified_Modeling_Language) of the [STAC model](STAC-UML.pdf) is also provided to help with navigating the specification. +## Foundations + +STAC is built on top of many great standards and practices. Every part of STAC is +[JSON](https://www.json.org/json-en.html), and [GeoJSON](https://geojson.org/) provides the core geometry fields +and [features](https://en.wikipedia.org/wiki/Simple_Features) definition. All fields are described in the +specifications, and the acceptable values are defined with [JSON Schema](https://json-schema.org/). The released +JSON Schemas provide the core testing definitions, and are used in an array of validation tools. We also rely +on [RFC 8288 (Web Linking)](https://tools.ietf.org/rfc/rfc8288.txt) to express relationships between resources, +and IANA [Media Types](https://en.wikipedia.org/wiki/Media_type) to describe file formats and format contents. +The [OGC API - Features](https://ogcapi.ogc.org/features/) standard is a final core building block. The STAC +Collection extends the [Collection](http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#_collection_) +JSON defined in OGC API - Features (and the full API definition is the foundation for the STAC API specification). + +The STAC specifications are written to be understandable without needing a full background in these. But if you +want to get deep into STAC tool implementation and are not familiar with any of the standards mentioned above it is +recommended to read up on them. STAC development is guided by set of core philosophical tenets, like +building small reusable parts that are loosely coupled, focusing on developers, and more - see our the +[principles](principles.md) document to learn more. + +*Note: Setting a field in JSON to `null` is not equivalent to a field not appearing in STAC, as JSON Schema tools treat +them differently. STAC defines `null` explicitly for some fields, where it has a particular meaning. So `null` should +not be used unless the STAC spec defines its use - instead the field should be left out entirely.* + ## Item Overview Fundamental to any SpatioTemporal Asset Catalog, an [Item](item-spec/item-spec.md) object represents a unit of @@ -21,10 +44,10 @@ and can be easily read by any modern GIS or geospatial library, and it describes The STAC Item JSON specification uses the GeoJSON geometry to describe the location of the asset, and then includes additional information: -* the time the asset represents; -* a thumbnail for quick browsing; -* asset links, to enable direct download or streaming access of the asset; -* relationship links, allowing users to traverse other related resources and STAC Items. +- the time the asset represents; +- a thumbnail for quick browsing; +- asset links, to enable direct download or streaming access of the asset; +- relationship links, allowing users to traverse other related resources and STAC Items. A STAC Item can contain additional fields and JSON structures to communicate more information about the asset, so it can be easily searched. STAC provides a core set of @@ -53,10 +76,11 @@ A Catalog is a very simple construct - it just provides links to Items or to oth The closest analog is a folder in a file structure, it is the container for Items, but it can also hold other containers (folders / catalogs). -The Collection specification shares some fields with the catalog spec but has a number of additional fields: +The Collection entity shares most fields with the Catalog entity but has a number of additional fields: license, extent (spatial and temporal), providers, keywords and summaries. Every Item in a Collection links back to their Collection, so clients can easily find fields like the license. Thus every Item implicitly -shares the fields described in their parent Collection. +shares the fields described in their parent Collection. Collection entities can be used just like Catalog +entities to provide structure, as they provide all the same options for linking and organizing. But what *should* go in a Collection, versus just in a Catalog? A Collection will generally consist of a set of assets that are defined with the same properties and share higher level metadata. In the @@ -68,8 +92,8 @@ to break the recommendation. Catalogs in turn are used for two main things: -* Split overly large collections into groups -* Group collections into a catalog of Collections (e.g. as entry point for navigation to several Collections). +- Split overly large collections into groups +- Group collections into a catalog of Collections (e.g. as entry point for navigation to several Collections). The first case allows users to browse down into the Items of large collections. A collection like Landsat usually would start with path and row Catalogs to group by geography, and then year, @@ -78,27 +102,30 @@ provide multiple grouping paths, serving as a sort of faceted search. The second case is used when one wants to represent diverse data in a single place. If an organization has an internal catalog with Landsat 8, Sentinel 2, NAIP data and several commercial imagery providers -then they'd have a root catalog that would link to a number of different Collections. +then they'd have a root Catalog that would link to a number of different Collections. So in conclusion it's best to use Collections for what you want user to find as starting point, and then -catalogs are just for structuring and grouping the data. Future work includes a mechanism to actually +Catalogs are just for structuring and grouping the data. Future work includes a mechanism to actually search Collection-level data, hopefully in concert with other specifications. ## Catalog Overview +*NOTE: The below examples all say Catalog, but those can all be Collections as well, as it has all the fields necessary to +serve as a Catalog* + There are two required element types of a Catalog: Catalog and Item. A STAC Catalog points to [STAC Items](item-spec/README.md), or to other STAC catalogs. It provides a simple linking structure that can be used recursively so that many Items can be included in a single Catalog, organized however the implementor desires. -STAC makes no formal distinction between a "root" catalog and the "child" catalogs. A root catalog -is simply the top-most catalog -- it has no parent. A nested catalog structure is useful (and +STAC makes no formal distinction between a "root" Catalog and the "child" Catalogs. A root Catalog +is simply the top-most Catalog or Collection -- it has no parent. A nested catalog structure is useful (and recommended) for breaking up massive numbers of catalog Items into logical groupings. For example, it might make sense to organize a catalog by date (year, month, day), or geography (continent, country, state/prov). See the [Catalog Layout](best-practices.md#catalog-layout) best practices section for more. -A simple Catalog structure might look like this: +A simple STAC structure might look like this: - catalog (root) - catalog @@ -164,8 +191,8 @@ each Item and Catalog, as well as ways to achieve that. ## Collection Overview -A STAC Collection extends the core fields of the Catalog construct to provide additional metadata to describe the set of Items it -contains. The required fields are fairly +A STAC Collection includes the core fields of the Catalog entity and also provides additional metadata to describe +the set of Items it contains. The required fields are fairly minimal - it includes the 4 required Catalog fields (id, description, stac_version and links), and adds license and extents. But there are a number of other common fields defined in the spec, and more common fields are also defined in [STAC extensions](extensions/). These serve as basic metadata, and ideally Collections also link to diff --git a/package.json b/package.json index 3fb2aeee..0012c9c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stac-spec", - "version": "1.0.0-rc.2", + "version": "1.0.0", "description": "STAC spec helpers to check the spec.", "repository": "https://github.com/radiantearth/stac-spec", "license": "Apache-2.0", @@ -21,6 +21,6 @@ "remark-preset-lint-markdown-style-guide": "^3.0.0", "remark-preset-lint-recommended": "^4.0.0", "remark-validate-links": "^10.0.0", - "stac-node-validator": "^0.4.6" + "stac-node-validator": "^1.1.0" } } diff --git a/principles.md b/principles.md index 03306596..72ef53be 100644 --- a/principles.md +++ b/principles.md @@ -4,42 +4,42 @@ reviewed in pull requests, approved by consensus. The goal of the principles is [bikeshedding](http://bikeshed.org/) - lay down some meta-rules so we can focus on creating useful core geospatial standards. -* **Creation and evolution of specs in Github**, using Open Source principles +- **Creation and evolution of specs in Github**, using Open Source principles (please read [Producing OSS](http://producingoss.com/) if that phrase doesn't immediately make sense to you). The collaboration facilities of Github should be used to their full extent. All proposed improvements and changes should come in the form of pull requests, using code review functionality to discuss changes. -* **JSON + REST + HTTP at the core.** JSON has won over XML, and REST over SOAP. We embrace them and +- **JSON + REST + HTTP at the core.** JSON has won over XML, and REST over SOAP. We embrace them and are not considering legacy options. Forward looking protocols can be considered as extensions, but the default specifications should be in JSON, following best REST practices. HTTP caching and error codes should be leveraged at the core. GeoJSON has already defined the core geospatial JSON response, so it should also be core. [JSON API](http://jsonapi.org/) should be used as basis of decisions where possible. -* **Small Reusable Pieces Loosely Coupled** - Each specification should be as focused as possible, +- **Small Reusable Pieces Loosely Coupled** - Each specification should be as focused as possible, defining one core concept and refraining from describing lots of options. Additional options can be made as separate specifications that build on the core. But the core specs should be small and easily understandble, with clear defaults for any choice. Handling complex cases should be possible by combining discrete pieces. Implementors should not be forced to implement lots of options just for basic compliance - they should be able to pick and choose which pieces are relevant to the problems they are trying to solve. -* **Focus on the developer**. Specifications should aim for implementability - any explanation or design choice +- **Focus on the developer**. Specifications should aim for implementability - any explanation or design choice should be considered with a developer audience. And specifications should be accessible to developers who do not have geospatial background. A developer should not need to understand 'projections' to implement a simple feature access service. But we should think through the spec extensions they could use in the future when their client asks for data in a different projection. -* **Working code required.** Proposed changes should be accompanied by working code +- **Working code required.** Proposed changes should be accompanied by working code (ideally with a link to an online service running the code). A reference implementation should be available online to power the interactive documentation. Fully accepted specifications should have at least 3 implementations that cover the entire specification. Extensions have their own [Extension Maturity](./extensions/README.md#extension-maturity) model. -* **Design for scale.** The design should work great with more data than can be imagined right now. +- **Design for scale.** The design should work great with more data than can be imagined right now. Ideally implementations are built with large test data sets to validate that they will work. Everything should be compatible with content distribution network (CDN) caching. ## Resources -* Open Source Principles - [Producing Open Source Software](http://producingoss.org) by Karl Fogel. -* Best Practices JSON API Design - [JSON API](http://jsonapi.org/) best practices for making API's with JSON -* Pragmatic REST - [Web API Design: Crafting interfaces that developers love](https://pages.apigee.com/rs/apigee/images/api-design-ebook-2012-03.pdf) -* Open API Initiative - [OpenAPIs.org](https://openapis.org/) +- Open Source Principles - [Producing Open Source Software](http://producingoss.org) by Karl Fogel. +- Best Practices JSON API Design - [JSON API](http://jsonapi.org/) best practices for making API's with JSON +- Pragmatic REST - [Web API Design: Crafting interfaces that developers love](https://pages.apigee.com/rs/apigee/images/api-design-ebook-2012-03.pdf) +- Open API Initiative - [OpenAPIs.org](https://openapis.org/) diff --git a/process.md b/process.md index 2250e844..92fbb7b6 100644 --- a/process.md +++ b/process.md @@ -24,18 +24,18 @@ and there is also information on how to [run the CI checks locally](CONTRIBUTING The current list of people who are part of the 'core STAC team', and can approve pull requests. -* [Alex Kaminsky](https://github.com/alkamin) -* [Alexandra Kirk](https://github.com/anayeaye) -* [Chris Holmes](http://github.com/cholmes) -* [Emmanuel Mathot](https://github.com/emmanuelmathot) -* [Michael Smith](https://github.com/hgs-msmith) -* [James Banting](https://github.com/jbants) -* [James Santucci](https://github.com/jisantuc) -* [Josh Fix](https://github.com/joshfix) -* [Rob Emanuele](https://github.com/lossyrob) -* [Matthias Mohr](https://github.com/m-mohr) -* [Matt Hanson](https://github.com/matthewhanson) -* [Phil Varner](https://github.com/philvarner) +- [Alex Kaminsky](https://github.com/alkamin) +- [Alexandra Kirk](https://github.com/anayeaye) +- [Chris Holmes](http://github.com/cholmes) +- [Emmanuel Mathot](https://github.com/emmanuelmathot) +- [Michael Smith](https://github.com/hgs-msmith) +- [James Banting](https://github.com/jbants) +- [James Santucci](https://github.com/jisantuc) +- [Josh Fix](https://github.com/joshfix) +- [Rob Emanuele](https://github.com/lossyrob) +- [Matthias Mohr](https://github.com/m-mohr) +- [Matt Hanson](https://github.com/matthewhanson) +- [Phil Varner](https://github.com/philvarner) Anyone can be nominated to the core STAC team, and that generally happens after contributing a few pull requests and/or helping review other PR's. Nominations are reviewed by the [PSC](#project-steering-committee), and must recieve @@ -45,31 +45,31 @@ and/or helping review other PR's. Nominations are reviewed by the [PSC](#project To release a new version of the STAC spec the following list of tasks must be done. -* **Update Issue Tracker**: Each release has a [milestone](https://github.com/radiantearth/stac-spec/milestones) in the github +- **Update Issue Tracker**: Each release has a [milestone](https://github.com/radiantearth/stac-spec/milestones) in the github issue tracker, and before a release is done all open issues that are filed against it should be reviewed. All issues do not need to be completed, but the core release team should all review the issues to make sure that the critical ones for the release have been addressed. Issues that aren't seen as essential should be moved to future releases, so that there are no open issues against the milestone. -* **Agreement from the Project Steering Committee**: The PSC should meet (on phone or on gitter) and decided that the release is ready. +- **Agreement from the Project Steering Committee**: The PSC should meet (on phone or on gitter) and decided that the release is ready. This should include review of the issues, as well as looking at the spec holistically, to make sure the new changes keep with a coherent whole. -* **Final Spec Read Through**: There should be a final read through of the core specification to make sure it makes sense +- **Final Spec Read Through**: There should be a final read through of the core specification to make sure it makes sense and there are no typos, errors, etc. -* **Update the version numbers**: There are several places in the spec that use the version number or a branch name in text +- **Update the version numbers**: There are several places in the spec that use the version number or a branch name in text or a link. These include the markdown files and the JSON schemas. Right now the best thing to do is just a search & replace for the last version number and `https://schemas.stacspec.org/dev/` with `https://schemas.stacspec.org//` (in JSON Schemas, don't replace it here). `` must correspond with the tag on GitHub, usually including a leading `v`. Hopefully in the future there will be scripts to do this. -* **Update the Changelog**: The [changelog](CHANGELOG.md) should be reviewed to make sure it includes all major improvements +- **Update the Changelog**: The [changelog](CHANGELOG.md) should be reviewed to make sure it includes all major improvements in the release. And anything in 'unreleased' section should move to the version of the spec to be released. -* **Merge dev to master**: As there is no 'build' process, since the specification *is* the markdown files in the github +- **Merge dev to master**: As there is no 'build' process, since the specification *is* the markdown files in the github repository, the key step in a release is to merge the `dev` branch into `master`, as `master` is the current stable state of the spec. -* **Release on Github**: The final step to create the release is to add a new 'release' on +- **Release on Github**: The final step to create the release is to add a new 'release' on . This should use a tag like the others, with a 'v' prefix and then the release number, like v0.5.2. The changelog should be copied over to be the release notes, and then also include a link to the full milestone of everything closed in the issue tracker. -* **Promote the release**: A blog post and tweet should be composed and sent out, and then inform those in the gitter channel +- **Promote the release**: A blog post and tweet should be composed and sent out, and then inform those in the gitter channel to post / promote it. #### Release Candidates @@ -98,15 +98,16 @@ decision making authority. This consists of individuals who are intended to repr stake in the specification and surrounding ecosystem. An odd number is chosen to facilitate the voting process and help prevent ties. This committee also handles the allocation of any funds that are raised for the project. -Turnover is allowed and expected to accommodate people only able to become active on the project in intervals. A PSC member may step down at any time. +Turnover is allowed and expected to accommodate people only able to become active on the project in intervals. +A PSC member may step down at any time. #### Current Project Steering Committee -* [Matthias Mohr](https://github.com/m-mohr) - University of Muenster, [OpenEO](https://openeo.org/) and [Radiant Earth](https://www.radiant.earth/) -* [Matt Hanson](https://github.com/matthewhanson) - [Element 84](https://www.element84.com/) -* [James Banting](https://github.com/jbants) - [SparkGeo](https://sparkgeo.com/) -* [Rob Emanuele](https://github.com/lossyrob) - [Microsoft](https://microsoft.com/) -* [Chris Holmes](https://github.com/cholmes) - [Planet](https://planet.com) and [Radiant Earth](https://www.radiant.earth/) +- [Matthias Mohr](https://github.com/m-mohr) - University of Muenster, [OpenEO](https://openeo.org/) and [Radiant Earth](https://www.radiant.earth/) +- [Matt Hanson](https://github.com/matthewhanson) - [Element 84](https://www.element84.com/) +- [James Banting](https://github.com/jbants) - [SparkGeo](https://sparkgeo.com/) +- [Rob Emanuele](https://github.com/lossyrob) - [Microsoft](https://microsoft.com/) +- [Chris Holmes](https://github.com/cholmes) - [Planet](https://planet.com) and [Radiant Earth](https://www.radiant.earth/) #### PSC Membership diff --git a/schema.json b/schema.json deleted file mode 100644 index e69de29b..00000000 From eb130a45c7c16ae024e6c7c2fd9ac0fe4dadf96f Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Mon, 7 Jun 2021 20:17:03 -0400 Subject: [PATCH 61/61] allow html in markdown, fix problems with filter openapi spec --- .circleci/rc.yaml | 4 ++-- fragments/filter/openapi.yaml | 32 ++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.circleci/rc.yaml b/.circleci/rc.yaml index b0dd538c..f70a2fb8 100644 --- a/.circleci/rc.yaml +++ b/.circleci/rc.yaml @@ -4,7 +4,7 @@ plugins: # Apply some recommended defaults for consistency - remark-preset-lint-consistent - remark-preset-lint-recommended - - lint-no-html + # - lint-no-html # General formatting - - remark-lint-emphasis-marker - '*' @@ -40,4 +40,4 @@ plugins: - space # Tables - remark-lint-table-pipes - - remark-lint-no-literal-urls \ No newline at end of file + - remark-lint-no-literal-urls diff --git a/fragments/filter/openapi.yaml b/fragments/filter/openapi.yaml index 98af69e5..bdfb7299 100644 --- a/fragments/filter/openapi.yaml +++ b/fragments/filter/openapi.yaml @@ -32,14 +32,21 @@ paths: responses: '200': description: A JSON Schema defining the Queryables allowed in CQL expressions - content: - application/schema+json: - schema: - $ref: 'https://json-schema.org/draft/2019-09/schema' + # content: + # application/schema+json: + # schema: + # $ref: 'https://json-schema.org/draft/2019-09/schema' default: $ref: '../../core/commons.yaml#/components/responses/Error' /collections/{collectionId}: get: + parameters: + - in: path + name: collectionId + schema: + type: string + required: true + description: ID of Collection responses: '200': description: Collection description @@ -56,15 +63,22 @@ paths: This endpoint returns a list of variable terms that can be used in CQL expressions. The precise definition of this can be found in the OGC API - Features - Part 3: Filtering and the Common Query Language (CQL) specification. + parameters: + - in: path + name: collectionId + schema: + type: string + required: true + description: ID of Collection tags: - Queryables responses: '200': description: A JSON Schema defining the Queryables allowed in CQL expressions filtering only that collection - content: - application/schema+json: - schema: - $ref: 'https://json-schema.org/draft/2019-09/schema' + # content: + # application/schema+json: + # schema: + # $ref: 'https://json-schema.org/draft/2019-09/schema' default: $ref: '../../core/commons.yaml#/components/responses/Error' components: @@ -125,8 +139,6 @@ components: A CQL filter expression in the 'cql-text' encoding. type: string filter-cql-json: - description: | - A CQL filter expression in the 'cql-json' encoding. $ref: './cql.yml#/components/schemas/booleanValueExpression' filter-lang: description: |