Coverage for pygeoapi/api/coverages.py: 22%
105 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 08:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 08:15 +0000
1# =================================================================
3# Authors: Tom Kralidis <tomkralidis@gmail.com>
4# Francesco Bartoli <xbartolone@gmail.com>
5# Sander Schaminee <sander.schaminee@geocat.net>
6# John A Stevenson <jostev@bgs.ac.uk>
7# Colin Blackburn <colb@bgs.ac.uk>
8# Ricardo Garcia Silva <ricardo.garcia.silva@geobeyond.it>
9# Bernhard Mallinger <bernhard.mallinger@eox.at>
10#
11# Copyright (c) 2026 Tom Kralidis
12# Copyright (c) 2025 Francesco Bartoli
13# Copyright (c) 2022 John A Stevenson and Colin Blackburn
14# Copyright (c) 2023 Ricardo Garcia Silva
15# Copyright (c) 2024 Bernhard Mallinger
16#
17# Permission is hereby granted, free of charge, to any person
18# obtaining a copy of this software and associated documentation
19# files (the "Software"), to deal in the Software without
20# restriction, including without limitation the rights to use,
21# copy, modify, merge, publish, distribute, sublicense, and/or sell
22# copies of the Software, and to permit persons to whom the
23# Software is furnished to do so, subject to the following
24# conditions:
25#
26# The above copyright notice and this permission notice shall be
27# included in all copies or substantial portions of the Software.
28#
29# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
31# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
33# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
34# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
35# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
36# OTHER DEALINGS IN THE SOFTWARE.
37#
38# =================================================================
40from copy import deepcopy
41import logging
42from http import HTTPStatus
43from typing import Tuple
45from pygeoapi import l10n
46from pygeoapi.formats import F_JSON
47from pygeoapi.openapi import get_oas_30_parameters
48from pygeoapi.plugin import load_plugin
49from pygeoapi.provider.base import ProviderGenericError, ProviderTypeError
50from pygeoapi.provider import get_provider_by_type
51from pygeoapi.util import filter_dict_by_key_value, to_json
53from . import (
54 APIRequest, API, SYSTEM_LOCALE, validate_bbox, validate_datetime,
55 validate_subset
56)
58LOGGER = logging.getLogger(__name__)
60CONFORMANCE_CLASSES = [
61 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/core',
62 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/oas30',
63 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/html',
64 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/geodata-coverage',
65 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/coverage-subset',
66 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/coverage-rangesubset', # noqa
67 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/coverage-bbox',
68 'http://www.opengis.net/spec/ogcapi-coverages-1/1.0/conf/coverage-datetime'
69]
72def get_collection_coverage(
73 api: API, request: APIRequest,
74 dataset: str) -> Tuple[dict, int, str]:
75 """
76 Returns a subset of a collection coverage
78 :param request: A request object
79 :param dataset: dataset name
81 :returns: tuple of headers, status code, content
82 """
84 query_args = {}
85 format_ = request.format or F_JSON
87 # Force response content type and language (en-US only) headers
88 headers = request.get_response_headers(SYSTEM_LOCALE, **api.api_headers)
90 LOGGER.debug('Loading provider')
91 try:
92 collection_def = get_provider_by_type(
93 api.config['resources'][dataset]['providers'], 'coverage')
95 p = load_plugin('provider', collection_def)
96 except KeyError:
97 msg = 'collection does not exist'
98 return api.get_exception(
99 HTTPStatus.NOT_FOUND, headers, format_,
100 'InvalidParameterValue', msg)
101 except ProviderGenericError as err:
102 return api.get_exception(
103 err.http_status_code, headers, request.format,
104 err.ogc_exception_code, err.message)
106 LOGGER.debug('Processing bbox parameter')
108 bbox = request.params.get('bbox')
110 if bbox is None:
111 bbox = []
112 else:
113 try:
114 bbox = validate_bbox(bbox)
115 except ValueError as err:
116 msg = str(err)
117 return api.get_exception(
118 HTTPStatus.INTERNAL_SERVER_ERROR, headers, format_,
119 'InvalidParameterValue', msg)
121 query_args['bbox'] = bbox
123 LOGGER.debug('Processing bbox-crs parameter')
125 bbox_crs = request.params.get('bbox-crs')
126 if bbox_crs is not None:
127 query_args['bbox_crs'] = bbox_crs
129 LOGGER.debug('Processing datetime parameter')
131 datetime_ = request.params.get('datetime')
133 try:
134 datetime_ = validate_datetime(
135 api.config['resources'][dataset]['extents'], datetime_)
136 except ValueError as err:
137 msg = str(err)
138 return api.get_exception(
139 HTTPStatus.BAD_REQUEST, headers, format_,
140 'InvalidParameterValue', msg)
142 query_args['datetime_'] = datetime_
143 query_args['format_'] = format_
145 properties = request.params.get('properties')
146 if properties:
147 LOGGER.debug('Processing properties parameter')
148 query_args['properties'] = [rs for
149 rs in properties.split(',') if rs]
150 LOGGER.debug(f"Fields: {query_args['properties']}")
152 for a in query_args['properties']:
153 if a not in p.fields:
154 msg = 'Invalid field specified'
155 return api.get_exception(
156 HTTPStatus.BAD_REQUEST, headers, format_,
157 'InvalidParameterValue', msg)
159 if 'subset' in request.params:
160 LOGGER.debug('Processing subset parameter')
161 try:
162 subsets = validate_subset(request.params['subset'] or '')
163 except (AttributeError, ValueError) as err:
164 msg = f'Invalid subset: {err}'
165 return api.get_exception(
166 HTTPStatus.BAD_REQUEST, headers, format_,
167 'InvalidParameterValue', msg)
169 if not set(subsets.keys()).issubset(p.axes):
170 msg = 'Invalid axis name'
171 return api.get_exception(
172 HTTPStatus.BAD_REQUEST, headers, format_,
173 'InvalidParameterValue', msg)
175 query_args['subsets'] = subsets
176 LOGGER.debug(f"Subsets: {query_args['subsets']}")
178 LOGGER.debug('Querying coverage')
179 try:
180 data = p.query(**query_args)
181 except ProviderGenericError as err:
182 return api.get_exception(
183 err.http_status_code, headers, request.format,
184 err.ogc_exception_code, err.message)
186 mt = collection_def['format']['name']
187 if format_ == mt: # native format
188 if p.filename is not None:
189 cd = f'attachment; filename="{p.filename}"'
190 headers['Content-Disposition'] = cd
192 headers['Content-Type'] = collection_def['format']['mimetype']
193 return headers, HTTPStatus.OK, data
194 elif format_ == F_JSON:
195 headers['Content-Type'] = 'application/prs.coverage+json'
196 return headers, HTTPStatus.OK, to_json(data, api.pretty_print)
197 else:
198 return api.get_format_exception(request)
201def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, dict]]: # noqa
202 """
203 Get OpenAPI fragments
205 :param cfg: `dict` of configuration
206 :param locale: `str` of locale
208 :returns: `tuple` of `list` of tag objects, and `dict` of path objects
209 """
211 from pygeoapi.openapi import OPENAPI_YAML, get_visible_collections
213 paths = {}
215 collections = filter_dict_by_key_value(cfg['resources'],
216 'type', 'collection')
218 for k, v in get_visible_collections(cfg).items():
219 try:
220 p = load_plugin('provider', get_provider_by_type(
221 collections[k]['providers'], 'coverage'))
222 except ProviderTypeError:
223 LOGGER.debug('collection is not coverage based')
224 continue
226 coverage_path = f'/collections/{k}/coverage'
227 title = l10n.translate(v['title'], locale)
228 description = l10n.translate(v['description'], locale)
230 parameters = get_oas_30_parameters(cfg, locale)
232 coll_properties = deepcopy(parameters)['properties']
233 coll_properties['schema']['items']['enum'] = list(p.fields.keys())
235 paths[coverage_path] = {
236 'get': {
237 'summary': f'Get {title} coverage',
238 'description': description,
239 'tags': [k],
240 'operationId': f'get{k.capitalize()}Coverage',
241 'parameters': [
242 {'$ref': '#/components/parameters/lang'},
243 {'$ref': '#/components/parameters/f'},
244 {'$ref': '#/components/parameters/bbox'},
245 {'$ref': '#/components/parameters/bbox-crs'},
246 {'$ref': f"{OPENAPI_YAML['oacov']}#/components/parameters/subset"}, # noqa
247 coll_properties
248 ],
249 'responses': {
250 '200': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/Features"}, # noqa
251 '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa
252 '404': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/NotFound"}, # noqa
253 '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa
254 }
255 }
256 }
258 return [{'name': 'coverages'}], {'paths': paths}