From 1556aedd479b1e2d5f0df995ea07a3b633213507 Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Mon, 26 Feb 2024 11:14:54 -0500 Subject: [PATCH] align OACov to latest specification updates --- .../data-publishing/ogcapi-coverages.rst | 6 +- docs/source/plugins.rst | 13 +- docs/source/tour.rst | 15 +- pygeoapi/api.py | 276 ++++++++---------- pygeoapi/django_/urls.py | 17 +- pygeoapi/django_/views.py | 57 ++-- pygeoapi/flask_app.py | 42 +-- pygeoapi/openapi.py | 64 ++-- pygeoapi/provider/base.py | 18 -- pygeoapi/provider/rasterio_.py | 113 ++----- pygeoapi/provider/xarray_.py | 145 ++------- pygeoapi/provider/xarray_edr.py | 9 - pygeoapi/starlette_app.py | 55 ++-- .../collections/coverage/domainset.html | 37 --- .../collections/coverage/rangetype.html | 19 -- pygeoapi/templates/collections/schema.html | 46 +++ tests/test_api.py | 71 +++-- tests/test_rasterio_provider.py | 20 +- tests/test_xarray_netcdf_provider.py | 18 +- tests/test_xarray_zarr_provider.py | 20 +- 20 files changed, 358 insertions(+), 703 deletions(-) delete mode 100644 pygeoapi/templates/collections/coverage/domainset.html delete mode 100644 pygeoapi/templates/collections/coverage/rangetype.html create mode 100644 pygeoapi/templates/collections/schema.html diff --git a/docs/source/data-publishing/ogcapi-coverages.rst b/docs/source/data-publishing/ogcapi-coverages.rst index 0861751cd2..1390ee75cd 100644 --- a/docs/source/data-publishing/ogcapi-coverages.rst +++ b/docs/source/data-publishing/ogcapi-coverages.rst @@ -103,10 +103,8 @@ Data access examples * http://localhost:5000/collections * overview of dataset * http://localhost:5000/collections/foo -* coverage rangetype - * http://localhost:5000/collections/foo/coverage/rangetype -* coverage domainset - * http://localhost:5000/collections/foo/coverage/domainset +* schema of dataset + * http://localhost:5000/collections/foo/schema * coverage access via CoverageJSON (default) * http://localhost:5000/collections/foo/coverage?f=json * coverage access via native format (as defined in ``provider.format.name``) diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index 018c13725c..a0e8a9eb1c 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -240,12 +240,15 @@ The below template provides a minimal example (let's call the file ``mycoolraste super().__init__(provider_def) self.num_bands = 4 self.axes = ['Lat', 'Long'] + self.fields = self.get_fields() - def get_coverage_domainset(self): - # return a CIS JSON DomainSet - - def get_coverage_rangetype(self): - # return a CIS JSON RangeType + def get_fields(self): + # generate a JSON Schema of coverage band metadata + return { + 'b1': { + 'type': 'number' + } + } def query(self, bands=[], subsets={}, format_='json', **kwargs): # process bands and subsets parameters diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 06a6e363d1..4fd4e51da3 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -117,19 +117,12 @@ Delete an item from a collection: Raster data ----------- -Collection coverage domainset -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Collection coverage schema +^^^^^^^^^^^^^^^^^^^^^^^^^^ -This page provides information on a collection coverage spatial properties and axis information. +This page provides information on a collection coverage information. -http://localhost:5000/collections/gdps-temperature/coverage/domainset - -Collection coverage rangetype -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This page provides information on a collection coverage rangetype (bands) information. - -http://localhost:5000/collections/gdps-temperature/coverage/rangetype +http://localhost:5000/collections/gdps-temperature/schema Collection coverage data ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pygeoapi/api.py b/pygeoapi/api.py index a3941494bb..ce9e4eff4e 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -7,7 +7,7 @@ # Colin Blackburn # Ricardo Garcia Silva # -# Copyright (c) 2023 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # Copyright (c) 2022 Francesco Bartoli # Copyright (c) 2022 John A Stevenson and Colin Blackburn # Copyright (c) 2023 Ricardo Garcia Silva @@ -135,7 +135,9 @@ 'http://www.opengis.net/spec/ogcapi-features-2/1.0/conf/crs', 'http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/queryables', 'http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/queryables-query-parameters', # noqa - 'http://www.opengis.net/spec/ogcapi-features-4/1.0/conf/create-replace-delete' # noqa + 'http://www.opengis.net/spec/ogcapi-features-4/1.0/conf/create-replace-delete', # noqa + 'http://www.opengis.net/spec/ogcapi-features-5/1.0/conf/schemas', + 'http://www.opengis.net/spec/ogcapi-features-5/1.0/req/core-roles-features' # noqa ], 'coverage': [ 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/core', @@ -1139,6 +1141,20 @@ def describe_collections(self, request: Union[APIRequest, Any], 'href': f'{self.get_collections_url()}/{k}?f={F_HTML}' }) + if collection_data_type in ['feature', 'coverage', 'record']: + collection['links'].append({ + 'type': FORMAT_TYPES[F_JSON], + 'rel': f'{OGC_RELTYPES_BASE}/schema', + 'title': 'Schema of collection in JSON', + 'href': f'{self.get_collections_url()}/{k}/schema?f={F_JSON}' # noqa + }) + collection['links'].append({ + 'type': FORMAT_TYPES[F_HTML], + 'rel': f'{OGC_RELTYPES_BASE}/schema', + 'title': 'Schema of collection in HTML', + 'href': f'{self.get_collections_url()}/{k}/schema?f={F_HTML}' # noqa + }) + if collection_data_type in ['feature', 'record', 'tile']: # TODO: translate collection['itemType'] = collection_data_type @@ -1184,44 +1200,6 @@ def describe_collections(self, request: Union[APIRequest, Any], elif collection_data_type == 'coverage': # TODO: translate LOGGER.debug('Adding coverage based links') - collection['links'].append({ - 'type': FORMAT_TYPES[F_JSON], - 'rel': 'collection', - 'title': 'Detailed Coverage metadata in JSON', - 'href': f'{self.get_collections_url()}/{k}?f={F_JSON}' - }) - collection['links'].append({ - 'type': FORMAT_TYPES[F_HTML], - 'rel': 'collection', - 'title': 'Detailed Coverage metadata in HTML', - 'href': f'{self.get_collections_url()}/{k}?f={F_HTML}' - }) - coverage_url = f'{self.get_collections_url()}/{k}/coverage' - - collection['links'].append({ - 'type': FORMAT_TYPES[F_JSON], - 'rel': f'{OGC_RELTYPES_BASE}/coverage-domainset', - 'title': 'Coverage domain set of collection in JSON', - 'href': f'{coverage_url}/domainset?f={F_JSON}' - }) - collection['links'].append({ - 'type': FORMAT_TYPES[F_HTML], - 'rel': f'{OGC_RELTYPES_BASE}/coverage-domainset', - 'title': 'Coverage domain set of collection in HTML', - 'href': f'{coverage_url}/domainset?f={F_HTML}' - }) - collection['links'].append({ - 'type': FORMAT_TYPES[F_JSON], - 'rel': f'{OGC_RELTYPES_BASE}/coverage-rangetype', - 'title': 'Coverage range type of collection in JSON', - 'href': f'{coverage_url}/rangetype?f={F_JSON}' - }) - collection['links'].append({ - 'type': FORMAT_TYPES[F_HTML], - 'rel': f'{OGC_RELTYPES_BASE}/coverage-rangetype', - 'title': 'Coverage range type of collection in HTML', - 'href': f'{coverage_url}/rangetype?f={F_HTML}' - }) collection['links'].append({ 'type': 'application/prs.coverage+json', 'rel': f'{OGC_RELTYPES_BASE}/coverage', @@ -1252,8 +1230,21 @@ def describe_collections(self, request: Union[APIRequest, Any], pass else: collection['crs'] = [p.crs] - collection['domainset'] = p.get_coverage_domainset() - collection['rangetype'] = p.get_coverage_rangetype() + collection['extent']['spatial'] = { + 'bbox': [ + p._coverage_properties['bbox'][0], + p._coverage_properties['bbox'][1], + p._coverage_properties['bbox'][2], + p._coverage_properties['bbox'][3] + ], + 'grid': [{ + 'cellsCount': p._coverage_properties['width'], + 'resolution': p._coverage_properties['resx'] + }, { + 'cellsCount': p._coverage_properties['height'], + 'resolution': p._coverage_properties['resy'] + }] + } try: tile = get_provider_by_type(v['providers'], 'tile') @@ -1390,6 +1381,90 @@ def describe_collections(self, request: Union[APIRequest, Any], return headers, HTTPStatus.OK, to_json(fcm, self.pretty_print) + @gzip + @pre_process + @jsonldify + def get_collection_schema( + self, request: Union[APIRequest, Any], + dataset) -> Tuple[dict, int, str]: + """ + Returns a collection schema + + :param request: A request object + :param dataset: dataset name + + :returns: tuple of headers, status code, content + """ + + if not request.is_valid(): + return self.get_format_exception(request) + headers = request.get_response_headers(**self.api_headers) + + if any([dataset is None, + dataset not in self.config['resources'].keys()]): + + msg = 'Collection not found' + return self.get_exception( + HTTPStatus.NOT_FOUND, headers, request.format, 'NotFound', msg) + + LOGGER.debug('Creating collection schema') + try: + LOGGER.debug('Loading feature provider') + p = load_plugin('provider', get_provider_by_type( + self.config['resources'][dataset]['providers'], 'feature')) + except ProviderTypeError: + LOGGER.debug('Loading coverage provider') + p = load_plugin('provider', get_provider_by_type( + self.config['resources'][dataset]['providers'], 'coverage')) + except ProviderTypeError: + LOGGER.debug('Loading record provider') + p = load_plugin('provider', get_provider_by_type( + self.config['resources'][dataset]['providers'], 'record')) + except ProviderGenericError as err: + LOGGER.error(err) + return self.get_exception( + err.http_status_code, headers, request.format, + err.ogc_exception_code, err.message) + + schema = { + 'type': 'object', + 'title': l10n.translate( + self.config['resources'][dataset]['title'], request.locale), + 'properties': {}, + '$schema': 'http://json-schema.org/draft/2019-09/schema', + '$id': f'{self.get_collections_url()}/{dataset}/schema' + } + + if p.type != 'coverage': + schema['properties']['geometry'] = { + '$ref': 'https://geojson.org/schema/Geometry.json', + 'x-ogc-role': 'primary-geometry' + } + + for k, v in p.fields.items(): + schema['properties'][k] = v + + if k == p.id_field: + schema['properties'][k]['x-ogc-role'] = 'id' + if k == p.time_field: + schema['properties'][k]['x-ogc-role'] = 'primary-instant' + + if request.format == F_HTML: # render + schema['title'] = l10n.translate( + self.config['resources'][dataset]['title'], request.locale) + + schema['collections_path'] = self.get_collections_url() + + content = render_j2_template(self.tpl_config, + 'collections/schema.html', + schema, request.locale) + + return headers, HTTPStatus.OK, content + + headers['Content-Type'] = 'application/schema+json' + + return headers, HTTPStatus.OK, to_json(schema, self.pretty_print) + @gzip @pre_process @jsonldify @@ -1420,6 +1495,10 @@ def get_collection_queryables(self, request: Union[APIRequest, Any], LOGGER.debug('Loading feature provider') p = load_plugin('provider', get_provider_by_type( self.config['resources'][dataset]['providers'], 'feature')) + except ProviderTypeError: + LOGGER.debug('Loading coverage provider') + p = load_plugin('provider', get_provider_by_type( + self.config['resources'][dataset]['providers'], 'coverage')) except ProviderTypeError: LOGGER.debug('Loading record provider') p = load_plugin('provider', get_provider_by_type( @@ -1441,7 +1520,8 @@ def get_collection_queryables(self, request: Union[APIRequest, Any], if p.fields: queryables['properties']['geometry'] = { - '$ref': 'https://geojson.org/schema/Geometry.json' + '$ref': 'https://geojson.org/schema/Geometry.json', + 'x-ogc-role': 'primary-geometry' } for k, v in p.fields.items(): @@ -1460,6 +1540,11 @@ def get_collection_queryables(self, request: Union[APIRequest, Any], if 'values' in v: queryables['properties'][k]['enum'] = v['values'] + if k == p.id_field: + queryables['properties'][k]['x-ogc-role'] = 'id' + if k == p.time_field: + queryables['properties'][k]['x-ogc-role'] = 'primary-instant' + if request.format == F_HTML: # render queryables['title'] = l10n.translate( self.config['resources'][dataset]['title'], request.locale) @@ -2628,111 +2713,6 @@ def get_collection_coverage(self, request: Union[APIRequest, Any], else: return self.get_format_exception(request) - @gzip - @pre_process - @jsonldify - def get_collection_coverage_domainset( - self, request: Union[APIRequest, Any], - dataset) -> Tuple[dict, int, str]: - """ - Returns a collection coverage domainset - - :param request: A request object - :param dataset: dataset name - - :returns: tuple of headers, status code, content - """ - - format_ = request.format or F_JSON - headers = request.get_response_headers(**self.api_headers) - - LOGGER.debug('Loading provider') - try: - collection_def = get_provider_by_type( - self.config['resources'][dataset]['providers'], 'coverage') - - p = load_plugin('provider', collection_def) - - data = p.get_coverage_domainset() - except KeyError: - msg = 'collection does not exist' - return self.get_exception( - HTTPStatus.NOT_FOUND, headers, format_, - 'InvalidParameterValue', msg) - except ProviderGenericError as err: - LOGGER.error(err) - return self.get_exception( - err.http_status_code, headers, request.format, - err.ogc_exception_code, err.message) - - if format_ == F_JSON: - return headers, HTTPStatus.OK, to_json(data, self.pretty_print) - - elif format_ == F_HTML: - data['id'] = dataset - data['title'] = l10n.translate( - self.config['resources'][dataset]['title'], - self.default_locale) - data['collections_path'] = self.get_collections_url() - content = render_j2_template(self.tpl_config, - 'collections/coverage/domainset.html', - data, self.default_locale) - return headers, HTTPStatus.OK, content - else: - return self.get_format_exception(request) - - @gzip - @pre_process - @jsonldify - def get_collection_coverage_rangetype( - self, request: Union[APIRequest, Any], - dataset) -> Tuple[dict, int, str]: - """ - Returns a collection coverage rangetype - - :param request: A request object - :param dataset: dataset name - - :returns: tuple of headers, status code, content - """ - format_ = request.format or F_JSON - headers = request.get_response_headers(self.default_locale, - **self.api_headers) - LOGGER.debug('Loading provider') - try: - collection_def = get_provider_by_type( - self.config['resources'][dataset]['providers'], 'coverage') - - p = load_plugin('provider', collection_def) - - data = p.get_coverage_rangetype() - except KeyError: - msg = 'collection does not exist' - return self.get_exception( - HTTPStatus.NOT_FOUND, headers, format_, - 'InvalidParameterValue', msg) - except ProviderGenericError as err: - LOGGER.error(err) - return self.get_exception( - err.http_status_code, headers, request.format, - err.ogc_exception_code, err.message) - - if format_ == F_JSON: - return headers, HTTPStatus.OK, to_json(data, self.pretty_print) - - elif format_ == F_HTML: - data['id'] = dataset - data['title'] = l10n.translate( - self.config['resources'][dataset]['title'], - self.default_locale) - data['collections_path'] = self.get_collections_url() - content = render_j2_template(self.tpl_config, - 'collections/coverage/rangetype.html', - data, self.default_locale) - return headers, HTTPStatus.OK, content - else: - return self.get_format_exception(request) - @gzip @pre_process @jsonldify diff --git a/pygeoapi/django_/urls.py b/pygeoapi/django_/urls.py index faf8bf4697..7d254600fd 100644 --- a/pygeoapi/django_/urls.py +++ b/pygeoapi/django_/urls.py @@ -8,7 +8,7 @@ # Copyright (c) 2022 Francesco Bartoli # Copyright (c) 2022 Luca Delucchi # Copyright (c) 2022 Krishna Lodha -# Copyright (c) 2022 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -96,6 +96,11 @@ def apply_slash_rule(url: str): views.collections, name='collection-detail', ), + path( + apply_slash_rule('collections//schema/'), + views.collection_schema, + name='collection-schema', + ), path( apply_slash_rule('collections//queryables/'), views.collection_queryables, @@ -116,16 +121,6 @@ def apply_slash_rule(url: str): views.collection_coverage, name='collection-coverage', ), - path( - apply_slash_rule('collections//coverage/domainset/'), # noqa - views.collection_coverage_domainset, - name='collection-coverage-domainset', - ), - path( - apply_slash_rule('collections//coverage/rangetype/'), # noqa - views.collection_coverage_rangetype, - name='collection-coverage-rangetype', - ), path( 'collections//map', views.collection_map, diff --git a/pygeoapi/django_/views.py b/pygeoapi/django_/views.py index 7f6896297a..95df4f024d 100644 --- a/pygeoapi/django_/views.py +++ b/pygeoapi/django_/views.py @@ -126,6 +126,25 @@ def collections(request: HttpRequest, return response +def collection_schema(request: HttpRequest, + collection_id: Optional[str] = None) -> HttpResponse: + """ + OGC API collections schema endpoint + + :request Django HTTP Request + :param collection_id: collection identifier + + :returns: Django HTTP Response + """ + + response_ = _feed_response( + request, 'get_collection_schema', collection_id + ) + response = _to_django_response(*response_) + + return response + + def collection_queryables(request: HttpRequest, collection_id: Optional[str] = None) -> HttpResponse: """ @@ -268,44 +287,6 @@ def collection_coverage(request: HttpRequest, return response -def collection_coverage_domainset(request: HttpRequest, - collection_id: str) -> HttpResponse: - """ - OGC API - Coverages coverage domainset endpoint - - :request Django HTTP Request - :param collection_id: collection identifier - - :returns: Django HTTP response - """ - - response_ = _feed_response( - request, 'get_collection_coverage_domainset', collection_id - ) - response = _to_django_response(*response_) - - return response - - -def collection_coverage_rangetype(request: HttpRequest, - collection_id: str) -> HttpResponse: - """ - OGC API - Coverages coverage rangetype endpoint - - :request Django HTTP Request - :param collection_id: collection identifier - - :returns: Django HTTP response - """ - - response_ = _feed_response( - request, 'get_collection_coverage_rangetype', collection_id - ) - response = _to_django_response(*response_) - - return response - - def collection_tiles(request: HttpRequest, collection_id: str) -> HttpResponse: """ OGC API - Tiles collection tiles endpoint diff --git a/pygeoapi/flask_app.py b/pygeoapi/flask_app.py index ac5946268f..b966cfc339 100644 --- a/pygeoapi/flask_app.py +++ b/pygeoapi/flask_app.py @@ -3,7 +3,7 @@ # Authors: Tom Kralidis # Norman Barker # -# Copyright (c) 2023 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -189,10 +189,22 @@ def collections(collection_id=None): return get_response(api_.describe_collections(request, collection_id)) +@BLUEPRINT.route('/collections//schema') +def collection_schema(collection_id): + """ + OGC API - collections schema endpoint + + :param collection_id: collection identifier + + :returns: HTTP response + """ + return get_response(api_.get_collection_schema(request, collection_id)) + + @BLUEPRINT.route('/collections//queryables') def collection_queryables(collection_id=None): """ - OGC API collections querybles endpoint + OGC API collections queryables endpoint :param collection_id: collection identifier @@ -263,32 +275,6 @@ def collection_coverage(collection_id): return get_response(api_.get_collection_coverage(request, collection_id)) -@BLUEPRINT.route('/collections//coverage/domainset') -def collection_coverage_domainset(collection_id): - """ - OGC API - Coverages coverage domainset endpoint - - :param collection_id: collection identifier - - :returns: HTTP response - """ - return get_response(api_.get_collection_coverage_domainset( - request, collection_id)) - - -@BLUEPRINT.route('/collections//coverage/rangetype') -def collection_coverage_rangetype(collection_id): - """ - OGC API - Coverages coverage rangetype endpoint - - :param collection_id: collection identifier - - :returns: HTTP response - """ - return get_response(api_.get_collection_coverage_rangetype( - request, collection_id)) - - @BLUEPRINT.route('/collections//tiles') def get_collection_tiles(collection_id=None): """ diff --git a/pygeoapi/openapi.py b/pygeoapi/openapi.py index d697df2064..c71b66bb50 100644 --- a/pygeoapi/openapi.py +++ b/pygeoapi/openapi.py @@ -4,7 +4,7 @@ # Authors: Francesco Bartoli # Authors: Ricardo Garcia Silva # -# Copyright (c) 2023 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # Copyright (c) 2022 Francesco Bartoli # Copyright (c) 2023 Ricardo Garcia Silva # @@ -640,6 +640,27 @@ def get_oas_30(cfg): paths[items_path]['get']['parameters'].append( {'$ref': f"{OPENAPI_YAML['oapir']}/parameters/q.yaml"}) if p.fields: + schema_path = f'{collection_name_path}/schemna' + + paths[schema_path] = { + 'get': { + 'summary': f'Get {title} schema', + 'description': desc, + 'tags': [name], + 'operationId': f'get{name.capitalize()}Queryables', + 'parameters': [ + items_f, + items_l + ], + 'responses': { + '200': {'$ref': '#/components/responses/Queryables'}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"}, # noqa + } + } + } + queryables_path = f'{collection_name_path}/queryables' paths[queryables_path] = { @@ -829,47 +850,6 @@ def get_oas_30(cfg): } } - coverage_domainset_path = f'{collection_name_path}/coverage/domainset' # noqa - - paths[coverage_domainset_path] = { - 'get': { - 'summary': f'Get {title} coverage domain set', - 'description': desc, - 'tags': [name], - 'operationId': f'get{name.capitalize()}CoverageDomainSet', - 'parameters': [ - items_f, - items_l - ], - 'responses': { - '200': {'$ref': f"{OPENAPI_YAML['oacov']}/schemas/cis_1.1/domainSet.yaml"}, # noqa - '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa - '404': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/NotFound"}, # noqa - '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa - } - } - } - - coverage_rangetype_path = f'{collection_name_path}/coverage/rangetype' # noqa - - paths[coverage_rangetype_path] = { - 'get': { - 'summary': f'Get {title} coverage range type', - 'description': desc, - 'tags': [name], - 'operationId': f'get{name.capitalize()}CoverageRangeType', - 'parameters': [ - items_f, - items_l - ], - 'responses': { - '200': {'$ref': f"{OPENAPI_YAML['oacov']}/schemas/cis_1.1/rangeType.yaml"}, # noqa - '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa - '404': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/NotFound"}, # noqa - '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa - } - } - } except ProviderTypeError: LOGGER.debug('collection is not coverage based') diff --git a/pygeoapi/provider/base.py b/pygeoapi/provider/base.py index 4f8728ddaf..6bdc7a2bbd 100644 --- a/pygeoapi/provider/base.py +++ b/pygeoapi/provider/base.py @@ -181,24 +181,6 @@ def delete(self, identifier): raise NotImplementedError() - def get_coverage_domainset(self): - """ - Provide coverage domainset - - :returns: CIS JSON object of domainset metadata - """ - - raise NotImplementedError() - - def get_coverage_rangetype(self): - """ - Provide coverage rangetype - - :returns: CIS JSON object of rangetype metadata - """ - - raise NotImplementedError() - def _load_and_prepare_item(self, item, identifier=None, accept_missing_identifier=False, raise_if_exists=True): diff --git a/pygeoapi/provider/rasterio_.py b/pygeoapi/provider/rasterio_.py index 7414fcc3f8..2bf25e3fbd 100644 --- a/pygeoapi/provider/rasterio_.py +++ b/pygeoapi/provider/rasterio_.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2022 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -59,107 +59,38 @@ def __init__(self, provider_def): self.axes = self._coverage_properties['axes'] self.crs = self._coverage_properties['bbox_crs'] self.num_bands = self._coverage_properties['num_bands'] - self.fields = [str(num) for num in range(1, self.num_bands+1)] + self.fields = self.get_fields() self.native_format = provider_def['format']['name'] except Exception as err: LOGGER.warning(err) raise ProviderConnectionError(err) - def get_coverage_domainset(self, *args, **kwargs): - """ - Provide coverage domainset - :returns: CIS JSON object of domainset metadata - """ + def get_fields(self): + fields = {} - domainset = { - 'type': 'DomainSet', - 'generalGrid': { - 'type': 'GeneralGridCoverage', - 'srsName': self._coverage_properties['bbox_crs'], - 'axisLabels': [ - self._coverage_properties['x_axis_label'], - self._coverage_properties['y_axis_label'] - ], - 'axis': [{ - 'type': 'RegularAxis', - 'axisLabel': self._coverage_properties['x_axis_label'], - 'lowerBound': self._coverage_properties['bbox'][0], - 'upperBound': self._coverage_properties['bbox'][2], - 'uomLabel': self._coverage_properties['bbox_units'], - 'resolution': self._coverage_properties['resx'] - }, { - 'type': 'RegularAxis', - 'axisLabel': self._coverage_properties['y_axis_label'], - 'lowerBound': self._coverage_properties['bbox'][1], - 'upperBound': self._coverage_properties['bbox'][3], - 'uomLabel': self._coverage_properties['bbox_units'], - 'resolution': self._coverage_properties['resy'] - }], - 'gridLimits': { - 'type': 'GridLimits', - 'srsName': 'http://www.opengis.net/def/crs/OGC/0/Index2D', - 'axisLabels': ['i', 'j'], - 'axis': [{ - 'type': 'IndexAxis', - 'axisLabel': 'i', - 'lowerBound': 0, - 'upperBound': self._coverage_properties['width'] - }, { - 'type': 'IndexAxis', - 'axisLabel': 'j', - 'lowerBound': 0, - 'upperBound': self._coverage_properties['height'] - }] - } - }, - '_meta': { - 'tags': self._coverage_properties['tags'] - } - } + for i, dtype in zip(self._data.indexes, self._data.dtypes): + LOGGER.debug(f'Adding field for band {i}') + i2 = str(i) - return domainset + parameter = _get_parameter_metadata( + self._data.profile['driver'], self._data.tags(i)) - def get_coverage_rangetype(self, *args, **kwargs): - """ - Provide coverage rangetype - :returns: CIS JSON object of rangetype metadata - """ + name = parameter['description'] + units = parameter.get('unit_label') - rangetype = { - 'type': 'DataRecord', - 'field': [] - } + dtype2 = dtype + if dtype.startswith('float'): + dtype2 = 'number' - for i, dtype, nodataval in zip(self._data.indexes, self._data.dtypes, - self._data.nodatavals): - LOGGER.debug(f'Determing rangetype for band {i}') - - name, units = None, None - if self._data.units[i-1] is None: - parameter = _get_parameter_metadata( - self._data.profile['driver'], self._data.tags(i)) - name = parameter['description'] - units = parameter['unit_label'] - - rangetype['field'].append({ - 'id': i, - 'type': 'Quantity', - 'name': name, - 'encodingInfo': { - 'dataType': f'http://www.opengis.net/def/dataType/OGC/0/{dtype}' # noqa - }, - 'nodata': nodataval, - 'uom': { - 'id': f'http://www.opengis.net/def/uom/UCUM/{units}', - 'type': 'UnitReference', - 'code': units - }, - '_meta': { - 'tags': self._data.tags(i) - } - }) + fields[i2] = { + 'title': name, + 'type': dtype2, + '_meta': self._data.tags(i) + } + if units is not None: + fields[i2]['x-ogc-unit'] = units - return rangetype + return fields def query(self, properties=[], subsets={}, bbox=None, bbox_crs=4326, datetime_=None, format_='json', **kwargs): diff --git a/pygeoapi/provider/xarray_.py b/pygeoapi/provider/xarray_.py index 4a1f396654..7aaf724b6e 100644 --- a/pygeoapi/provider/xarray_.py +++ b/pygeoapi/provider/xarray_.py @@ -88,118 +88,26 @@ def __init__(self, provider_def): self._coverage_properties['y_axis_label'], self._coverage_properties['time_axis_label']] - self.fields = self._coverage_properties['fields'] + self.fields = self.get_fields() except Exception as err: LOGGER.warning(err) raise ProviderConnectionError(err) - def get_coverage_domainset(self, *args, **kwargs): - """ - Provide coverage domainset + def get_fields(self): + fields = {} - :returns: CIS JSON object of domainset metadata - """ + for key, value in self._data.variables.items(): + if len(value.shape) >= 3: + LOGGER.debug('Adding variable') + dtype = value.dtype + if dtype.startswith('float'): + dtype = 'number' - c_props = self._coverage_properties - domainset = { - 'type': 'DomainSet', - 'generalGrid': { - 'type': 'GeneralGridCoverage', - 'srsName': c_props['bbox_crs'], - 'axisLabels': [ - c_props['x_axis_label'], - c_props['y_axis_label'], - c_props['time_axis_label'] - ], - 'axis': [{ - 'type': 'RegularAxis', - 'axisLabel': c_props['x_axis_label'], - 'lowerBound': c_props['bbox'][0], - 'upperBound': c_props['bbox'][2], - 'uomLabel': c_props['bbox_units'], - 'resolution': c_props['resx'] - }, { - 'type': 'RegularAxis', - 'axisLabel': c_props['y_axis_label'], - 'lowerBound': c_props['bbox'][1], - 'upperBound': c_props['bbox'][3], - 'uomLabel': c_props['bbox_units'], - 'resolution': c_props['resy'] - }, - { - 'type': 'RegularAxis', - 'axisLabel': c_props['time_axis_label'], - 'lowerBound': c_props['time_range'][0], - 'upperBound': c_props['time_range'][1], - 'uomLabel': c_props['restime'], - 'resolution': c_props['restime'] - } - ], - 'gridLimits': { - 'type': 'GridLimits', - 'srsName': 'http://www.opengis.net/def/crs/OGC/0/Index2D', - 'axisLabels': ['i', 'j'], - 'axis': [{ - 'type': 'IndexAxis', - 'axisLabel': 'i', - 'lowerBound': 0, - 'upperBound': c_props['width'] - }, { - 'type': 'IndexAxis', - 'axisLabel': 'j', - 'lowerBound': 0, - 'upperBound': c_props['height'] - }] + fields[key] = { + 'type': dtype, + 'title': value.attrs['long_name'], + 'x-ogc-unit': value.attrs['units'] } - }, - '_meta': { - 'tags': self._data.attrs - } - } - - return domainset - - def get_coverage_rangetype(self, *args, **kwargs): - """ - Provide coverage rangetype - - :returns: CIS JSON object of rangetype metadata - """ - - rangetype = { - 'type': 'DataRecord', - 'field': [] - } - - for name, var in self._data.variables.items(): - LOGGER.debug(f'Determining rangetype for {name}') - - desc, units = None, None - if len(var.shape) >= 3: - parameter = self._get_parameter_metadata( - name, var.attrs) - desc = parameter['description'] - units = parameter['unit_label'] - - rangetype['field'].append({ - 'id': name, - 'type': 'Quantity', - 'name': var.attrs.get('long_name') or desc, - 'encodingInfo': { - 'dataType': f'http://www.opengis.net/def/dataType/OGC/0/{var.dtype}' # noqa - }, - 'nodata': 'null', - 'uom': { - 'id': f'http://www.opengis.net/def/uom/UCUM/{units}', - 'type': 'UnitReference', - 'code': units - }, - '_meta': { - 'tags': var.attrs - } - }) - - return rangetype def query(self, properties=[], subsets={}, bbox=[], bbox_crs=4326, datetime_=None, format_='json', **kwargs): @@ -322,13 +230,13 @@ def query(self, properties=[], subsets={}, bbox=[], bbox_crs=4326, fp.seek(0) return fp.read() - def gen_covjson(self, metadata, data, range_type): + def gen_covjson(self, metadata, data, fields): """ Generate coverage as CoverageJSON representation :param metadata: coverage metadata :param data: rasterio DatasetReader object - :param range_type: range type list + :param fields: fields dict :returns: dict of CoverageJSON representation """ @@ -385,34 +293,31 @@ def gen_covjson(self, metadata, data, range_type): 'ranges': {} } - for variable in range_type: - pm = self._get_parameter_metadata( - variable, self._data[variable].attrs) - + for key, value in fields.items(): parameter = { 'type': 'Parameter', - 'description': pm['description'], + 'description': value['title'], 'unit': { - 'symbol': pm['unit_label'] + 'symbol': value['x-ogc-unit'] }, 'observedProperty': { - 'id': pm['observed_property_id'], + 'id': key, 'label': { - 'en': pm['observed_property_name'] + 'en': value['title'] } } } - cj['parameters'][pm['id']] = parameter + cj['parameters'][key] = parameter data = data.fillna(None) data = _convert_float32_to_float64(data) try: - for key in cj['parameters'].keys(): + for key, value in fields.items(): cj['ranges'][key] = { 'type': 'NdArray', - 'dataType': str(self._data[variable].dtype), + 'dataType': value['type'], 'axisNames': [ 'y', 'x', self._coverage_properties['time_axis_label'] ], @@ -435,6 +340,7 @@ def _get_coverage_properties(self): """ time_var, y_var, x_var = [None, None, None] + for coord in self._data.coords: if coord.lower() == 'time': time_var = coord @@ -503,9 +409,6 @@ def _get_coverage_properties(self): properties['time_axis_label'] ] - properties['fields'] = [name for name in self._data.variables - if len(self._data.variables[name].shape) >= 3] - return properties @staticmethod diff --git a/pygeoapi/provider/xarray_edr.py b/pygeoapi/provider/xarray_edr.py index f3c3e1af8f..7caa56ffe7 100644 --- a/pygeoapi/provider/xarray_edr.py +++ b/pygeoapi/provider/xarray_edr.py @@ -57,15 +57,6 @@ def __init__(self, provider_def): BaseEDRProvider.__init__(self, provider_def) XarrayProvider.__init__(self, provider_def) - def get_fields(self): - """ - Get provider field information (names, types) - - :returns: dict of dicts of parameters - """ - - return self.get_coverage_rangetype() - @BaseEDRProvider.register() def position(self, **kwargs): """ diff --git a/pygeoapi/starlette_app.py b/pygeoapi/starlette_app.py index 29db43e161..7af7a6160b 100644 --- a/pygeoapi/starlette_app.py +++ b/pygeoapi/starlette_app.py @@ -5,7 +5,7 @@ # Abdulazeez Abdulazeez Adeshina # # Copyright (c) 2020 Francesco Bartoli -# Copyright (c) 2022 Tom Kralidis +# Copyright (c) 2024 Tom Kralidis # Copyright (c) 2022 Abdulazeez Abdulazeez Adeshina # # Permission is hereby granted, free of charge, to any person @@ -186,6 +186,22 @@ async def get_tilematrix_sets(request: Request): return await get_response(api_.tilematrixsets, request) +async def collection_schema(request: Request, collection_id=None): + """ + OGC API collections schema endpoint + + :param request: Starlette Request instance + :param collection_id: collection identifier + + :returns: Starlette HTTP Response + """ + if 'collection_id' in request.path_params: + collection_id = request.path_params['collection_id'] + + return await get_response( + api_.get_collection_schema, request, collection_id) + + async def collection_queryables(request: Request, collection_id=None): """ OGC API collections queryables endpoint @@ -197,6 +213,7 @@ async def collection_queryables(request: Request, collection_id=None): """ if 'collection_id' in request.path_params: collection_id = request.path_params['collection_id'] + return await get_response( api_.get_collection_queryables, request, collection_id) @@ -341,39 +358,6 @@ async def collection_coverage(request: Request, collection_id=None): api_.get_collection_coverage, request, collection_id) -async def collection_coverage_domainset(request: Request, collection_id=None): - """ - OGC API - Coverages coverage domainset endpoint - - :param request: Starlette Request instance - :param collection_id: collection identifier - - :returns: Starlette HTTP Response - """ - if 'collection_id' in request.path_params: - collection_id = request.path_params['collection_id'] - - return await get_response( - api_.get_collection_coverage_domainset, request, collection_id) - - -async def collection_coverage_rangetype(request: Request, collection_id=None): - """ - OGC API - Coverages coverage rangetype endpoint - - :param request: Starlette Request instance - :param collection_id: collection identifier - - :returns: Starlette HTTP Response - """ - - if 'collection_id' in request.path_params: - collection_id = request.path_params['collection_id'] - - return await get_response( - api_.get_collection_coverage_rangetype, request, collection_id) - - async def collection_map(request: Request, collection_id, style_id=None): """ OGC API - Maps map render endpoint @@ -637,6 +621,7 @@ async def __call__(self, scope: Scope, Route('/conformance', conformance), Route('/TileMatrixSets/{tileMatrixSetId}', get_tilematrix_set), Route('/TileMatrixSets', get_tilematrix_sets), + Route('/collections/{collection_id:path}/schema', collection_schema), Route('/collections/{collection_id:path}/queryables', collection_queryables), # noqa Route('/collections/{collection_id:path}/tiles', get_collection_tiles), Route('/collections/{collection_id:path}/tiles/{tileMatrixSetId}', get_collection_tiles_metadata), # noqa @@ -645,8 +630,6 @@ async def __call__(self, scope: Scope, Route('/collections/{collection_id:path}/items', collection_items, methods=['GET', 'POST', 'OPTIONS']), # noqa Route('/collections/{collection_id:path}/items/{item_id:path}', collection_items, methods=['GET', 'PUT', 'DELETE', 'OPTIONS']), # noqa Route('/collections/{collection_id:path}/coverage', collection_coverage), # noqa - Route('/collections/{collection_id:path}/coverage/domainset', collection_coverage_domainset), # noqa - Route('/collections/{collection_id:path}/coverage/rangetype', collection_coverage_rangetype), # noqa Route('/collections/{collection_id:path}/map', collection_map), Route('/collections/{collection_id:path}/styles/{style_id:path}/map', collection_map), # noqa Route('/processes', get_processes), diff --git a/pygeoapi/templates/collections/coverage/domainset.html b/pygeoapi/templates/collections/coverage/domainset.html deleted file mode 100644 index 8ca91a4822..0000000000 --- a/pygeoapi/templates/collections/coverage/domainset.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "_base.html" %} -{% block title %}{{ super() }} {{ data['title'] }} {% endblock %} -{% block crumbs %}{{ super() }} -/ {% trans %}Collections{% endtrans %} -/ {{ data['title'] }} -{% endblock %} -{% block body %} -
-

{{ data['title'] }}

-

{{ data['description'] }}

-

{% trans %}Coverage domain set{% endtrans %}

-

{% trans %}Axis labels{% endtrans %}

-
    - {% for al in data['generalGrid']['axisLabels'] %} -
  • {{ al }}
  • - {% endfor %} -
-

{% trans %}Extent{% endtrans %}

-
    -
  • minx: {{ data['generalGrid']['axis'][0]['lowerBound'] }}
  • -
  • miny: {{ data['generalGrid']['axis'][1]['lowerBound'] }}
  • -
  • maxx: {{ data['generalGrid']['axis'][0]['upperBound'] }}
  • -
  • maxy: {{ data['generalGrid']['axis'][1]['upperBound'] }}
  • -
  • {% trans %}Coordinate reference system{% endtrans %}: {{ data['generalGrid']['srsName'] }}
  • -
-

{% trans %}Size{% endtrans %}

-
    -
  • {% trans %}width{% endtrans %}: {{ data['generalGrid']['gridLimits']['axis'][0]['upperBound'] }}
  • -
  • {% trans %}height{% endtrans %}: {{ data['generalGrid']['gridLimits']['axis'][1]['upperBound'] }}
  • -
-

{% trans %}Resolution{% endtrans %}

-
    -
  • {% trans %}x{% endtrans %}: {{ data['generalGrid']['axis'][0]['resolution'] }}
  • -
  • {% trans %}y{% endtrans %}: {{ data['generalGrid']['axis'][1]['resolution'] }}
  • -
-
-{% endblock %} diff --git a/pygeoapi/templates/collections/coverage/rangetype.html b/pygeoapi/templates/collections/coverage/rangetype.html deleted file mode 100644 index d047594a1c..0000000000 --- a/pygeoapi/templates/collections/coverage/rangetype.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "_base.html" %} -{% block title %}{{ super() }} {{ data['title'] }} {% endblock %} -{% block crumbs %}{{ super() }} -/ {% trans %}Collections{% endtrans %} -/ {{ data['title'] }} -{% endblock %} -{% block body %} -
-

{{ data['title'] }}

-

{{ data['description'] }}

-

{% trans %}Coverage range type{% endtrans %}

-

{% trans %}Fields{% endtrans %}

-
    - {% for field in data['field'] %} -
  • {{ field['id'] }}: {{ field['name'] }} ({{ field['definition'] }})
  • - {% endfor %} -
-
-{% endblock %} diff --git a/pygeoapi/templates/collections/schema.html b/pygeoapi/templates/collections/schema.html new file mode 100644 index 0000000000..908c60ddd4 --- /dev/null +++ b/pygeoapi/templates/collections/schema.html @@ -0,0 +1,46 @@ +{% extends "_base.html" %} +{% block title %}{{ super() }} {{ data['title'] }} {% endblock %} +{% block crumbs %}{{ super() }} +/ {% trans %}Collections{% endtrans %} +/ {{ data['title'] | truncate( 25 ) }} +/ {% trans %}Schema{% endtrans %} +{% endblock %} +{% block body %} +
+

{{ data['title'] }}

+

{{ data['description'] }}

+

+ {% for kw in data['keywords'] %} + {{ kw }} + {% endfor %} +

+

{% trans %}Schema{% endtrans %}

+ + + + + + +
    + {% for qname, qinfo in data['properties'].items() %} +
+ + + {% if qname == 'geometry' %} + + {% else %} + + {% endif %} + + + + {% endfor %} +
NameTitleTypeUnitsValues
{{ qname }}{{ qinfo['title'] }}{{ qname }} {{ qinfo['type'] }}{{ qinfo['x-ogc-unit'] }} +
    + {% for value in qinfo['enum'] %} +
  • {{ value }}
  • + {% endfor %} +
+
+
+{% endblock %} diff --git a/tests/test_api.py b/tests/test_api.py index 6732d27470..b8c3a1c936 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -752,6 +752,10 @@ def test_describe_collections(config, api_): assert collection['id'] == 'gdps-temperature' assert len(collection['links']) == 14 + assert collection['extent']['spatial']['grid'][0]['cellsCount'] == 2400 + assert collection['extent']['spatial']['grid'][0]['resolution'] == 0.15000000000000002 # noqa + assert collection['extent']['spatial']['grid'][1]['cellsCount'] == 1201 + assert collection['extent']['spatial']['grid'][0]['resolution'] == 0.15 # hiearchical collections rsp_headers, code, response = api_.describe_collections( @@ -787,6 +791,35 @@ def test_describe_collections_hidden_resources( assert len(collections['collections']) == 1 +def test_get_collection_schema(config, api_): + req = mock_request() + rsp_headers, code, response = api_.get_collection_schema(req, + 'notfound') + assert code == HTTPStatus.NOT_FOUND + + req = mock_request({'f': 'html'}) + rsp_headers, code, response = api_.get_collection_queryables(req, 'obs') + assert rsp_headers['Content-Type'] == FORMAT_TYPES[F_HTML] + + req = mock_request({'f': 'json'}) + rsp_headers, code, response = api_.get_collection_queryables(req, 'obs') + assert rsp_headers['Content-Type'] == 'application/schema+json' + queryables = json.loads(response) + + assert 'properties' in queryables + assert len(queryables['properties']) == 5 + + req = mock_request({'f': 'json'}) + rsp_headers, code, response = api_.get_collection_queryables( + req, 'gdps-temperature') + assert rsp_headers['Content-Type'] == 'application/schema+json' + queryables = json.loads(response) + + assert 'properties' in queryables + assert len(queryables['properties']) == 1 + assert queryables['properties'][0]['type'] == 'number' + + def test_get_collection_queryables(config, api_): req = mock_request() rsp_headers, code, response = api_.get_collection_queryables(req, @@ -1428,44 +1461,6 @@ def test_get_collection_item_json_ld(config, api_): assert rsp_headers['Content-Language'] == 'fr-CA' -def test_get_coverage_domainset(config, api_): - req = mock_request() - rsp_headers, code, response = api_.get_collection_coverage_domainset( - req, 'obs') - - assert code == HTTPStatus.BAD_REQUEST - - rsp_headers, code, response = api_.get_collection_coverage_domainset( - req, 'gdps-temperature') - - domainset = json.loads(response) - - assert domainset['type'] == 'DomainSet' - assert domainset['generalGrid']['axisLabels'] == ['Long', 'Lat'] - assert domainset['generalGrid']['gridLimits']['axisLabels'] == ['i', 'j'] - assert domainset['generalGrid']['gridLimits']['axis'][0]['upperBound'] == 2400 # noqa - assert domainset['generalGrid']['gridLimits']['axis'][1]['upperBound'] == 1201 # noqa - - -def test_get_collection_coverage_rangetype(config, api_): - req = mock_request() - rsp_headers, code, response = api_.get_collection_coverage_rangetype( - req, 'obs') - - assert code == HTTPStatus.BAD_REQUEST - - rsp_headers, code, response = api_.get_collection_coverage_rangetype( - req, 'gdps-temperature') - - rangetype = json.loads(response) - - assert rangetype['type'] == 'DataRecord' - assert len(rangetype['field']) == 1 - assert rangetype['field'][0]['id'] == 1 - assert rangetype['field'][0]['name'] == 'Temperature [C]' - assert rangetype['field'][0]['uom']['code'] == '[C]' - - def test_get_collection_coverage(config, api_): req = mock_request() rsp_headers, code, response = api_.get_collection_coverage( diff --git a/tests/test_rasterio_provider.py b/tests/test_rasterio_provider.py index f1b4471e4e..e0dd1f2ac1 100644 --- a/tests/test_rasterio_provider.py +++ b/tests/test_rasterio_provider.py @@ -61,24 +61,12 @@ def test_provider(config): assert p.axes == ['Long', 'Lat'] -def test_domainset(config): +def test_schema(config): p = RasterioProvider(config) - domainset = p.get_coverage_domainset() - assert isinstance(domainset, dict) - assert domainset['generalGrid']['axisLabels'] == ['Long', 'Lat'] - assert domainset['generalGrid']['gridLimits']['axisLabels'] == ['i', 'j'] - assert domainset['generalGrid']['gridLimits']['axis'][0]['upperBound'] == 2400 # noqa - assert domainset['generalGrid']['gridLimits']['axis'][1]['upperBound'] == 1201 # noqa - - -def test_rangetype(config): - p = RasterioProvider(config) - rangetype = p.get_coverage_rangetype() - - assert isinstance(rangetype, dict) - assert len(rangetype['field']) == 1 - assert rangetype['field'][0]['name'] == 'Temperature [C]' + assert isinstance(p.fields, dict) + assert len(p.fields) == 1 + assert p.fields['1']['title'] == 'Temperature [C]' def test_query(config): diff --git a/tests/test_xarray_netcdf_provider.py b/tests/test_xarray_netcdf_provider.py index f45b2df577..58ce02d918 100644 --- a/tests/test_xarray_netcdf_provider.py +++ b/tests/test_xarray_netcdf_provider.py @@ -58,24 +58,12 @@ def test_provider(config): assert p.axes == ['COADSX', 'COADSY', 'TIME'] -def test_domainset(config): - p = XarrayProvider(config) - domainset = p.get_coverage_domainset() - - assert isinstance(domainset, dict) - assert domainset['generalGrid']['axisLabels'] == ['COADSX', 'COADSY', 'TIME'] # noqa - assert domainset['generalGrid']['gridLimits']['axisLabels'] == ['i', 'j'] - assert domainset['generalGrid']['gridLimits']['axis'][0]['upperBound'] == 180 # noqa - assert domainset['generalGrid']['gridLimits']['axis'][1]['upperBound'] == 90 # noqa - - def test_rangetype(config): p = XarrayProvider(config) - rangetype = p.get_coverage_rangetype() - assert isinstance(rangetype, dict) - assert len(rangetype['field']) == 4 - assert rangetype['field'][0]['name'] == 'SEA SURFACE TEMPERATURE' + assert isinstance(p.fields, dict) + assert len(p.fields) == 4 + assert p.fields['SST'] == 'SEA SURFACE TEMPERATURE' def test_query(config): diff --git a/tests/test_xarray_zarr_provider.py b/tests/test_xarray_zarr_provider.py index 6e59906de6..3e9f6199e7 100644 --- a/tests/test_xarray_zarr_provider.py +++ b/tests/test_xarray_zarr_provider.py @@ -61,24 +61,12 @@ def test_provider(config): assert p.axes == ['lon', 'lat', 'time'] -def test_domainset(config): +def test_schema(config): p = XarrayProvider(config) - domainset = p.get_coverage_domainset() - assert isinstance(domainset, dict) - assert domainset['generalGrid']['axisLabels'] == ['lon', 'lat', 'time'] - assert domainset['generalGrid']['gridLimits']['axisLabels'] == ['i', 'j'] - assert domainset['generalGrid']['gridLimits']['axis'][0]['upperBound'] == 101 # noqa - assert domainset['generalGrid']['gridLimits']['axis'][1]['upperBound'] == 101 # noqa - - -def test_rangetype(config): - p = XarrayProvider(config) - rangetype = p.get_coverage_rangetype() - - assert isinstance(rangetype, dict) - assert len(rangetype['field']) == 4 - assert rangetype['field'][0]['name'] == 'analysed sea surface temperature' + assert isinstance(p.fields, dict) + assert len(p.fields) == 4 + assert p.fields['name'] == 'analysed sea surface temperature' def test_query(config):