diff --git a/services/src/api/model/db_types.rs b/services/src/api/model/db_types.rs index 63c99c3cf..1e5418b1e 100644 --- a/services/src/api/model/db_types.rs +++ b/services/src/api/model/db_types.rs @@ -1,10 +1,10 @@ use super::{ datatypes::{ BoundingBox2D, Breakpoint, ClassificationMeasurement, Colorizer, ContinuousMeasurement, - Coordinate2D, DateTimeParseFormat, DefaultColors, FeatureDataType, LinearGradient, - LogarithmicGradient, Measurement, MultiLineString, MultiPoint, MultiPolygon, NoGeometry, - OverUnderColors, Palette, RgbaColor, SpatialReferenceOption, TimeInstance, TimeInterval, - TimeStep, VectorDataType, + Coordinate2D, DataProviderId, DateTimeParseFormat, DefaultColors, FeatureDataType, + LinearGradient, LogarithmicGradient, Measurement, MultiLineString, MultiPoint, + MultiPolygon, NoGeometry, OverUnderColors, Palette, RgbaColor, SpatialReferenceOption, + TimeInstance, TimeInterval, TimeStep, VectorDataType, }, operators::{ CsvHeader, FileNotFoundHandling, FormatSpecifics, GdalConfigOption, @@ -20,12 +20,14 @@ use crate::{ datasets::{ external::{ aruna::ArunaDataProviderDefinition, + edr::{EdrDataProviderDefinition, EdrVectorSpec}, gbif::GbifDataProviderDefinition, gfbio_abcd::GfbioAbcdDataProviderDefinition, gfbio_collections::GfbioCollectionsDataProviderDefinition, netcdfcf::{EbvPortalDataProviderDefinition, NetCdfCfDataProviderDefinition}, pangaea::PangaeaDataProviderDefinition, }, + listing::Provenance, storage::MetaDataDefinition, }, error::Error, @@ -1732,6 +1734,79 @@ impl TryFrom for PangaeaDataProviderDefinit } } +#[derive(Debug, ToSql, FromSql)] +#[postgres(name = "EdrVectorSpec")] +pub struct EdrVectorSpecDbType { + x: String, + y: Option, + time: String, +} + +impl From<&EdrVectorSpec> for EdrVectorSpecDbType { + fn from(other: &EdrVectorSpec) -> Self { + Self { + x: other.x.clone(), + y: other.y.clone(), + time: other.time.clone(), + } + } +} + +impl TryFrom for EdrVectorSpec { + type Error = Error; + + fn try_from(other: EdrVectorSpecDbType) -> Result { + Ok(Self { + x: other.x, + y: other.y, + time: other.time, + }) + } +} + +#[derive(Debug, ToSql, FromSql)] +#[postgres(name = "EdrDataProviderDefinition")] +pub struct EdrDataProviderDefinitionDbType { + name: String, + id: DataProviderId, + base_url: String, + vector_spec: Option, + cache_ttl: CacheTtlSeconds, + /// List of vertical reference systems with a discrete scale + discrete_vrs: Vec, + provenance: Option>, +} + +impl From<&EdrDataProviderDefinition> for EdrDataProviderDefinitionDbType { + fn from(other: &EdrDataProviderDefinition) -> Self { + Self { + name: other.name.clone(), + id: other.id, + base_url: other.base_url.clone().into(), + vector_spec: other.vector_spec.clone(), + cache_ttl: other.cache_ttl, + discrete_vrs: other.discrete_vrs.clone(), + provenance: other.provenance.clone(), + } + } +} + +impl TryFrom for EdrDataProviderDefinition { + type Error = Error; + + fn try_from(other: EdrDataProviderDefinitionDbType) -> Result { + Ok(Self { + name: other.name, + id: other.id, + base_url: other.base_url.as_str().try_into()?, + vector_spec: other.vector_spec, + cache_ttl: other.cache_ttl, + discrete_vrs: other.discrete_vrs, + provenance: other.provenance, + }) + } +} + #[derive(Debug, ToSql, FromSql)] #[postgres(name = "DataProviderDefinition")] pub struct TypedDataProviderDefinitionDbType { @@ -1742,6 +1817,7 @@ pub struct TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: Option, net_cdf_cf_data_provider_definition: Option, pangaea_data_provider_definition: Option, + edr_data_provider_definition: Option, } impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { @@ -1756,6 +1832,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } } TypedDataProviderDefinition::GbifDataProviderDefinition(data_provider_definition) => { @@ -1767,6 +1844,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } } TypedDataProviderDefinition::GfbioAbcdDataProviderDefinition( @@ -1779,6 +1857,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, }, TypedDataProviderDefinition::GfbioCollectionsDataProviderDefinition( data_provider_definition, @@ -1790,6 +1869,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, }, TypedDataProviderDefinition::EbvPortalDataProviderDefinition( data_provider_definition, @@ -1801,6 +1881,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: Some(data_provider_definition.clone()), net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, }, TypedDataProviderDefinition::NetCdfCfDataProviderDefinition( data_provider_definition, @@ -1812,6 +1893,7 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: Some(data_provider_definition.clone()), pangaea_data_provider_definition: None, + edr_data_provider_definition: None, }, TypedDataProviderDefinition::PangaeaDataProviderDefinition( data_provider_definition, @@ -1823,7 +1905,20 @@ impl From<&TypedDataProviderDefinition> for TypedDataProviderDefinitionDbType { ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: Some(data_provider_definition.clone()), + edr_data_provider_definition: None, }, + TypedDataProviderDefinition::EdrDataProviderDefinition(data_provider_definition) => { + Self { + aruna_data_provider_definition: None, + gbif_data_provider_definition: None, + gfbio_abcd_data_provider_definition: None, + gfbio_collections_data_provider_definition: None, + ebv_portal_data_provider_definition: None, + net_cdf_cf_data_provider_definition: None, + pangaea_data_provider_definition: None, + edr_data_provider_definition: Some(data_provider_definition.clone()), + } + } } } } @@ -1842,6 +1937,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok(TypedDataProviderDefinition::ArunaDataProviderDefinition( data_provider_definition, )), @@ -1853,6 +1949,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok(TypedDataProviderDefinition::GbifDataProviderDefinition( data_provider_definition, )), @@ -1864,6 +1961,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok( TypedDataProviderDefinition::GfbioAbcdDataProviderDefinition( data_provider_definition, @@ -1877,6 +1975,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok( TypedDataProviderDefinition::GfbioCollectionsDataProviderDefinition( data_provider_definition, @@ -1890,6 +1989,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: Some(data_provider_definition), net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok( TypedDataProviderDefinition::EbvPortalDataProviderDefinition( data_provider_definition, @@ -1903,6 +2003,7 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: Some(data_provider_definition), pangaea_data_provider_definition: None, + edr_data_provider_definition: None, } => Ok(TypedDataProviderDefinition::NetCdfCfDataProviderDefinition( data_provider_definition, )), @@ -1914,9 +2015,22 @@ impl TryFrom for TypedDataProviderDefinition ebv_portal_data_provider_definition: None, net_cdf_cf_data_provider_definition: None, pangaea_data_provider_definition: Some(data_provider_definition), + edr_data_provider_definition: None, } => Ok(TypedDataProviderDefinition::PangaeaDataProviderDefinition( data_provider_definition, )), + TypedDataProviderDefinitionDbType { + aruna_data_provider_definition: None, + gbif_data_provider_definition: None, + gfbio_abcd_data_provider_definition: None, + gfbio_collections_data_provider_definition: None, + ebv_portal_data_provider_definition: None, + net_cdf_cf_data_provider_definition: None, + pangaea_data_provider_definition: None, + edr_data_provider_definition: Some(data_provider_definition), + } => Ok(TypedDataProviderDefinition::EdrDataProviderDefinition( + data_provider_definition, + )), _ => Err(Error::UnexpectedInvalidDbTypeConversion), } } @@ -1999,6 +2113,8 @@ delegate_from_to_sql!( PangaeaDataProviderDefinition, PangaeaDataProviderDefinitionDbType ); +delegate_from_to_sql!(EdrDataProviderDefinition, EdrDataProviderDefinitionDbType); +delegate_from_to_sql!(EdrVectorSpec, EdrVectorSpecDbType); delegate_from_to_sql!(Symbology, SymbologyDbType); delegate_from_to_sql!(VectorResultDescriptor, VectorResultDescriptorDbType); delegate_from_to_sql!( diff --git a/services/src/contexts/schema.sql b/services/src/contexts/schema.sql index 0c814db99..a6f9bac58 100644 --- a/services/src/contexts/schema.sql +++ b/services/src/contexts/schema.sql @@ -695,6 +695,22 @@ CREATE TYPE "PangaeaDataProviderDefinition" AS ( cache_ttl int ); +CREATE TYPE "EdrVectorSpec" AS ( + x text, + y text, + "time" text +); + +CREATE TYPE "EdrDataProviderDefinition" AS ( + "name" text, + id uuid, + base_url text, + vector_spec "EdrVectorSpec", + cache_ttl int, + discrete_vrs text [], + provenance "Provenance" [] +); + CREATE TYPE "DataProviderDefinition" AS ( -- one of aruna_data_provider_definition "ArunaDataProviderDefinition", @@ -704,7 +720,8 @@ CREATE TYPE "DataProviderDefinition" AS ( "GfbioCollectionsDataProviderDefinition", ebv_portal_data_provider_definition "EbvPortalDataProviderDefinition", net_cdf_cf_data_provider_definition "NetCdfCfDataProviderDefinition", - pangaea_data_provider_definition "PangaeaDataProviderDefinition" + pangaea_data_provider_definition "PangaeaDataProviderDefinition", + edr_data_provider_definition "EdrDataProviderDefinition" ); CREATE TABLE layer_providers ( diff --git a/services/src/datasets/external/edr.rs b/services/src/datasets/external/edr.rs new file mode 100644 index 000000000..77c41ca19 --- /dev/null +++ b/services/src/datasets/external/edr.rs @@ -0,0 +1,1721 @@ +use crate::api::model::datatypes::{DataId, DataProviderId, LayerId}; +use crate::datasets::listing::{Provenance, ProvenanceOutput}; +use crate::error::{Error, Result}; +use crate::layers::external::{DataProvider, DataProviderDefinition}; +use crate::layers::layer::{ + CollectionItem, Layer, LayerCollection, LayerCollectionListOptions, LayerCollectionListing, + LayerListing, ProviderLayerCollectionId, ProviderLayerId, +}; +use crate::layers::listing::{LayerCollectionId, LayerCollectionProvider}; +use crate::util::parsing::deserialize_base_url; +use crate::workflows::workflow::Workflow; +use async_trait::async_trait; +use gdal::Dataset; +use geoengine_datatypes::collections::VectorDataType; +use geoengine_datatypes::hashmap; +use geoengine_datatypes::primitives::{ + AxisAlignedRectangle, BoundingBox2D, CacheTtlSeconds, ContinuousMeasurement, Coordinate2D, + FeatureDataType, Measurement, RasterQueryRectangle, SpatialPartition2D, TimeInstance, + TimeInterval, VectorQueryRectangle, +}; +use geoengine_datatypes::raster::RasterDataType; +use geoengine_datatypes::spatial_reference::SpatialReference; +use geoengine_operators::engine::{ + MetaData, MetaDataProvider, RasterOperator, RasterResultDescriptor, StaticMetaData, + TypedOperator, VectorColumnInfo, VectorOperator, VectorResultDescriptor, +}; +use geoengine_operators::mock::MockDatasetDataSourceLoadingInfo; +use geoengine_operators::source::{ + FileNotFoundHandling, GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoTemporalSlice, + GdalMetaDataList, GdalSource, GdalSourceParameters, OgrSource, OgrSourceColumnSpec, + OgrSourceDataset, OgrSourceDatasetTimeType, OgrSourceDurationSpec, OgrSourceErrorSpec, + OgrSourceParameters, OgrSourceTimeFormat, +}; +use geoengine_operators::util::gdal::gdal_open_dataset; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use snafu::prelude::*; +use std::collections::{BTreeMap, HashMap}; +use std::str::FromStr; +use std::sync::OnceLock; +use url::Url; + +static IS_FILETYPE_RASTER: OnceLock> = OnceLock::new(); + +// TODO: change to `LazyLock' once stable +fn init_is_filetype_raster() -> HashMap<&'static str, bool> { + //name:is_raster + hashmap! { + "GeoTIFF" => true, + "GeoJSON" => false + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct EdrDataProviderDefinition { + pub name: String, + pub id: DataProviderId, + #[serde(deserialize_with = "deserialize_base_url")] + pub base_url: Url, + pub vector_spec: Option, + #[serde(default)] + pub cache_ttl: CacheTtlSeconds, + #[serde(default)] + /// List of vertical reference systems with a discrete scale + pub discrete_vrs: Vec, + pub provenance: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct EdrVectorSpec { + pub x: String, + pub y: Option, + pub time: String, +} + +#[async_trait] +impl DataProviderDefinition for EdrDataProviderDefinition { + async fn initialize(self: Box) -> Result> { + Ok(Box::new(EdrDataProvider { + id: self.id, + base_url: self.base_url, + vector_spec: self.vector_spec, + client: Client::new(), + cache_ttl: self.cache_ttl, + discrete_vrs: self.discrete_vrs, + provenance: self.provenance, + })) + } + + fn type_name(&self) -> &'static str { + "Environmental Data Retrieval" + } + + fn name(&self) -> String { + self.name.clone() + } + + fn id(&self) -> DataProviderId { + self.id + } +} + +#[derive(Debug)] +pub struct EdrDataProvider { + id: DataProviderId, + base_url: Url, + vector_spec: Option, + client: Client, + cache_ttl: CacheTtlSeconds, + /// List of vertical reference systems with a discrete scale + discrete_vrs: Vec, + provenance: Option>, +} + +#[async_trait] +impl DataProvider for EdrDataProvider { + async fn provenance(&self, id: &DataId) -> Result { + Ok(ProvenanceOutput { + data: id.clone(), + provenance: self.provenance.clone(), + }) + } +} + +impl EdrDataProvider { + async fn load_collection_by_name( + &self, + collection_name: &str, + ) -> Result { + self.client + .get( + self.base_url + .join(&format!("collections/{collection_name}?f=json"))?, + ) + .send() + .await? + .json() + .await + .map_err(|_| Error::EdrInvalidMetadataFormat) + } + + async fn load_collection_by_dataid( + &self, + id: &geoengine_datatypes::dataset::DataId, + ) -> Result<(EdrCollectionId, EdrCollectionMetaData), geoengine_operators::error::Error> { + let layer_id = id + .external() + .ok_or(Error::InvalidDataId) + .map_err(|e| geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + })? + .layer_id; + let edr_id: EdrCollectionId = EdrCollectionId::from_str(&layer_id.0).map_err(|e| { + geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + } + })?; + let collection_name = edr_id.get_collection_id().map_err(|e| { + geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + } + })?; + let collection_meta: EdrCollectionMetaData = self + .load_collection_by_name(collection_name) + .await + .map_err(|e| geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + })?; + Ok((edr_id, collection_meta)) + } + + async fn get_root_collection( + &self, + collection_id: &LayerCollectionId, + options: &LayerCollectionListOptions, + ) -> Result { + let collections: EdrCollectionsMetaData = self + .client + .get(self.base_url.join("collections?f=json")?) + .send() + .await? + .json() + .await + .map_err(|_| Error::EdrInvalidMetadataFormat)?; + + let items = collections + .collections + .into_iter() + .filter(|collection| { + collection.data_queries.cube.is_some() && collection.extent.spatial.is_some() + }) + .skip(options.offset as usize) + .take(options.limit as usize) + .map(|collection| { + if collection.is_raster_file()? || collection.extent.vertical.is_some() { + Ok(CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: EdrCollectionId::Collection { + collection: collection.id.clone(), + } + .try_into()?, + }, + name: collection.title.unwrap_or(collection.id), + description: collection.description.unwrap_or(String::new()), + properties: vec![], + })) + } else { + Ok(CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: self.id, + layer_id: EdrCollectionId::Collection { + collection: collection.id.clone(), + } + .try_into()?, + }, + name: collection.title.unwrap_or(collection.id), + description: collection.description.unwrap_or(String::new()), + properties: vec![], + })) + } + }) + .collect::>>()?; + + Ok(LayerCollection { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: collection_id.clone(), + }, + name: "EDR".to_owned(), + description: "Environmental Data Retrieval".to_owned(), + items, + entry_label: None, + properties: vec![], + }) + } + + fn get_raster_parameter_collection( + &self, + collection_id: &LayerCollectionId, + collection_meta: EdrCollectionMetaData, + options: &LayerCollectionListOptions, + ) -> Result { + let items = collection_meta + .parameter_names + .into_keys() + .skip(options.offset as usize) + .take(options.limit as usize) + .map(|parameter_name| { + if collection_meta.extent.vertical.is_some() { + Ok(CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: EdrCollectionId::ParameterOrHeight { + collection: collection_meta.id.clone(), + parameter: parameter_name.clone(), + } + .try_into()?, + }, + name: parameter_name, + description: String::new(), + properties: vec![], + })) + } else { + Ok(CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: self.id, + layer_id: EdrCollectionId::ParameterOrHeight { + collection: collection_meta.id.clone(), + parameter: parameter_name.clone(), + } + .try_into()?, + }, + name: parameter_name, + description: String::new(), + properties: vec![], + })) + } + }) + .collect::>>()?; + + Ok(LayerCollection { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: collection_id.clone(), + }, + name: collection_meta.id.clone(), + description: format!("Parameters of {}", collection_meta.id), + items, + entry_label: None, + properties: vec![], + }) + } + + fn get_vector_height_collection( + &self, + collection_id: &LayerCollectionId, + collection_meta: EdrCollectionMetaData, + options: &LayerCollectionListOptions, + ) -> Result { + let items = collection_meta + .extent + .vertical + .expect("checked before") + .values + .into_iter() + .skip(options.offset as usize) + .take(options.limit as usize) + .map(|height| { + Ok(CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: self.id, + layer_id: EdrCollectionId::ParameterOrHeight { + collection: collection_meta.id.clone(), + parameter: height.clone(), + } + .try_into()?, + }, + name: height, + description: String::new(), + properties: vec![], + })) + }) + .collect::>>()?; + + Ok(LayerCollection { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: collection_id.clone(), + }, + name: collection_meta.id.clone(), + description: format!("Height selection of {}", collection_meta.id), + items, + entry_label: None, + properties: vec![], + }) + } + + fn get_raster_height_collection( + &self, + collection_id: &LayerCollectionId, + collection_meta: EdrCollectionMetaData, + parameter: &str, + options: &LayerCollectionListOptions, + ) -> Result { + let items = collection_meta + .extent + .vertical + .expect("checked before") + .values + .into_iter() + .skip(options.offset as usize) + .take(options.limit as usize) + .map(|height| { + Ok(CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: self.id, + layer_id: EdrCollectionId::ParameterAndHeight { + collection: collection_meta.id.clone(), + parameter: parameter.to_string(), + height: height.clone(), + } + .try_into()?, + }, + name: height, + description: String::new(), + properties: vec![], + })) + }) + .collect::>>()?; + + Ok(LayerCollection { + id: ProviderLayerCollectionId { + provider_id: self.id, + collection_id: collection_id.clone(), + }, + name: collection_meta.id.clone(), + description: format!("Height selection of {}", collection_meta.id), + items, + entry_label: None, + properties: vec![], + }) + } +} + +#[derive(Deserialize)] +struct EdrCollectionsMetaData { + collections: Vec, +} + +#[derive(Deserialize)] +struct EdrCollectionMetaData { + id: String, + title: Option, + description: Option, + extent: EdrExtents, + //for paging keys need to be returned in same order every time + parameter_names: BTreeMap, + output_formats: Vec, + data_queries: EdrDataQueries, +} + +#[derive(Deserialize)] +struct EdrDataQueries { + cube: Option, +} + +impl EdrCollectionMetaData { + fn get_time_interval(&self) -> Result { + let temporal_extent = self.extent.temporal.as_ref().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::MissingTemporalExtent), + } + })?; + + Ok(TimeInterval::new_unchecked( + TimeInstance::from_str(&temporal_extent.interval[0][0]).unwrap(), + TimeInstance::from_str(&temporal_extent.interval[0][1]).unwrap(), + )) + } + + fn get_bounding_box(&self) -> Result { + let spatial_extent = self.extent.spatial.as_ref().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::MissingSpatialExtent), + } + })?; + + Ok(BoundingBox2D::new_unchecked( + Coordinate2D::new(spatial_extent.bbox[0][0], spatial_extent.bbox[0][1]), + Coordinate2D::new(spatial_extent.bbox[0][2], spatial_extent.bbox[0][3]), + )) + } + + fn select_output_format(&self) -> Result { + for format in &self.output_formats { + if IS_FILETYPE_RASTER + .get_or_init(init_is_filetype_raster) + .contains_key(format.as_str()) + { + return Ok(format.to_string()); + } + } + Err(geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::NoSupportedOutputFormat), + }) + } + + fn is_raster_file(&self) -> Result { + Ok(*IS_FILETYPE_RASTER + .get_or_init(init_is_filetype_raster) + .get(&self.select_output_format()?.as_str()) + .expect("can only return values in map")) + } + + fn get_vector_download_url( + &self, + base_url: &Url, + height: &str, + discrete_vrs: &[String], + ) -> Result<(String, String), geoengine_operators::error::Error> { + let spatial_extent = self.extent.spatial.as_ref().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::MissingSpatialExtent), + } + })?; + let temporal_extent = self.extent.temporal.as_ref().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::MissingTemporalExtent), + } + })?; + let z = if height == "default" { + String::new() + } else if self.extent.has_discrete_vertical_axis(discrete_vrs) { + format!("&z={height}") + } else { + format!("&z={height}%2F{height}") + }; + let layer_name = format!( + "cube?bbox={},{},{},{}{}&datetime={}%2F{}&f={}", + spatial_extent.bbox[0][0], + spatial_extent.bbox[0][1], + spatial_extent.bbox[0][2], + spatial_extent.bbox[0][3], + z, + temporal_extent.interval[0][0], + temporal_extent.interval[0][1], + self.select_output_format()? + ); + let download_url = format!( + "/vsicurl_streaming/{}collections/{}/{}", + base_url, self.id, layer_name, + ); + Ok((download_url, layer_name)) + } + + fn get_raster_download_url( + &self, + base_url: &Url, + parameter_name: &str, + height: &str, + time: &str, + discrete_vrs: &[String], + ) -> Result { + let spatial_extent = self.extent.spatial.as_ref().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::MissingSpatialExtent), + } + })?; + let z = if height == "default" { + String::new() + } else if self.extent.has_discrete_vertical_axis(discrete_vrs) { + format!("&z={height}") + } else { + format!("&z={height}%2F{height}") + }; + Ok(format!( + "/vsicurl_streaming/{}collections/{}/cube?bbox={},{},{},{}{}&datetime={}%2F{}&f={}¶meter-name={}", + base_url, + self.id, + spatial_extent.bbox[0][0], + spatial_extent.bbox[0][1], + spatial_extent.bbox[0][2], + spatial_extent.bbox[0][3], + z, + time, + time, + self.select_output_format()?, + parameter_name + )) + } + + fn get_vector_result_descriptor( + &self, + ) -> Result { + let column_map: HashMap = self + .parameter_names + .iter() + .map(|(parameter_name, parameter_metadata)| { + let data_type = if let Some(data_type) = parameter_metadata.data_type.as_ref() { + data_type.as_str().to_uppercase() + } else { + "FLOAT".to_string() + }; + match data_type.as_str() { + "STRING" => ( + parameter_name.to_string(), + VectorColumnInfo { + data_type: FeatureDataType::Text, + measurement: Measurement::Unitless, + }, + ), + "INTEGER" => ( + parameter_name.to_string(), + VectorColumnInfo { + data_type: FeatureDataType::Int, + measurement: Measurement::Continuous(ContinuousMeasurement { + measurement: parameter_metadata.observed_property.label.clone(), + unit: parameter_metadata.unit.as_ref().map(|x| x.symbol.clone()), + }), + }, + ), + _ => ( + parameter_name.to_string(), + VectorColumnInfo { + data_type: FeatureDataType::Float, + measurement: Measurement::Continuous(ContinuousMeasurement { + measurement: parameter_metadata.observed_property.label.clone(), + unit: parameter_metadata.unit.as_ref().map(|x| x.symbol.clone()), + }), + }, + ), + } + }) + .collect(); + + Ok(VectorResultDescriptor { + spatial_reference: SpatialReference::epsg_4326().into(), + data_type: VectorDataType::MultiPoint, + columns: column_map, + time: Some(self.get_time_interval()?), + bbox: Some(self.get_bounding_box()?), + }) + } + + fn get_column_spec(&self, vector_spec: EdrVectorSpec) -> OgrSourceColumnSpec { + let mut int = vec![]; + let mut float = vec![]; + let mut text = vec![]; + let bool = vec![]; + let datetime = vec![]; + + for (parameter_name, parameter_metadata) in &self.parameter_names { + let data_type = if let Some(data_type) = parameter_metadata.data_type.as_ref() { + data_type.as_str().to_uppercase() + } else { + "FLOAT".to_string() + }; + match data_type.as_str() { + "STRING" => { + text.push(parameter_name.clone()); + } + "INTEGER" => { + int.push(parameter_name.clone()); + } + _ => { + float.push(parameter_name.clone()); + } + } + } + OgrSourceColumnSpec { + format_specifics: None, + x: vector_spec.x, + y: vector_spec.y, + int, + float, + text, + bool, + datetime, + rename: None, + } + } + + fn get_ogr_source_ds( + &self, + download_url: String, + layer_name: String, + vector_spec: EdrVectorSpec, + cache_ttl: CacheTtlSeconds, + ) -> OgrSourceDataset { + OgrSourceDataset { + file_name: download_url.into(), + layer_name, + data_type: Some(VectorDataType::MultiPoint), + time: OgrSourceDatasetTimeType::Start { + start_field: vector_spec.time.clone(), + start_format: OgrSourceTimeFormat::Auto, + duration: OgrSourceDurationSpec::Zero, + }, + default_geometry: None, + columns: Some(self.get_column_spec(vector_spec)), + force_ogr_time_filter: false, + force_ogr_spatial_filter: false, + on_error: OgrSourceErrorSpec::Abort, + sql_query: None, + attribute_query: None, + cache_ttl, + } + } + + fn get_ogr_metadata( + &self, + base_url: &Url, + height: &str, + vector_spec: EdrVectorSpec, + cache_ttl: CacheTtlSeconds, + discrete_vrs: &[String], + ) -> Result> + { + let (download_url, layer_name) = + self.get_vector_download_url(base_url, height, discrete_vrs)?; + let omd = self.get_ogr_source_ds(download_url, layer_name, vector_spec, cache_ttl); + + Ok(StaticMetaData { + loading_info: omd, + result_descriptor: self.get_vector_result_descriptor()?, + phantom: Default::default(), + }) + } + + fn get_raster_result_descriptor( + &self, + ) -> Result { + let bbox = self.get_bounding_box()?; + let bbox = SpatialPartition2D::new_unchecked(bbox.upper_left(), bbox.lower_right()); + + Ok(RasterResultDescriptor { + data_type: RasterDataType::U8, + spatial_reference: SpatialReference::epsg_4326().into(), + measurement: Measurement::Unitless, + time: Some(self.get_time_interval()?), + bbox: Some(bbox), + resolution: None, + }) + } + + fn get_gdal_loading_info_temporal_slice( + &self, + provider: &EdrDataProvider, + parameter: &str, + height: &str, + data_time: TimeInterval, + current_time: &str, + dataset: &Dataset, + ) -> Result { + let rasterband = &dataset.rasterband(1)?; + + Ok(GdalLoadingInfoTemporalSlice { + time: data_time, + params: Some(GdalDatasetParameters { + file_path: self + .get_raster_download_url( + &provider.base_url, + parameter, + height, + current_time, + &provider.discrete_vrs, + )? + .into(), + rasterband_channel: 1, + geo_transform: dataset + .geo_transform() + .context(crate::error::Gdal) + .map_err(|e| geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + })? + .into(), + width: rasterband.x_size(), + height: rasterband.y_size(), + file_not_found_handling: FileNotFoundHandling::NoData, + no_data_value: None, + properties_mapping: None, + gdal_open_options: None, + gdal_config_options: Some(vec![( + "GTIFF_HONOUR_NEGATIVE_SCALEY".to_string(), + "YES".to_string(), + )]), + allow_alphaband_as_mask: false, + retry: None, + }), + cache_ttl: provider.cache_ttl, + }) + } +} + +#[derive(Deserialize)] +struct EdrExtents { + spatial: Option, + vertical: Option, + temporal: Option, +} + +impl EdrExtents { + fn has_discrete_vertical_axis(&self, discrete_vrs: &[String]) -> bool { + self.vertical + .as_ref() + .map_or(false, |val| discrete_vrs.contains(&val.vrs)) + } +} + +#[derive(Deserialize)] +struct EdrSpatialExtent { + bbox: Vec>, +} + +#[derive(Deserialize)] +struct EdrVerticalExtent { + values: Vec, + vrs: String, +} + +#[derive(Deserialize, Clone)] +struct EdrTemporalExtent { + interval: Vec>, + values: Vec, +} + +#[derive(Deserialize)] +struct EdrParameter { + #[serde(rename = "data-type")] + data_type: Option, + unit: Option, + #[serde(rename = "observedProperty")] + observed_property: ObservedProperty, +} + +#[derive(Deserialize)] +struct EdrUnit { + symbol: String, +} + +#[derive(Deserialize)] +struct ObservedProperty { + label: String, +} + +enum EdrCollectionId { + Collections, + Collection { + collection: String, + }, + ParameterOrHeight { + collection: String, + parameter: String, + }, + ParameterAndHeight { + collection: String, + parameter: String, + height: String, + }, +} + +impl EdrCollectionId { + fn get_collection_id(&self) -> Result<&String> { + match self { + EdrCollectionId::Collections => Err(Error::InvalidLayerId), + EdrCollectionId::Collection { collection } + | EdrCollectionId::ParameterOrHeight { collection, .. } + | EdrCollectionId::ParameterAndHeight { collection, .. } => Ok(collection), + } + } +} + +impl FromStr for EdrCollectionId { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + // Collection ids use ampersands as separators because some collection names + // contain slashes. + let split = s.split('!').collect::>(); + + Ok(match *split.as_slice() { + ["collections"] => EdrCollectionId::Collections, + ["collections", collection] => EdrCollectionId::Collection { + collection: collection.to_string(), + }, + ["collections", collection, parameter] => EdrCollectionId::ParameterOrHeight { + collection: collection.to_string(), + parameter: parameter.to_string(), + }, + ["collections", collection, parameter, height] => EdrCollectionId::ParameterAndHeight { + collection: collection.to_string(), + parameter: parameter.to_string(), + height: height.to_string(), + }, + _ => return Err(Error::InvalidLayerCollectionId), + }) + } +} + +impl TryFrom for LayerCollectionId { + type Error = Error; + + fn try_from(value: EdrCollectionId) -> std::result::Result { + let s = match value { + EdrCollectionId::Collections => "collections".to_string(), + EdrCollectionId::Collection { collection } => format!("collections!{collection}"), + EdrCollectionId::ParameterOrHeight { + collection, + parameter, + } => format!("collections!{collection}!{parameter}"), + EdrCollectionId::ParameterAndHeight { .. } => { + return Err(Error::InvalidLayerCollectionId) + } + }; + + Ok(LayerCollectionId(s)) + } +} + +impl TryFrom for LayerId { + type Error = Error; + + fn try_from(value: EdrCollectionId) -> std::result::Result { + let s = match value { + EdrCollectionId::Collections => return Err(Error::InvalidLayerId), + EdrCollectionId::Collection { collection } => format!("collections!{collection}"), + EdrCollectionId::ParameterOrHeight { + collection, + parameter, + } => format!("collections!{collection}!{parameter}"), + EdrCollectionId::ParameterAndHeight { + collection, + parameter, + height, + } => format!("collections!{collection}!{parameter}!{height}"), + }; + + Ok(LayerId(s)) + } +} + +#[async_trait] +impl LayerCollectionProvider for EdrDataProvider { + async fn load_layer_collection( + &self, + collection_id: &LayerCollectionId, + options: LayerCollectionListOptions, + ) -> Result { + let edr_id: EdrCollectionId = EdrCollectionId::from_str(&collection_id.0) + .map_err(|_e| Error::InvalidLayerCollectionId)?; + + match edr_id { + EdrCollectionId::Collections => self.get_root_collection(collection_id, &options).await, + EdrCollectionId::Collection { collection } => { + let collection_meta = self.load_collection_by_name(&collection).await?; + + if collection_meta.is_raster_file()? { + // The collection is of type raster. A layer can only contain one parameter + // of a raster dataset at a time, so let the user choose one. + self.get_raster_parameter_collection(collection_id, collection_meta, &options) + } else if collection_meta.extent.vertical.is_some() { + // The collection is of type vector and data is provided for multiple heights. + // The user needs to be able to select the height he wants to see. It is not + // needed to select a parameter, because for vector datasets all parameters + // can be loaded simultaneously. + self.get_vector_height_collection(collection_id, collection_meta, &options) + } else { + // The collection is of type vector and there is only data for a single height. + // No height or parameter needs to be selected by the user. Therefore the name + // of the collection already identifies a layer sufficiently. + Err(Error::InvalidLayerCollectionId) + } + } + EdrCollectionId::ParameterOrHeight { + collection, + parameter, + } => { + let collection_meta = self.load_collection_by_name(&collection).await?; + + if !collection_meta.is_raster_file()? || collection_meta.extent.vertical.is_none() { + // When the collection is of type raster, the parameter-name is set by the + // parameter field. The height must not be selected when the collection has + // no height information. + // When the collection is of type vector, the height is already set by the + // parameter field. For vectors no parameter-name must be selected. + return Err(Error::InvalidLayerCollectionId); + } + // If the program gets here, it is a raster collection and it contains multiple + // heights. The parameter-name was already chosen by the paramter field, but a + // height must still be selected. + self.get_raster_height_collection( + collection_id, + collection_meta, + ¶meter, + &options, + ) + } + EdrCollectionId::ParameterAndHeight { .. } => Err(Error::InvalidLayerCollectionId), + } + } + + async fn get_root_layer_collection_id(&self) -> Result { + EdrCollectionId::Collections.try_into() + } + + async fn load_layer(&self, id: &LayerId) -> Result { + let edr_id: EdrCollectionId = EdrCollectionId::from_str(&id.0)?; + let collection_id = edr_id.get_collection_id()?; + + let collection = self.load_collection_by_name(collection_id).await?; + + let operator = if collection.is_raster_file()? { + TypedOperator::Raster( + GdalSource { + params: GdalSourceParameters { + data: geoengine_datatypes::dataset::NamedData::with_system_provider( + self.id.to_string(), + id.to_string(), + ), + }, + } + .boxed(), + ) + } else { + TypedOperator::Vector( + OgrSource { + params: OgrSourceParameters { + data: geoengine_datatypes::dataset::NamedData::with_system_provider( + self.id.to_string(), + id.to_string(), + ), + attribute_projection: None, + attribute_filters: None, + }, + } + .boxed(), + ) + }; + + Ok(Layer { + id: ProviderLayerId { + provider_id: self.id, + layer_id: id.clone(), + }, + name: collection.title.unwrap_or(collection.id), + description: String::new(), + workflow: Workflow { operator }, + symbology: None, // TODO + properties: vec![], + metadata: HashMap::new(), + }) + } +} + +#[async_trait] +impl + MetaDataProvider + for EdrDataProvider +{ + async fn meta_data( + &self, + _id: &geoengine_datatypes::dataset::DataId, + ) -> Result< + Box< + dyn MetaData< + MockDatasetDataSourceLoadingInfo, + VectorResultDescriptor, + VectorQueryRectangle, + >, + >, + geoengine_operators::error::Error, + > { + Err(geoengine_operators::error::Error::NotYetImplemented) + } +} + +#[async_trait] +impl MetaDataProvider + for EdrDataProvider +{ + async fn meta_data( + &self, + id: &geoengine_datatypes::dataset::DataId, + ) -> Result< + Box>, + geoengine_operators::error::Error, + > { + let vector_spec = self.vector_spec.clone().ok_or_else(|| { + geoengine_operators::error::Error::DatasetMetaData { + source: Box::new(EdrProviderError::NoVectorSpecConfigured), + } + })?; + let (edr_id, collection) = self.load_collection_by_dataid(id).await?; + + let height = match edr_id { + EdrCollectionId::Collection { .. } => "default".to_string(), + EdrCollectionId::ParameterOrHeight { parameter, .. } => parameter, + _ => unreachable!(), + }; + + let smd = collection + .get_ogr_metadata( + &self.base_url, + &height, + vector_spec, + self.cache_ttl, + &self.discrete_vrs, + ) + .map_err(|e| geoengine_operators::error::Error::LoadingInfo { + source: Box::new(e), + })?; + + Ok(Box::new(smd)) + } +} + +#[async_trait] +impl MetaDataProvider + for EdrDataProvider +{ + async fn meta_data( + &self, + id: &geoengine_datatypes::dataset::DataId, + ) -> Result< + Box>, + geoengine_operators::error::Error, + > { + let (edr_id, collection) = self.load_collection_by_dataid(id).await?; + + let (parameter, height) = match edr_id { + EdrCollectionId::ParameterOrHeight { parameter, .. } => { + (parameter, "default".to_string()) + } + EdrCollectionId::ParameterAndHeight { + parameter, height, .. + } => (parameter, height), + _ => unreachable!(), + }; + + let mut params: Vec = Vec::new(); + + if let Some(temporal_extent) = collection.extent.temporal.clone() { + let mut temporal_values_iter = temporal_extent.values.iter(); + let mut previous_start = temporal_values_iter.next().unwrap(); + let dataset = gdal_open_dataset( + collection + .get_raster_download_url( + &self.base_url, + ¶meter, + &height, + previous_start, + &self.discrete_vrs, + )? + .as_ref(), + )?; + + for current_time in temporal_values_iter { + params.push(collection.get_gdal_loading_info_temporal_slice( + self, + ¶meter, + &height, + TimeInterval::new_unchecked( + TimeInstance::from_str(previous_start).unwrap(), + TimeInstance::from_str(current_time).unwrap(), + ), + previous_start, + &dataset, + )?); + previous_start = current_time; + } + params.push(collection.get_gdal_loading_info_temporal_slice( + self, + ¶meter, + &height, + TimeInterval::new_unchecked( + TimeInstance::from_str(previous_start).unwrap(), + TimeInstance::from_str(&temporal_extent.interval[0][1]).unwrap(), + ), + previous_start, + &dataset, + )?); + } else { + let dummy_time = "2023-06-06T00:00:00Z"; + let dataset = gdal_open_dataset( + collection + .get_raster_download_url( + &self.base_url, + ¶meter, + &height, + dummy_time, + &self.discrete_vrs, + )? + .as_ref(), + )?; + params.push(collection.get_gdal_loading_info_temporal_slice( + self, + ¶meter, + &height, + TimeInterval::default(), + dummy_time, + &dataset, + )?); + } + + Ok(Box::new(GdalMetaDataList { + result_descriptor: collection.get_raster_result_descriptor()?, + params, + })) + } +} + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub(crate)))] +#[snafu(context(suffix(false)))] // disables default `Snafu` suffix +pub enum EdrProviderError { + MissingSpatialExtent, + MissingTemporalExtent, + NoSupportedOutputFormat, + NoVectorSpecConfigured, +} + +#[cfg(test)] +mod tests { + use crate::api::model::datatypes::ExternalDataId; + use geoengine_datatypes::{primitives::SpatialResolution, util::gdal::hide_gdal_errors}; + use geoengine_operators::{engine::ResultDescriptor, source::GdalDatasetGeoTransform}; + use httptest::{matchers::*, responders::status_code, Expectation, Server}; + use std::path::PathBuf; + + use super::*; + + const DEMO_PROVIDER_ID: DataProviderId = + DataProviderId::from_u128(0xdc2d_dc34_b0d9_4ee0_bf3e_414f_01a8_05ad); + + fn test_data_path(file_name: &str) -> PathBuf { + crate::test_data!(String::from("edr/") + file_name).into() + } + + async fn create_provider(server: &Server) -> Box { + Box::new(EdrDataProviderDefinition { + name: "EDR".to_string(), + id: DEMO_PROVIDER_ID, + base_url: Url::parse(server.url_str("").strip_suffix('/').unwrap()).unwrap(), + vector_spec: Some(EdrVectorSpec { + x: "geometry".to_string(), + y: None, + time: "time".to_string(), + }), + cache_ttl: Default::default(), + discrete_vrs: vec!["between-depth".to_string()], + provenance: None, + }) + .initialize() + .await + .unwrap() + } + + async fn setup_url( + server: &mut Server, + url: &str, + content_type: &str, + file_name: &str, + times: usize, + ) { + let path = test_data_path(file_name); + let body = tokio::fs::read(path).await.unwrap(); + + let responder = status_code(200) + .append_header("content-type", content_type.to_owned()) + .append_header("content-length", body.len()) + .body(body); + + server.expect( + Expectation::matching(request::method_path("GET", url.to_string())) + .times(times) + .respond_with(responder), + ); + } + + async fn load_layer_collection(collection: &LayerCollectionId) -> LayerCollection { + let mut server = Server::run(); + + if collection.0 == "collections" { + setup_url( + &mut server, + "/collections", + "application/json", + "edr_collections.json", + 1, + ) + .await; + } else { + let collection_name = collection.0.split('!').nth(1).unwrap(); + setup_url( + &mut server, + &format!("/collections/{collection_name}"), + "application/json", + &format!("edr_{collection_name}.json"), + 1, + ) + .await; + } + + let provider = create_provider(&server).await; + + let datasets = provider + .load_layer_collection( + collection, + LayerCollectionListOptions { + offset: 0, + limit: 20, + }, + ) + .await + .unwrap(); + server.verify_and_clear(); + + datasets + } + + #[tokio::test] + async fn it_loads_root_collection() { + let root_collection_id = LayerCollectionId("collections".to_string()); + let datasets = load_layer_collection(&root_collection_id).await; + + assert_eq!( + datasets, + LayerCollection { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: root_collection_id + }, + name: "EDR".to_owned(), + description: "Environmental Data Retrieval".to_owned(), + items: vec![ + // Note: The dataset GFS_single-level_50 gets filtered out because there is no extent set. + // This means that it contains no data. + CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: LayerCollectionId( + "collections!GFS_single-level".to_string() + ) + }, + name: "GFS - Single Level".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: LayerCollectionId( + "collections!GFS_isobaric".to_string() + ) + }, + name: "GFS - Isobaric level".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: LayerCollectionId( + "collections!GFS_between-depth".to_string() + ) + }, + name: "GFS - Layer between two depths below land surface".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId("collections!PointsInGermany".to_string()) + }, + name: "PointsInGermany".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: LayerCollectionId( + "collections!PointsInFrance".to_string() + ) + }, + name: "PointsInFrance".to_string(), + description: String::new(), + properties: vec![], + }), + ], + entry_label: None, + properties: vec![] + } + ); + } + + #[tokio::test] + async fn it_loads_raster_parameter_collection() { + let collection_id = LayerCollectionId("collections!GFS_isobaric".to_string()); + let datasets = load_layer_collection(&collection_id).await; + + assert_eq!( + datasets, + LayerCollection { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id + }, + name: "GFS_isobaric".to_owned(), + description: "Parameters of GFS_isobaric".to_owned(), + items: vec![CollectionItem::Collection(LayerCollectionListing { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id: LayerCollectionId( + "collections!GFS_isobaric!temperature".to_string() + ) + }, + name: "temperature".to_string(), + description: String::new(), + properties: vec![], + })], + entry_label: None, + properties: vec![] + } + ); + } + + #[tokio::test] + async fn it_loads_vector_height_collection() { + let collection_id = LayerCollectionId("collections!PointsInFrance".to_string()); + let datasets = load_layer_collection(&collection_id).await; + + assert_eq!( + datasets, + LayerCollection { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id + }, + name: "PointsInFrance".to_owned(), + description: "Height selection of PointsInFrance".to_owned(), + items: vec![ + CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId("collections!PointsInFrance!0\\10cm".to_string()) + }, + name: "0\\10cm".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId("collections!PointsInFrance!10\\40cm".to_string()) + }, + name: "10\\40cm".to_string(), + description: String::new(), + properties: vec![], + }) + ], + entry_label: None, + properties: vec![] + } + ); + } + + #[tokio::test] + #[should_panic(expected = "InvalidLayerCollectionId")] + async fn vector_without_height_collection_invalid() { + let collection_id = LayerCollectionId("collections!PointsInGermany".to_string()); + load_layer_collection(&collection_id).await; + } + + #[tokio::test] + async fn it_loads_raster_height_collection() { + let collection_id = LayerCollectionId("collections!GFS_isobaric!temperature".to_string()); + let datasets = load_layer_collection(&collection_id).await; + + assert_eq!( + datasets, + LayerCollection { + id: ProviderLayerCollectionId { + provider_id: DEMO_PROVIDER_ID, + collection_id + }, + name: "GFS_isobaric".to_owned(), + description: "Height selection of GFS_isobaric".to_owned(), + items: vec![ + CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId( + "collections!GFS_isobaric!temperature!0.01".to_string() + ) + }, + name: "0.01".to_string(), + description: String::new(), + properties: vec![], + }), + CollectionItem::Layer(LayerListing { + id: ProviderLayerId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId( + "collections!GFS_isobaric!temperature!1000".to_string() + ) + }, + name: "1000".to_string(), + description: String::new(), + properties: vec![], + }) + ], + entry_label: None, + properties: vec![] + } + ); + } + + #[tokio::test] + #[should_panic(expected = "InvalidLayerCollectionId")] + async fn vector_with_parameter_collection_invalid() { + let collection_id = LayerCollectionId("collections!PointsInGermany!ID".to_string()); + load_layer_collection(&collection_id).await; + } + + #[tokio::test] + #[should_panic(expected = "InvalidLayerCollectionId")] + async fn raster_with_parameter_without_height_collection_invalid() { + let collection_id = + LayerCollectionId("collections!GFS_single-level!temperature_max-wind".to_string()); + load_layer_collection(&collection_id).await; + } + + #[tokio::test] + #[should_panic(expected = "InvalidLayerCollectionId")] + async fn collection_with_parameter_and_height_invalid() { + let collection_id = + LayerCollectionId("collections!GFS_isobaric!temperature!1000".to_string()); + load_layer_collection(&collection_id).await; + } + + async fn load_metadata( + server: &mut Server, + collection: &'static str, + ) -> Box> + where + R: ResultDescriptor, + dyn DataProvider: MetaDataProvider, + { + let collection_name = collection.split('!').next().unwrap(); + setup_url( + server, + &format!("/collections/{collection_name}"), + "application/json", + &format!("edr_{collection_name}.json"), + 1, + ) + .await; + + let provider = create_provider(server).await; + + let meta: Box> = provider + .meta_data( + &DataId::External(ExternalDataId { + provider_id: DEMO_PROVIDER_ID, + layer_id: LayerId(format!("collections!{collection}")), + }) + .into(), + ) + .await + .unwrap(); + server.verify_and_clear(); + meta + } + + #[tokio::test] + async fn generate_ogr_metadata() { + let mut server = Server::run(); + let meta = load_metadata::( + &mut server, + "PointsInGermany", + ) + .await; + let loading_info = meta + .loading_info(VectorQueryRectangle { + spatial_bounds: BoundingBox2D::new_unchecked( + (-180., -90.).into(), + (180., 90.).into(), + ), + time_interval: TimeInterval::default(), + spatial_resolution: SpatialResolution::zero_point_one(), + }) + .await + .unwrap(); + assert_eq!( + loading_info, + OgrSourceDataset { + file_name: format!("/vsicurl_streaming/{}", server.url_str("/collections/PointsInGermany/cube?bbox=-180,-90,180,90&datetime=2023-01-01T12:42:29Z%2F2023-02-01T12:42:29Z&f=GeoJSON")).into(), + layer_name: "cube?bbox=-180,-90,180,90&datetime=2023-01-01T12:42:29Z%2F2023-02-01T12:42:29Z&f=GeoJSON".to_string(), + data_type: Some(VectorDataType::MultiPoint), + time: OgrSourceDatasetTimeType::Start { + start_field: "time".to_string(), + start_format: OgrSourceTimeFormat::Auto, + duration: OgrSourceDurationSpec::Zero, + }, + default_geometry: None, + columns: Some(OgrSourceColumnSpec { + format_specifics: None, + x: "geometry".to_string(), + y: None, + int: vec!["ID".to_string()], + float: vec![], + text: vec![], + bool: vec![], + datetime: vec![], + rename: None, + }), + force_ogr_time_filter: false, + force_ogr_spatial_filter: false, + on_error: OgrSourceErrorSpec::Abort, + sql_query: None, + attribute_query: None, + cache_ttl: Default::default(), + } + ); + + let result_descriptor = meta.result_descriptor().await.unwrap(); + assert_eq!( + result_descriptor, + VectorResultDescriptor { + spatial_reference: SpatialReference::epsg_4326().into(), + data_type: VectorDataType::MultiPoint, + columns: hashmap! { + "ID".to_string() => VectorColumnInfo { + data_type: FeatureDataType::Int, + measurement: Measurement::Continuous(ContinuousMeasurement { + measurement: "ID".to_string(), + unit: None, + }), + } + }, + time: Some(TimeInterval::new_unchecked( + 1_672_576_949_000, + 1_675_255_349_000, + )), + bbox: Some(BoundingBox2D::new_unchecked( + (-180., -90.).into(), + (180., 90.).into() + )), + } + ); + } + + #[tokio::test] + #[allow(clippy::too_many_lines)] + async fn generate_gdal_metadata() { + hide_gdal_errors(); //hide GTIFF_HONOUR_NEGATIVE_SCALEY warning + + let mut server = Server::run(); + setup_url( + &mut server, + "/collections/GFS_isobaric/cube", + "image/tiff", + "edr_raster.tif", + 4, + ) + .await; + server.expect( + Expectation::matching(all_of![ + request::method_path("HEAD", "/collections/GFS_isobaric/cube"), + request::query(url_decoded(contains(( + "parameter-name", + "temperature.aux.xml" + )))) + ]) + .times(1) + .respond_with(status_code(404)), + ); + let meta = load_metadata::( + &mut server, + "GFS_isobaric!temperature!1000", + ) + .await; + + let loading_info_parts = meta + .loading_info(RasterQueryRectangle { + spatial_bounds: SpatialPartition2D::new_unchecked( + (0., 90.).into(), + (360., -90.).into(), + ), + time_interval: TimeInterval::new_unchecked(1_692_144_000_000, 1_692_500_400_000), + spatial_resolution: SpatialResolution::new_unchecked(1., 1.), + }) + .await + .unwrap() + .info + .map(Result::unwrap) + .collect::>(); + assert_eq!( + loading_info_parts, + vec![ + GdalLoadingInfoTemporalSlice { + time: TimeInterval::new_unchecked( + 1_692_144_000_000, 1_692_154_800_000 + ), + params: Some(GdalDatasetParameters { + file_path: format!("/vsicurl_streaming/{}", server.url_str("/collections/GFS_isobaric/cube?bbox=0,-90,359.50000000000006,90&z=1000%2F1000&datetime=2023-08-16T00:00:00Z%2F2023-08-16T00:00:00Z&f=GeoTIFF¶meter-name=temperature")).into(), + rasterband_channel: 1, + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (0., -90.).into(), + x_pixel_size: 0.499_305_555_555_555_6, + y_pixel_size: -0.498_614_958_448_753_5, + }, + width: 720, + height: 361, + file_not_found_handling: FileNotFoundHandling::NoData, + no_data_value: None, + properties_mapping: None, + gdal_open_options: None, + gdal_config_options: Some(vec![( + "GTIFF_HONOUR_NEGATIVE_SCALEY".to_string(), + "YES".to_string(), + )]), + allow_alphaband_as_mask: false, + retry: None, + }), + cache_ttl: Default::default(), + }, + GdalLoadingInfoTemporalSlice { + time: TimeInterval::new_unchecked( + 1_692_154_800_000, 1_692_500_400_000 + ), + params: Some(GdalDatasetParameters { + file_path: format!("/vsicurl_streaming/{}", server.url_str("/collections/GFS_isobaric/cube?bbox=0,-90,359.50000000000006,90&z=1000%2F1000&datetime=2023-08-16T03:00:00Z%2F2023-08-16T03:00:00Z&f=GeoTIFF¶meter-name=temperature")).into(), + rasterband_channel: 1, + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (0., -90.).into(), + x_pixel_size: 0.499_305_555_555_555_6, + y_pixel_size: -0.498_614_958_448_753_5, + }, + width: 720, + height: 361, + file_not_found_handling: FileNotFoundHandling::NoData, + no_data_value: None, + properties_mapping: None, + gdal_open_options: None, + gdal_config_options: Some(vec![( + "GTIFF_HONOUR_NEGATIVE_SCALEY".to_string(), + "YES".to_string(), + )]), + allow_alphaband_as_mask: false, + retry: None, + }), + cache_ttl: Default::default(), + } + ] + ); + + let result_descriptor = meta.result_descriptor().await.unwrap(); + assert_eq!( + result_descriptor, + RasterResultDescriptor { + data_type: RasterDataType::U8, + spatial_reference: SpatialReference::epsg_4326().into(), + measurement: Measurement::Unitless, + time: Some(TimeInterval::new_unchecked( + 1_692_144_000_000, + 1_692_500_400_000 + )), + bbox: Some(SpatialPartition2D::new_unchecked( + (0., 90.).into(), + (359.500_000_000_000_06, -90.).into() + )), + resolution: None, + } + ); + } +} diff --git a/services/src/datasets/external/mod.rs b/services/src/datasets/external/mod.rs index 874fab288..223fa6e47 100644 --- a/services/src/datasets/external/mod.rs +++ b/services/src/datasets/external/mod.rs @@ -1,4 +1,5 @@ pub mod aruna; +pub mod edr; pub mod gbif; pub mod gfbio_abcd; pub mod gfbio_collections; diff --git a/services/src/error.rs b/services/src/error.rs index 18cb4bc94..0b1965821 100644 --- a/services/src/error.rs +++ b/services/src/error.rs @@ -245,6 +245,8 @@ pub enum Error { PangaeaNoTsv, GfbioMissingAbcdField, + #[snafu(display("The response from the EDR server does not match the expected format."))] + EdrInvalidMetadataFormat, ExpectedExternalDataId, InvalidExternalDataId { provider: DataProviderId, diff --git a/services/src/layers/external.rs b/services/src/layers/external.rs index 6edec6d99..fd887d43c 100644 --- a/services/src/layers/external.rs +++ b/services/src/layers/external.rs @@ -1,6 +1,7 @@ use super::listing::LayerCollectionProvider; use crate::api::model::datatypes::{DataId, DataProviderId}; use crate::datasets::external::aruna::ArunaDataProviderDefinition; +use crate::datasets::external::edr::EdrDataProviderDefinition; use crate::datasets::external::gbif::GbifDataProviderDefinition; use crate::datasets::external::gfbio_abcd::GfbioAbcdDataProviderDefinition; use crate::datasets::external::gfbio_collections::GfbioCollectionsDataProviderDefinition; @@ -86,6 +87,7 @@ pub enum TypedDataProviderDefinition { EbvPortalDataProviderDefinition(EbvPortalDataProviderDefinition), NetCdfCfDataProviderDefinition(NetCdfCfDataProviderDefinition), PangaeaDataProviderDefinition(PangaeaDataProviderDefinition), + EdrDataProviderDefinition(EdrDataProviderDefinition), } impl From for TypedDataProviderDefinition { @@ -130,6 +132,12 @@ impl From for TypedDataProviderDefinition { } } +impl From for TypedDataProviderDefinition { + fn from(def: EdrDataProviderDefinition) -> Self { + Self::EdrDataProviderDefinition(def) + } +} + impl From for Box { fn from(typed: TypedDataProviderDefinition) -> Self { match typed { @@ -142,6 +150,7 @@ impl From for Box { TypedDataProviderDefinition::EbvPortalDataProviderDefinition(def) => Box::new(def), TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(def) => Box::new(def), TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => Box::new(def), + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => Box::new(def), } } } @@ -156,6 +165,7 @@ impl AsRef for TypedDataProviderDefinition { TypedDataProviderDefinition::EbvPortalDataProviderDefinition(def) => def, TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(def) => def, TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => def, + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => def, } } } @@ -185,6 +195,9 @@ impl DataProviderDefinition for TypedDataProviderDefinition { TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => { Box::new(def).initialize().await } + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => { + Box::new(def).initialize().await + } } } @@ -199,6 +212,7 @@ impl DataProviderDefinition for TypedDataProviderDefinition { TypedDataProviderDefinition::EbvPortalDataProviderDefinition(def) => def.type_name(), TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(def) => def.type_name(), TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => def.type_name(), + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => def.type_name(), } } @@ -211,6 +225,7 @@ impl DataProviderDefinition for TypedDataProviderDefinition { TypedDataProviderDefinition::EbvPortalDataProviderDefinition(def) => def.name(), TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(def) => def.name(), TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => def.name(), + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => def.name(), } } @@ -223,6 +238,7 @@ impl DataProviderDefinition for TypedDataProviderDefinition { TypedDataProviderDefinition::EbvPortalDataProviderDefinition(def) => def.id(), TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(def) => def.id(), TypedDataProviderDefinition::PangaeaDataProviderDefinition(def) => def.id(), + TypedDataProviderDefinition::EdrDataProviderDefinition(def) => def.id(), } } } diff --git a/test_data/edr/edr_GFS_isobaric.json b/test_data/edr/edr_GFS_isobaric.json new file mode 100644 index 000000000..53f278228 --- /dev/null +++ b/test_data/edr/edr_GFS_isobaric.json @@ -0,0 +1,273 @@ +{ + "id": "GFS_isobaric", + "title": "GFS - Isobaric level", + "extent": { + "spatial": { + "bbox": [ + [ + 0, + -90, + 359.50000000000006, + 90 + ] + ], + "crs": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]", + "_projString": "+proj=longlat +lon_0=180 +R=6371229" + }, + "vertical": { + "interval": [ + [ + "0.01", + "1000" + ] + ], + "values": [ + "0.01", + "1000" + ], + "vrs": "isobaric" + }, + "temporal": { + "interval": [ + [ + "2023-08-16T00:00:00Z", + "2023-08-20T03:00:00Z" + ] + ], + "values": [ + "2023-08-16T00:00:00Z", + "2023-08-16T03:00:00Z" + ], + "trs": "TIMECRS[\"DateTime\",TDATUM[\"Gregorian Calendar\"],CS[TemporalDateTime,1],AXIS[\"Time (T)\",future]" + } + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF3", + "NetCDF3_float", + "NetCDF3_grid", + "NetCDF3_grid_float", + "NetCDF3_point", + "NetCDF4", + "NetCDF4_float", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF4_point" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "NetCDF4_point", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF3_point", + "NetCDF3_grid", + "NetCDF3_grid_float", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF4", + "NetCDF4_float", + "NetCDF3", + "NetCDF3_float" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "NetCDF4", + "NetCDF3", + "CoverageJSON" + ], + "default_output_format": "NetCDF4", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "Pa", + "hPa", + "mmHg", + "mb" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "temperature": { + "type": "Parameter", + "id": "temperature", + "label": "Temperature", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + } + } + } + } \ No newline at end of file diff --git a/test_data/edr/edr_GFS_single-level.json b/test_data/edr/edr_GFS_single-level.json new file mode 100644 index 000000000..1aea605df --- /dev/null +++ b/test_data/edr/edr_GFS_single-level.json @@ -0,0 +1,1221 @@ +{ + "id": "GFS_single-level", + "title": "GFS - Single Level", + "extent": { + "spatial": { + "bbox": [ + [ + 0, + -90, + 359.50000000000006, + 90 + ] + ], + "crs": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]", + "_projString": "+proj=longlat +lon_0=180 +R=6371229" + }, + "temporal": { + "interval": [ + [ + "2023-08-23T06:00:00Z", + "2023-08-24T15:00:00Z" + ] + ], + "values": [ + "2023-08-23T06:00:00Z", + "2023-08-23T09:00:00Z", + "2023-08-23T12:00:00Z", + "2023-08-23T15:00:00Z", + "2023-08-23T18:00:00Z", + "2023-08-23T21:00:00Z", + "2023-08-24T00:00:00Z", + "2023-08-24T03:00:00Z", + "2023-08-24T06:00:00Z", + "2023-08-24T09:00:00Z", + "2023-08-24T12:00:00Z", + "2023-08-24T15:00:00Z" + ], + "trs": "TIMECRS[\"DateTime\",TDATUM[\"Gregorian Calendar\"],CS[TemporalDateTime,1],AXIS[\"Time (T)\",future]" + } + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF3", + "NetCDF3_float", + "NetCDF3_grid", + "NetCDF3_grid_float", + "NetCDF3_point", + "NetCDF4", + "NetCDF4_float", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF4_point" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "NetCDF4_point", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF3_point", + "NetCDF3_grid", + "NetCDF3_grid_float", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF4", + "NetCDF4_float", + "NetCDF3", + "NetCDF3_float" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "NetCDF4", + "NetCDF3", + "CoverageJSON" + ], + "default_output_format": "NetCDF4", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "1" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "temperature_gnd-surf": { + "type": "Parameter", + "id": "temperature_gnd-surf", + "label": "Temperature - Ground surface", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "temperature_max-wind": { + "type": "Parameter", + "id": "temperature_max-wind", + "label": "Temperature - Max. wind", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "temperature_tropopause": { + "type": "Parameter", + "id": "temperature_tropopause", + "label": "Temperature - Tropopause", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "relative-humidity_0-isoterm": { + "type": "Parameter", + "id": "relative-humidity_0-isoterm", + "label": "Relative humidity - 0C isoterm", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-1", + "label": "Relative humidity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_0-isoterm" + }, + "relative-humidity_atmosphere": { + "type": "Parameter", + "id": "relative-humidity_atmosphere", + "label": "Relative humidity - Atmosphere", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-1", + "label": "Relative humidity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "relative-humidity_tropo-freeze": { + "type": "Parameter", + "id": "relative-humidity_tropo-freeze", + "label": "Relative humidity - Highest tropospheric freezing level", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-1", + "label": "Relative humidity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropo-freeze" + }, + "precipitable-water_atmosphere": { + "type": "Parameter", + "id": "precipitable-water_atmosphere", + "label": "Precipitable water - Atmosphere", + "unit": { + "symbol": "kg/m²" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-3", + "label": "Precipitable water" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "precipitation-rate_gnd-surf": { + "type": "Parameter", + "id": "precipitation-rate_gnd-surf", + "label": "Precipitation rate - Ground surface", + "unit": { + "symbol": "kg/m²s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-7", + "label": "Precipitation rate" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "snow-depth_gnd-surf": { + "type": "Parameter", + "id": "snow-depth_gnd-surf", + "label": "Snow depth - Ground surface", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-11", + "label": "Snow depth" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "water-equivalent-of-accumulated-snow-depth_gnd-surf": { + "type": "Parameter", + "id": "water-equivalent-of-accumulated-snow-depth_gnd-surf", + "label": "Water equivalent of accumulated snow depth - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-13", + "label": "Water equivalent of accumulated snow depth" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "percent-frozen-precipitation_gnd-surf": { + "type": "Parameter", + "id": "percent-frozen-precipitation_gnd-surf", + "label": "Percent frozen precipitation - Ground surface", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-39", + "label": "Percent frozen precipitation" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "categorical-rain-yes-1-no-0_gnd-surf": { + "type": "Parameter", + "id": "categorical-rain-yes-1-no-0_gnd-surf", + "label": "Categorical rain (yes=1; no=0) - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-192", + "label": "Categorical rain (yes=1; no=0)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "categorical-freezing-rain-yes-1-no-0_gnd-surf": { + "type": "Parameter", + "id": "categorical-freezing-rain-yes-1-no-0_gnd-surf", + "label": "Categorical freezing rain (yes=1; no=0) - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-193", + "label": "Categorical freezing rain (yes=1; no=0)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "categorical-ice-pellets-yes-1-no-0_gnd-surf": { + "type": "Parameter", + "id": "categorical-ice-pellets-yes-1-no-0_gnd-surf", + "label": "Categorical ice pellets (yes=1; no=0) - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-194", + "label": "Categorical ice pellets (yes=1; no=0)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "categorical-snow-yes-1-no-0_gnd-surf": { + "type": "Parameter", + "id": "categorical-snow-yes-1-no-0_gnd-surf", + "label": "Categorical snow (yes=1; no=0) - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-195", + "label": "Categorical snow (yes=1; no=0)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "u-component-of-wind_max-wind": { + "type": "Parameter", + "id": "u-component-of-wind_max-wind", + "label": "U component of wind - Max. wind", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-2", + "label": "U component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "u-component-of-wind_tropopause": { + "type": "Parameter", + "id": "u-component-of-wind_tropopause", + "label": "U component of wind - Tropopause", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-2", + "label": "U component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "u-component-of-wind_PBL": { + "type": "Parameter", + "id": "u-component-of-wind_PBL", + "label": "U component of wind - Planetary boundary layer", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-2", + "label": "U component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_PBL" + }, + "v-component-of-wind_max-wind": { + "type": "Parameter", + "id": "v-component-of-wind_max-wind", + "label": "V component of wind - Max. wind", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-3", + "label": "V component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "v-component-of-wind_tropopause": { + "type": "Parameter", + "id": "v-component-of-wind_tropopause", + "label": "V component of wind - Tropopause", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-3", + "label": "V component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "v-component-of-wind_PBL": { + "type": "Parameter", + "id": "v-component-of-wind_PBL", + "label": "V component of wind - Planetary boundary layer", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-3", + "label": "V component of wind" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_PBL" + }, + "wind-speed-gust_gnd-surf": { + "type": "Parameter", + "id": "wind-speed-gust_gnd-surf", + "label": "Wind speed (gust) - Ground surface", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-22", + "label": "Wind speed (gust)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "vertical-speed-shear_tropopause": { + "type": "Parameter", + "id": "vertical-speed-shear_tropopause", + "label": "Vertical speed shear - Tropopause", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-192", + "label": "Vertical speed shear" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "frictional-velocity_gnd-surf": { + "type": "Parameter", + "id": "frictional-velocity_gnd-surf", + "label": "Frictional velocity - Ground surface", + "unit": { + "symbol": "m/s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-197", + "label": "Frictional velocity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "ventilation-rate_PBL": { + "type": "Parameter", + "id": "ventilation-rate_PBL", + "label": "Ventilation rate - Planetary boundary layer", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-2-224", + "label": "Ventilation rate" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_PBL" + }, + "pressure_gnd-surf": { + "type": "Parameter", + "id": "pressure_gnd-surf", + "label": "Pressure - Ground surface", + "unit": { + "symbol": "Pa" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-0", + "label": "Pressure" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "pressure_max-wind": { + "type": "Parameter", + "id": "pressure_max-wind", + "label": "Pressure - Max. wind", + "unit": { + "symbol": "Pa" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-0", + "label": "Pressure" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "pressure_tropopause": { + "type": "Parameter", + "id": "pressure_tropopause", + "label": "Pressure - Tropopause", + "unit": { + "symbol": "Pa" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-0", + "label": "Pressure" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "pressure-reduced-to-msl_mean-sea": { + "type": "Parameter", + "id": "pressure-reduced-to-msl_mean-sea", + "label": "Pressure reduced to MSL - Mean sea", + "unit": { + "symbol": "Pa" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-1", + "label": "Pressure reduced to MSL" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_mean-sea" + }, + "icao-standard-atmosphere-reference-height_max-wind": { + "type": "Parameter", + "id": "icao-standard-atmosphere-reference-height_max-wind", + "label": "ICAO Standard Atmosphere reference height - Max. wind", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-3", + "label": "ICAO Standard Atmosphere reference height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "icao-standard-atmosphere-reference-height_tropopause": { + "type": "Parameter", + "id": "icao-standard-atmosphere-reference-height_tropopause", + "label": "ICAO Standard Atmosphere reference height - Tropopause", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-3", + "label": "ICAO Standard Atmosphere reference height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "geopotential-height_gnd-surf": { + "type": "Parameter", + "id": "geopotential-height_gnd-surf", + "label": "Geopotential height - Ground surface", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "geopotential-height_0-isoterm": { + "type": "Parameter", + "id": "geopotential-height_0-isoterm", + "label": "Geopotential height - 0C isoterm", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_0-isoterm" + }, + "geopotential-height_max-wind": { + "type": "Parameter", + "id": "geopotential-height_max-wind", + "label": "Geopotential height - Max. wind", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + }, + "geopotential-height_tropopause": { + "type": "Parameter", + "id": "geopotential-height_tropopause", + "label": "Geopotential height - Tropopause", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropopause" + }, + "geopotential-height_tropo-freeze": { + "type": "Parameter", + "id": "geopotential-height_tropo-freeze", + "label": "Geopotential height - Highest tropospheric freezing level", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_tropo-freeze" + }, + "geopotential-height_cloud-ceiling": { + "type": "Parameter", + "id": "geopotential-height_cloud-ceiling", + "label": "Geopotential height - Cloud ceiling", + "unit": { + "symbol": "gpm" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-5", + "label": "Geopotential height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_cloud-ceiling" + }, + "msl-pressure-eta-reduction_mean-sea": { + "type": "Parameter", + "id": "msl-pressure-eta-reduction_mean-sea", + "label": "MSL pressure (Eta reduction) - Mean sea", + "unit": { + "symbol": "Pa" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-192", + "label": "MSL pressure (Eta reduction)" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_mean-sea" + }, + "planetary-boundary-layer-height_gnd-surf": { + "type": "Parameter", + "id": "planetary-boundary-layer-height_gnd-surf", + "label": "Planetary boundary layer height - Ground surface", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-3-196", + "label": "Planetary boundary layer height" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "total-cloud-cover_atmosphere": { + "type": "Parameter", + "id": "total-cloud-cover_atmosphere", + "label": "Total cloud cover - Atmosphere", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-1", + "label": "Total cloud cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "low-cloud-cover_low-cloud": { + "type": "Parameter", + "id": "low-cloud-cover_low-cloud", + "label": "Low cloud cover - Low cloud layer", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-3", + "label": "Low cloud cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_low-cloud" + }, + "medium-cloud-cover_mid-cloud": { + "type": "Parameter", + "id": "medium-cloud-cover_mid-cloud", + "label": "Medium cloud cover - Middle cloud layer", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-4", + "label": "Medium cloud cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_mid-cloud" + }, + "high-cloud-cover_high-cld": { + "type": "Parameter", + "id": "high-cloud-cover_high-cld", + "label": "High cloud cover - High cloud layer", + "unit": { + "symbol": "%" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-5", + "label": "High cloud cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_high-cld" + }, + "cloud-water_atmosphere": { + "type": "Parameter", + "id": "cloud-water_atmosphere", + "label": "Cloud water - Atmosphere", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-6", + "label": "Cloud water" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "sunshine-duration_gnd-surf": { + "type": "Parameter", + "id": "sunshine-duration_gnd-surf", + "label": "Sunshine duration - Ground surface", + "unit": { + "symbol": "s" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-6-201", + "label": "Sunshine duration" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "convective-available-potential-energy_gnd-surf": { + "type": "Parameter", + "id": "convective-available-potential-energy_gnd-surf", + "label": "Convective available potential energy - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-7-6", + "label": "Convective available potential energy" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "convective-inhibition_gnd-surf": { + "type": "Parameter", + "id": "convective-inhibition_gnd-surf", + "label": "Convective inhibition - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-7-7", + "label": "Convective inhibition" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "surface-lifted-index_gnd-surf": { + "type": "Parameter", + "id": "surface-lifted-index_gnd-surf", + "label": "Surface lifted index - Ground surface", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-7-192", + "label": "Surface lifted index" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "best-4-layer-lifted-index_gnd-surf": { + "type": "Parameter", + "id": "best-4-layer-lifted-index_gnd-surf", + "label": "Best (4 layer) lifted index - Ground surface", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-7-193", + "label": "Best (4 layer) lifted index" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "total-ozone_atmosphere": { + "type": "Parameter", + "id": "total-ozone_atmosphere", + "label": "Total ozone - Atmosphere", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-14-0", + "label": "Total ozone" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "composite-reflectivity_atmosphere": { + "type": "Parameter", + "id": "composite-reflectivity_atmosphere", + "label": "Composite reflectivity - Atmosphere", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-16-196", + "label": "Composite reflectivity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_atmosphere" + }, + "visibility_gnd-surf": { + "type": "Parameter", + "id": "visibility_gnd-surf", + "label": "Visibility - Ground surface", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-19-0", + "label": "Visibility" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "land-cover_gnd-surf": { + "type": "Parameter", + "id": "land-cover_gnd-surf", + "label": "Land cover - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-0", + "label": "Land cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "surface-roughness_gnd-surf": { + "type": "Parameter", + "id": "surface-roughness_gnd-surf", + "label": "Surface roughness - Ground surface", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-1", + "label": "Surface roughness" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "vegetation_gnd-surf": { + "type": "Parameter", + "id": "vegetation_gnd-surf", + "label": "Vegetation - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-4", + "label": "Vegetation" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "plant-canopy-surface-water_gnd-surf": { + "type": "Parameter", + "id": "plant-canopy-surface-water_gnd-surf", + "label": "Plant canopy surface water - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-196", + "label": "Plant canopy surface water" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "wilting-point_gnd-surf": { + "type": "Parameter", + "id": "wilting-point_gnd-surf", + "label": "Wilting point - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-201", + "label": "Wilting point" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "soil-type_gnd-surf": { + "type": "Parameter", + "id": "soil-type_gnd-surf", + "label": "Soil type - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-3-0", + "label": "Soil type" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "field-capacity_gnd-surf": { + "type": "Parameter", + "id": "field-capacity_gnd-surf", + "label": "Field capacity - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-3-203", + "label": "Field capacity" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "haines-index_gnd-surf": { + "type": "Parameter", + "id": "haines-index_gnd-surf", + "label": "Haines index - Ground surface", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-4-2", + "label": "Haines index" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "ice-cover_gnd-surf": { + "type": "Parameter", + "id": "ice-cover_gnd-surf", + "label": "Ice cover - Ground surface", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_10-2-0", + "label": "Ice cover" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "ice-thickness_gnd-surf": { + "type": "Parameter", + "id": "ice-thickness_gnd-surf", + "label": "Ice thickness - Ground surface", + "unit": { + "symbol": "m" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_10-2-1", + "label": "Ice thickness" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + }, + "ice-temperature_gnd-surf": { + "type": "Parameter", + "id": "ice-temperature_gnd-surf", + "label": "Ice temperature - Ground surface", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_10-2-8", + "label": "Ice temperature" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_gnd-surf" + } + } +} \ No newline at end of file diff --git a/test_data/edr/edr_PointsInFrance.json b/test_data/edr/edr_PointsInFrance.json new file mode 100644 index 000000000..e39c6f3ac --- /dev/null +++ b/test_data/edr/edr_PointsInFrance.json @@ -0,0 +1,102 @@ +{ + "links": [ + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/", + "rel": "self", + "type": "application/json" + }, + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/cube", + "rel": "data", + "type": "application/geo+json" + } + ], + "id": "PointsInFrance", + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -90.0, + 180.0, + 90.0 + ] + ], + "crs": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + }, + "vertical": { + "interval": [ + [ + "0\\10cm", + "40\\100cm" + ] + ], + "values": [ + "0\\10cm", + "10\\40cm" + ], + "vrs": "ibl#between-depth" + }, + "temporal": { + "interval": [ + [ + "2023-01-01T12:42:29Z", + "2023-02-01T12:42:29Z" + ] + ], + "values": [ + "2023-01-01T12:42:29Z/2023-02-01T12:42:29Z" + ], + "trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" + } + }, + "data_queries": { + "cube": { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/cube", + "rel": "data", + "variables": { + "title": "PointsInFrance", + "description": "SELECT * FROM GeoStream2", + "query_type": "cube", + "output_formats": [ + "GeoJSON" + ], + "default_output_format": "GeoJSON", + "crs_details": [ + { + "crs": "EPSG:4326", + "wkt": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + } + ] + } + } + }, + "crs": [ + "EPSG:4326" + ], + "output_formats": [ + "GeoJSON" + ], + "parameter_names": { + "ID": { + "type": "Parameter", + "description": "ID", + "label": "ID", + "observedProperty": { + "id": "ID", + "label": "ID" + }, + "data-type": "INTEGER" + }, + "NAME": { + "type": "Parameter", + "description": "NAME", + "label": "NAME", + "observedProperty": { + "id": "NAME", + "label": "NAME" + }, + "data-type": "STRING" + } + } +} \ No newline at end of file diff --git a/test_data/edr/edr_PointsInGermany.json b/test_data/edr/edr_PointsInGermany.json new file mode 100644 index 000000000..f35b7b705 --- /dev/null +++ b/test_data/edr/edr_PointsInGermany.json @@ -0,0 +1,79 @@ +{ + "links": [ + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/", + "rel": "self", + "type": "application/json" + }, + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/cube", + "rel": "data", + "type": "application/geo+json" + } + ], + "id": "PointsInGermany", + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ], + "crs": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + }, + "temporal": { + "interval": [ + [ + "2023-01-01T12:42:29Z", + "2023-02-01T12:42:29Z" + ] + ], + "values": [ + "2023-01-01T12:42:29Z/2023-02-01T12:42:29Z" + ], + "trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" + } + }, + "data_queries": { + "cube": { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/cube", + "rel": "data", + "variables": { + "title": "PointsInGermany", + "description": "SELECT * FROM GeoStream", + "query_type": "cube", + "output_formats": [ + "GeoJSON" + ], + "default_output_format": "GeoJSON", + "crs_details": [ + { + "crs": "EPSG:4326", + "wkt": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + } + ] + } + } + }, + "crs": [ + "EPSG:4326" + ], + "output_formats": [ + "GeoJSON" + ], + "parameter_names": { + "ID": { + "type": "Parameter", + "description": "ID", + "label": "ID", + "observedProperty": { + "id": "ID", + "label": "ID" + }, + "data-type": "INTEGER" + } + } + } \ No newline at end of file diff --git a/test_data/edr/edr_collections.json b/test_data/edr/edr_collections.json new file mode 100644 index 000000000..0dd01f4ed --- /dev/null +++ b/test_data/edr/edr_collections.json @@ -0,0 +1,1376 @@ +{ + "collections": [ + { + "id": "GFS_single-level", + "title": "GFS - Single Level", + "extent": { + "spatial": { + "bbox": [ + [ + 0, + -90, + 359.50000000000006, + 90 + ] + ], + "crs": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]", + "_projString": "+proj=longlat +lon_0=180 +R=6371229" + }, + "temporal": { + "interval": [ + [ + "2023-08-15T06:00:00Z", + "2023-08-19T09:00:00Z" + ] + ], + "values": [ + "2023-08-15T06:00:00Z", + "2023-08-15T09:00:00Z", + "2023-08-15T12:00:00Z", + "2023-08-15T15:00:00Z", + "2023-08-15T18:00:00Z", + "2023-08-15T21:00:00Z", + "2023-08-16T00:00:00Z", + "2023-08-16T03:00:00Z", + "2023-08-16T06:00:00Z", + "2023-08-16T09:00:00Z", + "2023-08-16T12:00:00Z", + "2023-08-16T15:00:00Z", + "2023-08-16T18:00:00Z", + "2023-08-16T21:00:00Z", + "2023-08-17T00:00:00Z", + "2023-08-17T03:00:00Z", + "2023-08-17T06:00:00Z", + "2023-08-17T09:00:00Z", + "2023-08-17T12:00:00Z", + "2023-08-17T15:00:00Z", + "2023-08-17T18:00:00Z", + "2023-08-17T21:00:00Z", + "2023-08-18T00:00:00Z", + "2023-08-18T03:00:00Z", + "2023-08-18T06:00:00Z", + "2023-08-18T09:00:00Z", + "2023-08-18T12:00:00Z", + "2023-08-18T15:00:00Z", + "2023-08-18T18:00:00Z", + "2023-08-18T21:00:00Z", + "2023-08-19T00:00:00Z", + "2023-08-19T03:00:00Z", + "2023-08-19T06:00:00Z", + "2023-08-19T09:00:00Z" + ], + "trs": "TIMECRS[\"DateTime\",TDATUM[\"Gregorian Calendar\"],CS[TemporalDateTime,1],AXIS[\"Time (T)\",future]" + } + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF3", + "NetCDF3_float", + "NetCDF3_grid", + "NetCDF3_grid_float", + "NetCDF3_point", + "NetCDF4", + "NetCDF4_float", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF4_point" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "NetCDF4_point", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF3_point", + "NetCDF3_grid", + "NetCDF3_grid_float", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF4", + "NetCDF4_float", + "NetCDF3", + "NetCDF3_float" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "NetCDF4", + "NetCDF3", + "CoverageJSON" + ], + "default_output_format": "NetCDF4", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "1" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "temperature_max-wind": { + "type": "Parameter", + "id": "temperature_max-wind", + "label": "Temperature - Max. wind", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + }, + "_verticalReference": "special_max-wind" + } + } + }, + { + "id": "GFS_single-level_50", + "title": "GFS - Single Level (50)", + "links": [ + { + "href": "https://example.com/edr/collections/GFS_single-level_50?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Collection metadata in JSON" + }, + { + "href": "https://example.com/edr/collections/GFS_single-level_50/instances?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Instances metadata in JSON" + } + ], + "extent": { + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF3", + "NetCDF3_float", + "NetCDF3_grid", + "NetCDF3_grid_float", + "NetCDF3_point", + "NetCDF4", + "NetCDF4_float", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF4_point" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "NetCDF4_point", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF3_point", + "NetCDF3_grid", + "NetCDF3_grid_float", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF4", + "NetCDF4_float", + "NetCDF3", + "NetCDF3_float" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_single-level_50/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "NetCDF4", + "NetCDF3", + "CoverageJSON" + ], + "default_output_format": "NetCDF4", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "1" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "total-precipitation_gnd-surf_stat:acc/P6DT3H": { + "type": "Parameter", + "id": "total-precipitation_gnd-surf_stat:acc/P6DT3H", + "label": "Total precipitation - Ground surface - Accumulation 147h", + "unit": { + "symbol": "kg/m²" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-8", + "label": "Total precipitation" + }, + "measurementType": { + "method": "spec:regular;stat:accumulation/P6DT3H", + "duration": "P6DT3H" + }, + "_verticalReference": "special_gnd-surf" + }, + "convective-precipitation_gnd-surf_stat:acc/P6DT3H": { + "type": "Parameter", + "id": "convective-precipitation_gnd-surf_stat:acc/P6DT3H", + "label": "Convective precipitation - Ground surface - Accumulation 147h", + "unit": { + "symbol": "kg/m²" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-1-10", + "label": "Convective precipitation" + }, + "measurementType": { + "method": "spec:regular;stat:accumulation/P6DT3H", + "duration": "P6DT3H" + }, + "_verticalReference": "special_gnd-surf" + } + } + }, + { + "id": "GFS_isobaric", + "title": "GFS - Isobaric level", + "links": [ + { + "href": "https://example.com/edr/collections/GFS_isobaric?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Collection metadata in JSON" + }, + { + "href": "https://example.com/edr/collections/GFS_isobaric/instances?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Instances metadata in JSON" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 0, + -90, + 359.50000000000006, + 90 + ] + ], + "crs": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]", + "_projString": "+proj=longlat +lon_0=180 +R=6371229" + }, + "vertical": { + "interval": [ + [ + "0.01", + "1000" + ] + ], + "values": [ + "0.01", + "1000" + ], + "vrs": "isobaric" + }, + "temporal": { + "interval": [ + [ + "2023-08-15T06:00:00Z", + "2023-08-19T09:00:00Z" + ] + ], + "values": [ + "2023-08-15T06:00:00Z", + "2023-08-15T09:00:00Z", + "2023-08-15T12:00:00Z", + "2023-08-15T15:00:00Z", + "2023-08-15T18:00:00Z", + "2023-08-15T21:00:00Z", + "2023-08-16T00:00:00Z", + "2023-08-16T03:00:00Z", + "2023-08-16T06:00:00Z", + "2023-08-16T09:00:00Z", + "2023-08-16T12:00:00Z", + "2023-08-16T15:00:00Z", + "2023-08-16T18:00:00Z", + "2023-08-16T21:00:00Z", + "2023-08-17T00:00:00Z", + "2023-08-17T03:00:00Z", + "2023-08-17T06:00:00Z", + "2023-08-17T09:00:00Z", + "2023-08-17T12:00:00Z", + "2023-08-17T15:00:00Z", + "2023-08-17T18:00:00Z", + "2023-08-17T21:00:00Z", + "2023-08-18T00:00:00Z", + "2023-08-18T03:00:00Z", + "2023-08-18T06:00:00Z", + "2023-08-18T09:00:00Z", + "2023-08-18T12:00:00Z", + "2023-08-18T15:00:00Z", + "2023-08-18T18:00:00Z", + "2023-08-18T21:00:00Z", + "2023-08-19T00:00:00Z", + "2023-08-19T03:00:00Z", + "2023-08-19T06:00:00Z", + "2023-08-19T09:00:00Z" + ], + "trs": "TIMECRS[\"DateTime\",TDATUM[\"Gregorian Calendar\"],CS[TemporalDateTime,1],AXIS[\"Time (T)\",future]" + } + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF3", + "NetCDF3_float", + "NetCDF3_grid", + "NetCDF3_grid_float", + "NetCDF3_point", + "NetCDF4", + "NetCDF4_float", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF4_point" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "NetCDF4_point", + "NetCDF4_grid", + "NetCDF4_grid_float", + "NetCDF3_point", + "NetCDF3_grid", + "NetCDF3_grid_float", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML", + "NetCDF4", + "NetCDF4_float", + "NetCDF3", + "NetCDF3_float" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON", + "NetCDF4", + "NetCDF3" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_isobaric/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "NetCDF4", + "NetCDF3", + "CoverageJSON" + ], + "default_output_format": "NetCDF4", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "Pa", + "hPa", + "mmHg", + "mb" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "temperature": { + "type": "Parameter", + "id": "temperature", + "label": "Temperature", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_0-0-0", + "label": "Temperature" + }, + "measurementType": { + "method": "spec:regular" + } + } + } + }, + { + "id": "GFS_between-depth", + "title": "GFS - Layer between two depths below land surface", + "links": [ + { + "href": "https://example.com/edr/collections/GFS_between-depth?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Collection metadata in JSON" + }, + { + "href": "https://example.com/edr/collections/GFS_between-depth/instances?f=JSON", + "rel": "data", + "type": "application/json", + "title": "Instances metadata in JSON" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 0, + -90, + 359.50000000000006, + 90 + ] + ], + "crs": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]", + "_projString": "+proj=longlat +lon_0=180 +R=6371229" + }, + "vertical": { + "interval": [ + [ + "0\\10cm", + "40\\100cm" + ] + ], + "values": [ + "0\\10cm", + "100\\200cm", + "10\\40cm", + "40\\100cm" + ], + "vrs": "between-depth" + }, + "temporal": { + "interval": [ + [ + "2023-08-15T06:00:00Z", + "2023-08-19T09:00:00Z" + ] + ], + "values": [ + "2023-08-15T06:00:00Z", + "2023-08-15T09:00:00Z", + "2023-08-15T12:00:00Z", + "2023-08-15T15:00:00Z", + "2023-08-15T18:00:00Z", + "2023-08-15T21:00:00Z", + "2023-08-16T00:00:00Z", + "2023-08-16T03:00:00Z", + "2023-08-16T06:00:00Z", + "2023-08-16T09:00:00Z", + "2023-08-16T12:00:00Z", + "2023-08-16T15:00:00Z", + "2023-08-16T18:00:00Z", + "2023-08-16T21:00:00Z", + "2023-08-17T00:00:00Z", + "2023-08-17T03:00:00Z", + "2023-08-17T06:00:00Z", + "2023-08-17T09:00:00Z", + "2023-08-17T12:00:00Z", + "2023-08-17T15:00:00Z", + "2023-08-17T18:00:00Z", + "2023-08-17T21:00:00Z", + "2023-08-18T00:00:00Z", + "2023-08-18T03:00:00Z", + "2023-08-18T06:00:00Z", + "2023-08-18T09:00:00Z", + "2023-08-18T12:00:00Z", + "2023-08-18T15:00:00Z", + "2023-08-18T18:00:00Z", + "2023-08-18T21:00:00Z", + "2023-08-19T00:00:00Z", + "2023-08-19T03:00:00Z", + "2023-08-19T06:00:00Z", + "2023-08-19T09:00:00Z" + ], + "trs": "TIMECRS[\"DateTime\",TDATUM[\"Gregorian Calendar\"],CS[TemporalDateTime,1],AXIS[\"Time (T)\",future]" + } + }, + "crs": [ + "CRS:84" + ], + "output_formats": [ + "CoverageJSON", + "CoverageJSON_Grid", + "CoverageJSON_MultiPointSeries", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "data_queries": { + "position": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/position", + "rel": "data", + "title": "Position Query", + "templated": false, + "variables": { + "title": "Position Query", + "description": "Data at point location", + "query_type": "position", + "output_formats": [ + "CoverageJSON", + "KML" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "radius": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/radius", + "rel": "data", + "title": "Radius Query", + "templated": false, + "variables": { + "title": "Radius Query", + "description": "Data within circle", + "query_type": "radius", + "output_formats": [ + "CoverageJSON" + ], + "default_output_format": "CoverageJSON", + "within_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "area": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/area", + "rel": "data", + "title": "Area Query", + "templated": false, + "variables": { + "title": "Area Query", + "description": "Data within polygonal area", + "query_type": "area", + "output_formats": [ + "CoverageJSON_MultiPointSeries", + "CoverageJSON_Grid", + "GRIB2", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "CoverageJSON_MultiPointSeries", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "cube": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/cube", + "rel": "data", + "title": "Cube Query", + "templated": false, + "variables": { + "title": "Cube Query", + "description": "Data within 4D cube", + "query_type": "cube", + "output_formats": [ + "GRIB2", + "CoverageJSON", + "GeoTIFF", + "GeoTIFF_float", + "KML" + ], + "default_output_format": "GRIB2", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "trajectory": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/trajectory", + "rel": "data", + "title": "Trajectory Query", + "templated": false, + "variables": { + "title": "Trajectory Query", + "description": "Data along trajectory", + "query_type": "trajectory", + "output_formats": [ + "CoverageJSON" + ], + "default_output_format": "CoverageJSON", + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + }, + "corridor": { + "link": { + "href": "https://example.com/edr/collections/GFS_between-depth/corridor", + "rel": "data", + "title": "Corridor Query", + "templated": false, + "variables": { + "title": "Corridor Query", + "description": "Data within corridor", + "query_type": "corridor", + "output_formats": [ + "CoverageJSON" + ], + "default_output_format": "CoverageJSON", + "width_units": [ + "m", + "km", + "ft", + "mi", + "NM(US)", + "NM(UK)" + ], + "height_units": [ + "1" + ], + "crs_details": [ + { + "crs": "CRS:84", + "wkt": "GEOGCS[\"Unknown\", DATUM[\"Unknown\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\",0], UNIT[\"degree\", 0.017453], AXIS[\"Lon\", EAST], AXIS[\"Lat\", NORTH]]" + } + ] + } + } + } + }, + "parameter_names": { + "soil-temperature": { + "type": "Parameter", + "id": "soil-temperature", + "label": "Soil temperature", + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-2", + "label": "Soil temperature" + }, + "measurementType": { + "method": "spec:regular" + } + }, + "volumetric-soil-moisture-content": { + "type": "Parameter", + "id": "volumetric-soil-moisture-content", + "label": "Volumetric soil moisture content", + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-0-192", + "label": "Volumetric soil moisture content" + }, + "measurementType": { + "method": "spec:regular" + } + }, + "liquid-volumetric-soil-moisture-non-frozen": { + "type": "Parameter", + "id": "liquid-volumetric-soil-moisture-non-frozen", + "label": "Liquid volumetric soil moisture (non frozen)", + "unit": { + "symbol": "1" + }, + "observedProperty": { + "id": "http://codes.wmo.int/grib2/codeflag/4.2/_2-3-192", + "label": "Liquid volumetric soil moisture (non frozen)" + }, + "measurementType": { + "method": "spec:regular" + } + } + } + }, + { + "links": [ + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/", + "rel": "self", + "type": "application/json" + }, + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/cube", + "rel": "data", + "type": "application/geo+json" + } + ], + "id": "PointsInGermany", + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ], + "crs": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + }, + "temporal": { + "interval": [ + [ + "2023-01-01T12:42:29Z", + "2023-02-01T12:42:29Z" + ] + ], + "values": [ + "2023-01-01T12:42:29Z/2023-02-01T12:42:29Z" + ], + "trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" + } + }, + "data_queries": { + "cube": { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInGermany/cube", + "rel": "data", + "variables": { + "title": "PointsInGermany", + "description": "SELECT * FROM GeoStream", + "query_type": "cube", + "output_formats": [ + "GeoJSON" + ], + "default_output_format": "GeoJSON", + "crs_details": [ + { + "crs": "EPSG:4326", + "wkt": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + } + ] + } + } + }, + "crs": [ + "EPSG:4326" + ], + "output_formats": [ + "GeoJSON" + ], + "parameter_names": { + "ID": { + "type": "Parameter", + "description": "ID", + "label": "ID", + "observedProperty": { + "id": "ID", + "label": "ID" + }, + "data-type": "INTEGER" + } + } + }, + { + "links": [ + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/", + "rel": "self", + "type": "application/json" + }, + { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/cube", + "rel": "data", + "type": "application/geo+json" + } + ], + "id": "PointsInFrance", + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ], + "crs": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + }, + "vertical": { + "interval": [ + [ + "0\\10cm", + "40\\100cm" + ] + ], + "values": [ + "0\\10cm", + "10\\40cm" + ], + "vrs": "between-depth" + }, + "temporal": { + "interval": [ + [ + "2023-01-01T12:42:29Z", + "2023-02-01T12:42:29Z" + ] + ], + "values": [ + "2023-01-01T12:42:29Z/2023-02-01T12:42:29Z" + ], + "trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" + } + }, + "data_queries": { + "cube": { + "href": "https://dbs-demo.mathematik.uni-marburg.de/dev/cep/edr/collections/PointsInFrance/cube", + "rel": "data", + "variables": { + "title": "PointsInFrance", + "description": "SELECT * FROM GeoStream2", + "query_type": "cube", + "output_formats": [ + "GeoJSON" + ], + "default_output_format": "GeoJSON", + "crs_details": [ + { + "crs": "EPSG:4326", + "wkt": "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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]" + } + ] + } + } + }, + "crs": [ + "EPSG:4326" + ], + "output_formats": [ + "GeoJSON" + ], + "parameter_names": { + "ID": { + "type": "Parameter", + "description": "ID", + "label": "ID", + "observedProperty": { + "id": "ID", + "label": "ID" + }, + "data-type": "INTEGER" + }, + "NAME": { + "type": "Parameter", + "description": "NAME", + "label": "NAME", + "observedProperty": { + "id": "NAME", + "label": "NAME" + }, + "data-type": "STRING" + } + } + } + ] + } \ No newline at end of file diff --git a/test_data/edr/edr_raster.tif b/test_data/edr/edr_raster.tif new file mode 100644 index 000000000..c6e0f6775 Binary files /dev/null and b/test_data/edr/edr_raster.tif differ diff --git a/test_data/provider_defs/open_weather.json b/test_data/provider_defs/open_weather.json new file mode 100644 index 000000000..c83536df3 --- /dev/null +++ b/test_data/provider_defs/open_weather.json @@ -0,0 +1,14 @@ +{ + "type": "EdrDataProviderDefinition", + "id": "0668d980-6c82-47c4-b1d9-1096f6770666", + "name": "Open Weather", + "baseUrl": "https://ogcie.iblsoft.com/edr", + "discreteVrs": ["ibl#between-depth"], + "provenance": [ + { + "citation": "For GFS: U.S. National Centers for Environmental Information. Scale not given. \"Global Forecast System\" For others look at source website.", + "license": "GFS is public domain. For others look at source website.", + "uri": "https://www.iblsoft.com/products/open-weather" + } + ] +} \ No newline at end of file