diff --git a/.github/remark.yaml b/.github/remark.yaml index 564baf8..d4aa71e 100644 --- a/.github/remark.yaml +++ b/.github/remark.yaml @@ -12,7 +12,7 @@ plugins: - remark-lint-blockquote-indentation - remark-lint-no-consecutive-blank-lines - - remark-lint-maximum-line-length - - 150 + - 170 # Code - remark-lint-fenced-code-flag - remark-lint-fenced-code-marker diff --git a/README.md b/README.md index 725efee..83ef40f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # CF Extension Specification - **Title:** CF -- **Identifier:** +- **Identifier:** - **Field Name Prefix:** cf - **Scope:** Item, Collection - **Extension [Maturity Classification](https://github.com/radiantearth/stac-spec/tree/master/extensions/README.md#extension-maturity):** Proposal @@ -13,50 +13,94 @@ It adds a field to provide the Standard Name Table based on the [CF metadata con - Examples: - [Item](examples/item.json) and [Collection](examples/collection.json): Shows the basic usage of the extension in a STAC Item and a corresponding summarizing STAC Collection - - [Standalone Collection](examples/standalone_collection.json): - Shows the basic usage of the extension in a STAC Collection without items - [JSON Schema](json-schema/schema.json) - [Changelog](./CHANGELOG.md) ## Fields The fields in the table below can be used in these parts of STAC documents: + - [ ] Catalogs - [x] Collections - [x] Item Properties (incl. Summaries in Collections) - [x] Assets (for both Collections and Items, incl. Item Asset Definitions in Collections) +- [x] Bands +- [x] Data Cube Extension (`cube:variables`, `cube:dimensions`) - [ ] Links -| Field Name | Type | Description | -| ------------ | --------------------------- | ------------------------------------ | -| cf:parameter | \[[CF Object](#CF-object)\] | **REQUIRED**. CF Standard Name Table | +| Field Name | Type | Description | +| ---------------- | --------- | ----------- | +| cf:standard_name | string | Corresponds to the [CF Standard Name](https://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html). | +| cf:cell_methods | \[string|null] | A list of string or `null` attributes that describe the "method" applied to the data as defined in the CF conventions. | +| description | string | Corresponds to the [CF `long_name`](https://cfconventions.org/cf-conventions/cf-conventions.html#long-name). | +| unit | string | Corresponds to the [CF `units`](https://cfconventions.org/cf-conventions/cf-conventions.html#units). | ### Additional Field Information -#### cf:parameter +#### cf:standard_name + +The CF standard name is a controlled vocabulary term used in Climate and Forecast (CF) metadata conventions +to unambiguously describe the physical quantity represented by a variable in climate and geophysical data files. +If variable has a standard_name definition in the CF convention, it must be a non-empty value from the +[CF Standard Name Table](https://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html). +Otherwise `cf:standard_name` is an empty value. + +#### cf:cell_methods + +The cell_methods attribute in the CF (Climate and Forecast) convention is designed to describe how the values in a data variable +were derived with respect to one or more axes (e.g., time, latitude, longitude). Each "method" represents the statistical or +computational operations applied to data along specific axes. For example, if used within the datacube extension then the +order of cell_methods aligns with the order of spatial and temporal extensions. + +```json +"cube:variables": { + "some_variable": { + "cf:cell_methods": [null, "minimum"], + "dimensions": [ + "vertical_dimension1", + "time_interval2" + ] + } +} +``` -The `cf:parameter` array is used to describe the parameters in an Asset or Collection. -It requires at least one entry with a non-empty name. -This enables clients to read the file and understand which parameters are available. +In this case `null` indicates that no method is applied over the first dimension `vertical_dimension1` but +the `minimum` method is applied over the second dimension `time_interval2`. +These dimensions would be defined in the `cube:dimensions` fields. + +If a data value is representative of variation over a combination of axes this approach is not sufficient \(e.g. the standard +deviation of topographic height within a longitude-latitude gridbox would have `cell_methods="latitude: longitude: standard_deviation"`\). +Such `cell_methods` cannot be described as per dimension methods in an array and would need a plain string representation. + +```json +"cube:variables": { + "some_variable": { + "cf:cell_methods": "latitude: longitude: standard_deviation", + "dimensions": [ + "latitude", + "longitude" + ] + } +} +``` -If assets with a `cf:parameter` array are provided, the field may optionally be used in the -Item Properties or Collection and it must summarize the available parameters in the assets. -This must be the 'union' of all the possible parameters represented in assets. -If no assets are provided in a Collection, the field can be used freely to describe the Collection for e.g. search. -An Item is only allowed to use `cf:parameter` in its Properties if it has at least one asset with a defined parameter array. +See [CF Cell Methods](https://cfconventions.org/cf-conventions/cf-conventions.html#cell-methods) for more details. -The `cf:parameter` list in Item Properties or Collections should be considered merely informative - -clients should rely on the `cf:parameter` of each asset, if available. +#### description -#### CF Object +The description field as defined by the [NUG](https://docs.unidata.ucar.edu/nug/current/index.html) is meant to contain a long descriptive +name which may, for example, be used for labeling plots. +See [CF "long_name"](https://cfconventions.org/cf-conventions/cf-conventions.html#long-name) for more details. -This object should contain a variable name from the [CF list](https://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html) -and where applicable a unit from the [UDUNITS-2 database](https://docs.unidata.ucar.edu/udunits/current/) +#### unit -| Field Name | Type | Description | -| ---------- | ------ | ----------- | -| name | string | **REQUIRED**. Should be a non-empty value from the CF standard names list | -| unit | string | Indicates the unit, preferably available in the database from the UDUNITS-2 package (unidata) | +The unit of measurement for the values, preferably compliant to [UCUM](https://ucum.org/[) (unit code) +or [UDUNITS-2](https://ncics.org/portfolio/other-resources/udunits2/) (unit symbol or alternatively singular unit name). +Unit is not required for dimensionless quantities. A variable with no unit attribute is assumed to be +dimensionless. The conforming unit for quantities that represent fractions, or parts of a whole, is "1". +Descriptive information about dimensionless quantities, such as sea-ice concentration, cloud fraction, +probability, etc., should be given in the "description" attribute rather than the unit field. +See [CF "units"](https://cfconventions.org/cf-conventions/cf-conventions.html#dimensionless-units) for more details. ## Contributing @@ -68,16 +112,18 @@ for running tests are copied here for convenience. ### Running tests -The same checks that run as checks on PR's are part of the repository and can be run locally to verify that changes are valid. +The same checks that run as checks on PR's are part of the repository and can be run locally to verify that changes are valid. To run tests locally, you'll need `npm`, which is a standard part of any [node.js installation](https://nodejs.org/en/download/). -First you'll need to install everything with npm once. Just navigate to the root of this repository and on +First you'll need to install everything with npm once. Just navigate to the root of this repository and on your command line run: + ```bash npm install ``` Then to check markdown formatting and test the examples against the JSON schema, you can run: + ```bash npm test ``` @@ -85,6 +131,7 @@ npm test This will spit out the same texts that you see online, and you can then go and fix your markdown or examples. If the tests reveal formatting problems with the examples, you can fix them with: + ```bash npm run format-examples ``` diff --git a/examples/collection.json b/examples/collection.json index 3b69ed5..b1429b3 100644 --- a/examples/collection.json +++ b/examples/collection.json @@ -1,11 +1,11 @@ { "stac_version": "1.0.0", "stac_extensions": [ - "https://stac-extensions.github.io/cf/v0.2.0/schema.json" + "https://stac-extensions.github.io/cf/v0.3.0/schema.json" ], "type": "Collection", "id": "collection", - "title": "Collection with an Item", + "title": "A title", "description": "A description", "license": "Apache-2.0", "extent": { @@ -28,31 +28,15 @@ ] } }, + "assets": { + "example": { + "href": "https://example.com/examples/file.xyz" + } + }, "item_assets": { - "sea_surface_temperature": { - "type": "application/netcdf", - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } - ] - }, - "sea_ice_surface_temperature": { - "type": "application/netcdf", - "cf:parameter": [ - { - "name": "sea_ice_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } + "data": { + "roles": [ + "data" ] } }, @@ -61,20 +45,81 @@ "minimum": "2015-06-23T00:00:00Z", "maximum": "2019-07-10T13:44:56Z" }, - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" + "cube:dimensions": { + "time_interval1": { + "type": "temporal", + "description": "time interval that cell_methods is applied over", + "values": [ + -24 + ], + "unit": "h" }, - { - "name": "sea_ice_surface_temperature", - "unit": "K" + "vertical_dimension1": { + "type": "spatial", + "axis": "z", + "cf:standard_name": "height", + "description": "Height above ground level", + "unit": "m", + "values": [ + 10 + ] + }, + "time_interval2": { + "type": "temporal", + "description": "time interval that cell_methods is applied over", + "values": [ + -60 + ], + "unit": "min" }, - { - "name": "depth", - "unit": "m" + "vertical_dimension2": { + "type": "spatial", + "axis": "z", + "cf:standard_name": "height", + "description": "Air pressure", + "unit": "hPa", + "values": [ + 500 + ] } - ] + }, + "cube:variables": { + "sea_surface_temperature": { + "type": "data", + "cf:standard_name": "sea_surface_temperature", + "description": "Average temperature on sea surface for preceding 24 hours", + "unit": "K", + "cf:cell_methods": [ + "mean" + ], + "dimensions": [ + "time_interval1" + ] + }, + "wind_speed_at_10m": { + "type": "data", + "cf:standard_name": "wind_speed", + "description": "minimum wind speed in 1 hour at 10 m agl", + "unit": "kt", + "cf:cell_methods": [ + null, + "minimum" + ], + "dimensions": [ + "vertical_dimension1", + "time_interval2" + ] + }, + "temp_at_500hPa": { + "type": "data", + "cf:standard_name": "air_temperature", + "description": "air temperature at 500 hPa", + "unit": "degC", + "dimensions": [ + "vertical_dimension2" + ] + } + } }, "links": [ { @@ -82,8 +127,8 @@ "rel": "self" }, { - "href": "./item.json", + "href": "https://example.com/examples/item.json", "rel": "item" } ] -} \ No newline at end of file +} diff --git a/examples/item.json b/examples/item.json index 582dc9c..17ff380 100644 --- a/examples/item.json +++ b/examples/item.json @@ -1,7 +1,7 @@ { "stac_version": "1.0.0", "stac_extensions": [ - "https://stac-extensions.github.io/cf/v0.2.0/schema.json" + "https://stac-extensions.github.io/cf/v0.3.0/schema.json" ], "type": "Feature", "id": "item", @@ -40,68 +40,91 @@ }, "properties": { "datetime": "2020-12-11T22:38:32Z", - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" + "cube:dimensions": { + "time_interval1": { + "type": "temporal", + "description": "time interval that cell_methods is applied over", + "values": [ + -24 + ], + "unit": "h" + }, + "vertical_dimension1": { + "type": "spatial", + "axis": "z", + "cf:standard_name": "height", + "description": "Height above ground level", + "unit": "m", + "values": [ + 10 + ] }, - { - "name": "sea_ice_surface_temperature", - "unit": "K" + "time_interval2": { + "type": "temporal", + "description": "time interval that cell_methods is applied over", + "values": [ + -60 + ], + "unit": "min" }, - { - "name": "depth", - "unit": "m" + "vertical_dimension2": { + "type": "spatial", + "axis": "z", + "cf:standard_name": "height", + "description": "Air pressure", + "unit": "hPa", + "values": [ + 500 + ] } - ] + }, + "cube:variables": { + "sea_surface_temperature": { + "type": "data", + "cf:standard_name": "sea_surface_temperature", + "description": "Average temperature on sea surface for preceding 24 hours", + "unit": "K", + "cf:cell_methods": [ + "mean" + ], + "dimensions": [ + "time_interval1" + ] + }, + "wind_speed_at_10m": { + "type": "data", + "cf:standard_name": "wind_speed", + "description": "minimum wind speed in 1 hour at 10 m agl", + "unit": "kt", + "cf:cell_methods": [ + null, + "minimum" + ], + "dimensions": [ + "vertical_dimension1", + "time_interval2" + ] + }, + "temp_at_500hPa": { + "type": "data", + "cf:standard_name": "air_temperature", + "description": "air temperature at 500 hPa", + "unit": "degC", + "dimensions": [ + "vertical_dimension2" + ] + } + } }, "links": [ { "href": "https://example.com/examples/item.json", "rel": "self" - }, - { - "href": "./collection.json", - "rel": "collection" - }, - { - "href": "./collection.json", - "rel": "parent" - }, - { - "href": "./collection.json", - "rel": "root" } ], "assets": { - "sea_surface_temperature": { - "href": "https://example.com/examples/sea_surface_temperature.nc", - "type": "application/netcdf", - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } - ] - }, - "sea_ice_surface_temperature": { - "href": "https://example.com/examples/sea_ice_surface_temperature.nc", - "type": "application/netcdf", - "cf:parameter": [ - { - "name": "sea_ice_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } - ] + "data": { + "href": "https://example.com/examples/file.xyz" } - }, - "collection": "collection" -} \ No newline at end of file + } +} diff --git a/examples/standalone_collection.json b/examples/standalone_collection.json deleted file mode 100644 index c90c181..0000000 --- a/examples/standalone_collection.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "stac_version": "1.0.0", - "stac_extensions": [ - "https://stac-extensions.github.io/cf/v0.2.0/schema.json" - ], - "type": "Collection", - "id": "standalone_collection", - "title": "Collection without Items", - "description": "A description", - "license": "Apache-2.0", - "extent": { - "spatial": { - "bbox": [ - [ - 172.9, - 1.3, - 173, - 1.4 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2015-06-23T00:00:00Z", - null - ] - ] - } - }, - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } - ], - "assets": { - "example": { - "href": "https://example.com/examples/example.nc", - "cf:parameter": [ - { - "name": "sea_surface_temperature", - "unit": "K" - }, - { - "name": "depth", - "unit": "m" - } - ] - } - }, - "links": [ - { - "href": "https://example.com/examples/standalone_collection.json", - "rel": "self" - } - ] -} \ No newline at end of file diff --git a/json-schema/schema.json b/json-schema/schema.json index a950413..dcf2b14 100644 --- a/json-schema/schema.json +++ b/json-schema/schema.json @@ -1,150 +1,194 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://stac-extensions.github.io/cf/v0.2.0/schema.json#", - "title": "CF Extension", + "$id": "https://stac-extensions.github.io/cf/v0.3.0/schema.json#", + "title": "Climate and Forecasting Convention Extension", "description": "STAC CF Extension for STAC Items and STAC Collections.", - "type": "object", - "required": [ - "stac_extensions" - ], - "properties": { - "stac_extensions": { - "type": "array", - "contains": { - "const": "https://stac-extensions.github.io/cf/v0.2.0/schema.json" - } - }, - "assets": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/fields" - } - } - }, "oneOf": [ { - "$comment": "Schema for Collections", - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "const": "Collection" + "$comment": "This is the schema for STAC Items. Remove this object if this extension only applies to Collections.", + "allOf": [ + { + "$ref": "#/definitions/stac_extensions" }, - "item_assets": { + { "type": "object", - "additionalProperties": { - "$ref": "#/definitions/fields" + "required": [ + "type", + "properties", + "assets" + ], + "properties": { + "type": { + "const": "Feature" + }, + "properties": { + "allOf": [ + { + "$comment": "Require fields here for Item Properties.", + "required": [ + ] + }, + { + "$ref": "#/definitions/fields" + } + ] + }, + "assets": { + "$comment": "This validates the fields in Item Assets, but does not require them.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/fields" + } + } + } + } + ] + }, + { + "$comment": "This is the schema for STAC Collections.", + "type": "object", + "allOf": [ + { + "required": [ + "type" + ], + "properties": { + "type": { + "const": "Collection" + } } }, - "summaries": { - "oneOf": [ + { + "$ref": "#/definitions/stac_extensions" + } + ], + "anyOf": [ + { + "$comment": "This is the schema for the top-level fields in a Collection. Remove this if this extension does not define top-level fields for Collections.", + "allOf": [ { - "$ref": "#/definitions/fields" + "$comment": "Require fields here for Collections (top-level).", + "required": [ + ] }, { - "$comment": "JSON Schema summary", + "$ref": "#/definitions/fields" + } + ] + }, + { + "$comment": "This validates the fields in Collection Assets, but does not require them.", + "required": [ + "assets" + ], + "properties": { + "assets": { "type": "object", - "properties": { - "cf:parameter": { - "type": "object", - "properties": { - "type": { - "const": "array" - } + "not": { + "additionalProperties": { + "not": { + "allOf": [ + { + "$ref": "#/definitions/require_any_field" + }, + { + "$ref": "#/definitions/fields" + } + ] } } - }, - "patternProperties": { - "^(?!cf:)": { - "$comment": "Above, change `cf` to the prefix of this extension" - } - }, - "additionalProperties": false + } } - ] - } - }, - "allOf": [ + } + }, { - "$ref": "#/definitions/fields" - } - ] - }, - { - "$comment": "Schema for Items", - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "const": "Feature" + "$comment": "This is the schema for the fields in Item Asset Definitions. It doesn't require any fields.", + "required": [ + "item_assets" + ], + "properties": { + "item_assets": { + "type": "object", + "not": { + "additionalProperties": { + "not": { + "allOf": [ + { + "$ref": "#/definitions/require_any_field" + }, + { + "$ref": "#/definitions/fields" + } + ] + } + } + } + } + } }, - "properties": { - "$ref": "#/definitions/fields" - } - }, - "allOf": [ { - "$ref": "#/definitions/cf_in_assets" + "$comment": "This is the schema for the fields in Summaries. By default, only checks the existence of the properties, but not the schema of the summaries.", + "required": [ + "summaries" + ], + "properties": { + "summaries": { + "$ref": "#/definitions/require_any_field" + } + } } ] } ], "definitions": { - "cf_in_assets": { + "stac_extensions": { + "type": "object", "required": [ - "assets" + "stac_extensions" ], "properties": { - "assets": { - "not": { - "additionalProperties": { - "not": { - "allOf": [ - { - "required": [ - "cf:parameter" - ] - }, - { - "$ref": "#/definitions/fields" - } - ] - } - } + "stac_extensions": { + "type": "array", + "contains": { + "const": "https://stac-extensions.github.io/cf/v0.3.0/schema.json" } } } }, - "forbid_fields": { - "patternProperties": { - "^(?!cf:)": {} - }, - "additionalProperties": false + "require_any_field": { + "$comment": "Please list all fields here so that we can force the existence of one of them in other parts of the schemas.", + "anyOf": [ + {"required": [ + "cf:standard_name", + "description", + "unit", + "cf:cell_methods" + ]} + ] }, "fields": { + "$comment": "Add your new fields here. Don't require them here, do that above in the corresponding schema.", "type": "object", "properties": { - "cf:parameter": { + "cf:standard_name": { + "title": "CF standard_name", + "type": "string" + }, + "description": { + "title": "CF long_name field", + "type": "string", + "minLength": 1 + }, + "unit": { + "type": "string" + }, + "cf:cell_methods": { "type": "array", - "minItems": 1, "items": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "unit": { - "type": "string" - } - } + "anyOf": [ + { "type": "string", "minLength": 1 }, + { "type": "null" } + ] } } }, @@ -156,4 +200,4 @@ "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index cf73c2c..6e433e1 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "scripts": { "test": "npm run check-markdown && npm run check-examples", "check-markdown": "remark . -f -r .github/remark.yaml", - "check-examples": "stac-node-validator . --lint --verbose --schemaMap https://stac-extensions.github.io/cf/v0.2.0/schema.json=./json-schema/schema.json", - "format-examples": "stac-node-validator . --format --schemaMap https://stac-extensions.github.io/cf/v0.2.0/schema.json=./json-schema/schema.json" + "check-examples": "stac-node-validator . --lint --verbose --schemaMap https://stac-extensions.github.io/cf/v0.3.0/schema.json=./json-schema/schema.json", + "format-examples": "stac-node-validator . --format --schemaMap https://stac-extensions.github.io/cf/v0.3.0/schema.json=./json-schema/schema.json" }, "dependencies": { - "remark-cli": "^8.0.0", + "remark-cli": "^12.0.1", "remark-lint": "^7.0.0", "remark-lint-no-html": "^2.0.0", "remark-preset-lint-consistent": "^3.0.0",