Coverage for pygeoapi/api/environmental_data_retrieval.py: 10%
254 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# =================================================================
2#
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
41from http import HTTPStatus
42import logging
43from typing import Tuple
44import urllib
46from pyproj.exceptions import CRSError
47from shapely.errors import ShapelyError
48from shapely.wkt import loads as shapely_loads
50from pygeoapi import l10n
51from pygeoapi.api import evaluate_limit
52from pygeoapi.formats import F_COVERAGEJSON, F_HTML, F_JSON, F_JSONLD
53from pygeoapi.formatter.base import FormatterSerializationError
54from pygeoapi.crs import (create_crs_transform_spec, set_content_crs_header)
55from pygeoapi.openapi import get_oas_30_parameters
56from pygeoapi.plugin import load_plugin, PLUGINS
57from pygeoapi.provider import filter_providers_by_type, get_provider_by_type
58from pygeoapi.provider.base import (
59 ProviderGenericError, ProviderItemNotFoundError)
60from pygeoapi.util import (get_dataset_formatters, get_typed_value,
61 render_j2_template, to_json,
62 filter_dict_by_key_value)
64from . import APIRequest, API, validate_datetime, validate_bbox
66LOGGER = logging.getLogger(__name__)
68CONFORMANCE_CLASSES = [
69 'http://www.opengis.net/spec/ogcapi-edr-1/1.0/conf/core'
70]
73def get_collection_edr_instances(api: API, request: APIRequest,
74 dataset: str,
75 instance_id: str | None = None
76 ) -> Tuple[dict, int, str]:
77 """
78 Queries collection EDR instances
80 :param request: APIRequest instance with query params
81 :param dataset: dataset name
83 :returns: tuple of headers, status code, content
84 """
86 data = {
87 'instances': [],
88 'links': []
89 }
91 if not request.is_valid(PLUGINS['formatter'].keys()):
92 return api.get_format_exception(request)
93 headers = request.get_response_headers(api.default_locale,
94 **api.api_headers)
95 collections = filter_dict_by_key_value(api.config['resources'],
96 'type', 'collection')
98 if dataset not in collections.keys():
99 msg = 'Collection not found'
100 return api.get_exception(
101 HTTPStatus.NOT_FOUND, headers, request.format, 'NotFound', msg)
103 uri = f'{api.get_collections_url()}/{dataset}'
105 LOGGER.debug('Loading provider')
106 try:
107 p = load_plugin('provider', get_provider_by_type(
108 collections[dataset]['providers'], 'edr'))
109 except ProviderGenericError as err:
110 return api.get_exception(
111 err.http_status_code, headers, request.format,
112 err.ogc_exception_code, err.message)
114 if instance_id is not None:
115 try:
116 if p.get_instance(instance_id):
117 instances = [instance_id]
118 except ProviderItemNotFoundError:
119 msg = 'Instance not found'
120 return api.get_exception(
121 HTTPStatus.NOT_FOUND, headers, request.format, 'NotFound', msg)
122 else:
123 instances = p.get_instances()
125 for instance in instances:
126 instance_dict = {
127 'id': instance,
128 'links': [{
129 'href': f'{uri}/instances/{instance}?f={F_JSON}',
130 'rel': request.get_linkrel(F_JSON),
131 'type': 'application/json'
132 }, {
133 'href': f'{uri}/instances/{instance}?f={F_HTML}',
134 'rel': request.get_linkrel(F_HTML),
135 'type': 'text/html'
136 }, {
137 'href': f'{uri}?f={F_HTML}',
138 'rel': 'collection',
139 'title': collections[dataset]['title'],
140 'type': 'text/html'
141 }, {
142 'href': f'{uri}?f={F_JSON}',
143 'rel': 'collection',
144 'title': collections[dataset]['title'],
145 'type': 'application/json'
146 }],
147 'data_queries': {}
148 }
150 for qt in p.get_query_types():
151 if qt == 'instances':
152 continue
154 data_query = {
155 'link': {
156 'href': f'{uri}/instances/{instance}/{qt}?f={request.format}', # noqa
157 'rel': 'data',
158 'title': f'{qt} query'
159 }
160 }
162 if request.format is not None and request.format == 'json':
163 data_query['link']['type'] = 'application/vnd.cov+json'
165 instance_dict['data_queries'][qt] = data_query
167 data['instances'].append(instance_dict)
169 if instance_id is not None:
170 data = data['instances'][0]
171 data.pop('instances', None)
172 links_uri = f'{uri}/instances/{instance_id}'
173 else:
174 links_uri = f'{uri}/instances'
176 if instance_id is None:
177 data['links'].extend([{
178 'href': f'{links_uri}?f={F_JSON}',
179 'rel': request.get_linkrel(F_JSON),
180 'type': 'application/json'
181 }, {
182 'href': f'{links_uri}?f={F_HTML}',
183 'rel': request.get_linkrel(F_HTML),
184 'type': 'text/html'
185 }])
187 if request.format == F_HTML: # render
188 tpl_config = api.get_dataset_templates(dataset)
190 serialized_query_params = ''
191 for k, v in request.params.items():
192 if k != 'f':
193 serialized_query_params += '&'
194 serialized_query_params += urllib.parse.quote(k, safe='')
195 serialized_query_params += '='
196 serialized_query_params += urllib.parse.quote(str(v), safe=',')
198 if instance_id is None:
199 uri = f'{uri}/instances'
200 else:
201 uri = f'{uri}/instances/{instance_id}'
203 data['query_type'] = 'instances'
204 data['query_path'] = uri
205 data['title'] = collections[dataset]['title']
206 data['description'] = collections[dataset]['description']
207 data['keywords'] = collections[dataset]['keywords']
208 data['collections_path'] = api.get_collections_url()
210 if instance_id is None:
211 data['dataset_path'] = data['collections_path'] + '/' + uri.split('/')[-2] # noqa
212 template = 'collections/edr/instances.html'
213 else:
214 data['dataset_path'] = data['collections_path'] + '/' + uri.split('/')[-3] # noqa
215 template = 'collections/edr/instance.html'
217 data['links'] = [{
218 'rel': 'collection',
219 'title': collections[dataset]['title'],
220 'href': f"{data['dataset_path']}?f={F_JSON}",
221 'type': 'text/html'
222 }, {
223 'rel': 'collection',
224 'title': collections[dataset]['title'],
225 'href': f"{data['dataset_path']}?f={F_HTML}",
226 'type': 'application/json'
227 }, {
228 'type': 'application/json',
229 'rel': 'alternate',
230 'title': l10n.translate('This document as JSON', request.locale),
231 'href': f'{uri}?f={F_JSON}{serialized_query_params}'
232 }, {
233 'type': 'application/ld+json',
234 'rel': 'alternate',
235 'title': l10n.translate('This document as JSON-LD', request.locale), # noqa
236 'href': f'{uri}?f={F_JSONLD}{serialized_query_params}'
237 }]
239 content = render_j2_template(api.tpl_config, tpl_config, template,
240 data, api.default_locale)
241 else:
242 content = to_json(data, api.pretty_print)
244 return headers, HTTPStatus.OK, content
247def get_collection_edr_query(api: API, request: APIRequest,
248 dataset: str, instance: str,
249 query_type: str,
250 location_id: str | None = None
251 ) -> Tuple[dict, int, str]:
252 """
253 Queries collection EDR
255 :param request: APIRequest instance with query params
256 :param dataset: dataset name
257 :param instance: instance name
258 :param query_type: EDR query type
259 :param location_id: location id of a /location/<location_id> query
261 :returns: tuple of headers, status code, content
262 """
264 headers = request.get_response_headers(api.default_locale,
265 **api.api_headers)
266 collections = filter_dict_by_key_value(api.config['resources'],
267 'type', 'collection')
269 if dataset not in collections.keys():
270 msg = 'Collection not found'
271 return api.get_exception(
272 HTTPStatus.NOT_FOUND, headers, request.format, 'NotFound', msg)
274 LOGGER.debug('Loading provider')
275 try:
276 provider_def = get_provider_by_type(
277 collections[dataset]['providers'], 'edr')
278 p = load_plugin('provider', provider_def)
279 except ProviderGenericError as err:
280 return api.get_exception(
281 err.http_status_code, headers, request.format,
282 err.ogc_exception_code, err.message)
284 if instance is not None and not p.get_instance(instance):
285 msg = 'Invalid instance identifier'
286 return api.get_exception(
287 HTTPStatus.BAD_REQUEST, headers,
288 request.format, 'InvalidParameterValue', msg)
290 if query_type not in p.get_query_types():
291 msg = 'Unsupported query type'
292 return api.get_exception(
293 HTTPStatus.BAD_REQUEST, headers, request.format,
294 'InvalidParameterValue', msg)
296 LOGGER.debug('Validating requested format')
297 dataset_formatters = get_dataset_formatters(collections[dataset])
299 if dataset_formatters:
300 LOGGER.debug(f'Dataset formatters: {dataset_formatters}')
301 request._format = request._get_format(
302 request.get_request_headers(request.headers),
303 {v.f: v.mimetype for v in dataset_formatters.values()})
305 LOGGER.debug(f'Request format: {request.format}')
307 if not request.is_valid(dataset_formatters.keys()):
308 return api.get_format_exception(request)
310 crs_transform_spec = None
311 query_crs_uri = request.params.get('crs')
312 if query_crs_uri is not None:
313 LOGGER.debug('Processing crs parameter')
314 try:
315 crs_transform_spec = create_crs_transform_spec(
316 provider_def, query_crs_uri
317 )
318 except (ValueError, CRSError) as err:
319 msg = str(err)
320 return api.get_exception(
321 HTTPStatus.BAD_REQUEST, headers, request.format,
322 'InvalidParameterValue', msg)
323 set_content_crs_header(headers, provider_def, query_crs_uri)
325 LOGGER.debug('Processing query parameters')
327 LOGGER.debug('Processing datetime parameter')
328 datetime_ = request.params.get('datetime')
329 try:
330 datetime_ = validate_datetime(collections[dataset]['extents'],
331 datetime_)
332 except ValueError as err:
333 msg = str(err)
334 return api.get_exception(
335 HTTPStatus.BAD_REQUEST, headers, request.format,
336 'InvalidParameterValue', msg)
338 LOGGER.debug('Processing parameter-name parameter')
339 parameternames = request.params.get('parameter-name') or []
340 if isinstance(parameternames, str):
341 parameternames = parameternames.split(',')
343 bbox = None
344 if query_type in ['cube', 'locations']:
345 LOGGER.debug('Processing cube bbox')
346 try:
347 bbox = validate_bbox(request.params.get('bbox'))
348 if not bbox and query_type == 'cube':
349 raise ValueError('bbox parameter required by cube queries')
350 except ValueError as err:
351 return api.get_exception(
352 HTTPStatus.BAD_REQUEST, headers, request.format,
353 'InvalidParameterValue', str(err))
355 LOGGER.debug('Processing coords parameter')
356 wkt = request.params.get('coords')
358 if wkt:
359 try:
360 wkt = shapely_loads(wkt)
361 except ShapelyError:
362 msg = 'invalid coords parameter'
363 return api.get_exception(
364 HTTPStatus.BAD_REQUEST, headers, request.format,
365 'InvalidParameterValue', msg)
366 elif query_type not in ['cube', 'locations']:
367 msg = 'missing coords parameter'
368 return api.get_exception(
369 HTTPStatus.BAD_REQUEST, headers, request.format,
370 'InvalidParameterValue', msg)
372 within = within_units = None
373 if query_type == 'radius':
374 LOGGER.debug('Processing within / within-units parameters')
375 within = request.params.get('within')
376 within_units = request.params.get('within-units')
378 corridor_width = width_units = None
379 corridor_height = height_units = None
380 if query_type == 'corridor':
381 LOGGER.debug('Processing corridor width / height / units parameters')
382 corridor_width = request.params.get('corridor-width')
383 width_units = request.params.get('width-units')
384 corridor_height = request.params.get('corridor-height')
385 height_units = request.params.get('height-units')
387 LOGGER.debug('Processing z parameter')
388 try:
389 z = get_typed_value(request.params.get('z'))
390 except TypeError:
391 z = None
393 if parameternames and not any((fld in parameternames)
394 for fld in p.get_fields().keys()):
395 msg = 'Invalid parameter-name'
396 return api.get_exception(
397 HTTPStatus.BAD_REQUEST, headers, request.format,
398 'InvalidParameterValue', msg)
400 LOGGER.debug('Processing limit parameter')
401 if api.config['server'].get('limit') is not None:
402 msg = ('server.limit is no longer supported! '
403 'Please use limits at the server or collection '
404 'level (RFC5)')
405 LOGGER.warning(msg)
406 try:
407 limit = evaluate_limit(request.params.get('limit'),
408 api.config['server'].get('limits', {}),
409 collections[dataset].get('limits', {}))
410 except ValueError as err:
411 return api.get_exception(
412 HTTPStatus.BAD_REQUEST, headers, request.format,
413 'InvalidParameterValue', str(err))
415 query_args = dict(
416 query_type=query_type,
417 instance=instance,
418 format_=request.format,
419 datetime_=datetime_,
420 select_properties=parameternames,
421 wkt=wkt,
422 z=z,
423 bbox=bbox,
424 within=within,
425 within_units=within_units,
426 corridor_width=corridor_width,
427 width_units=width_units,
428 corridor_height=corridor_height,
429 height_units=height_units,
430 limit=limit,
431 location_id=location_id,
432 crs_transform_spec=crs_transform_spec
433 )
435 try:
436 data = p.query(**query_args)
437 except ProviderGenericError as err:
438 return api.get_exception(
439 err.http_status_code, headers, request.format,
440 err.ogc_exception_code, err.message)
442 if request.format == F_HTML: # render
443 tpl_config = api.get_dataset_templates(dataset)
445 uri = f'{api.get_collections_url()}/{dataset}/{query_type}'
446 serialized_query_params = ''
447 for k, v in request.params.items():
448 if k != 'f':
449 serialized_query_params += '&'
450 serialized_query_params += urllib.parse.quote(k, safe='')
451 serialized_query_params += '='
452 serialized_query_params += urllib.parse.quote(str(v), safe=',')
454 data['query_type'] = query_type.capitalize()
455 data['query_path'] = uri
456 data['dataset_path'] = '/'.join(uri.split('/')[:-1])
457 data['collections_path'] = api.get_collections_url()
459 data['links'] = [{
460 'rel': 'collection',
461 'title': collections[dataset]['title'],
462 'href': data['dataset_path']
463 }, {
464 'type': 'application/prs.coverage+json',
465 'rel': request.get_linkrel(F_COVERAGEJSON),
466 'title': l10n.translate('This document as CoverageJSON', request.locale), # noqa
467 'href': f'{uri}?f={F_COVERAGEJSON}{serialized_query_params}'
468 }, {
469 'type': 'application/ld+json',
470 'rel': 'alternate',
471 'title': l10n.translate('This document as JSON-LD', request.locale), # noqa
472 'href': f'{uri}?f={F_JSONLD}{serialized_query_params}'
473 }]
475 content = render_j2_template(api.tpl_config, tpl_config,
476 'collections/edr/query.html', data,
477 api.default_locale)
478 elif request.format in [df.f for df in dataset_formatters.values()]:
479 formatter = [v for v in dataset_formatters.values() if
480 v.f == request.format][0]
482 try:
483 content = formatter.write(
484 data=data,
485 options={
486 'provider_def': get_provider_by_type(
487 collections[dataset]['providers'],
488 'edr')
489 }
490 )
491 except FormatterSerializationError:
492 msg = 'Error serializing output'
493 return api.get_exception(
494 HTTPStatus.INTERNAL_SERVER_ERROR, headers, request.format,
495 'NoApplicableCode', msg)
497 if formatter.attachment:
498 filename = f'{dataset}.{formatter.extension}'
499 cd = f'attachment; filename="{filename}"'
500 headers['Content-Disposition'] = cd
502 else:
503 headers['Content-Type'] = 'application/vnd.cov+json'
504 content = to_json(data, api.pretty_print)
506 return headers, HTTPStatus.OK, content
509def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, dict]]: # noqa
510 """
511 Get OpenAPI fragments
513 :param cfg: `dict` of configuration
514 :param locale: `str` of locale
516 :returns: `tuple` of `list` of tag objects, and `dict` of path objects
517 """
519 from pygeoapi.openapi import OPENAPI_YAML, get_visible_collections
521 LOGGER.debug('setting up edr endpoints')
523 paths = {}
525 collections = filter_dict_by_key_value(cfg['resources'],
526 'type', 'collection')
528 for k, v in get_visible_collections(cfg).items():
529 edr_extension = filter_providers_by_type(
530 collections[k]['providers'], 'edr')
532 description = l10n.translate(v['description'], locale)
534 if edr_extension: 534 ↛ 535line 534 didn't jump to line 535 because the condition on line 534 was never true
535 collection_name_path = f'/collections/{k}'
537 ep = load_plugin('provider', edr_extension)
539 edr_query_endpoints = []
541 for qt in [qt for qt in ep.get_query_types() if qt != 'locations']:
542 edr_query_endpoints.append({
543 'path': f'{collection_name_path}/{qt}',
544 'qt': qt,
545 'op_id': f'query{qt.capitalize()}{k.capitalize()}'
546 })
547 if 'instances' in ep.get_query_types() and qt != 'instances':
548 edr_query_endpoints.append({
549 'path': f'{collection_name_path}/instances/{{instanceId}}/{qt}', # noqa
550 'qt': qt,
551 'op_id': f'query{qt.capitalize()}Instance{k.capitalize()}' # noqa
552 })
554 for eqe in edr_query_endpoints:
555 if eqe['qt'] == 'cube':
556 spatial_parameter = {
557 'description': 'Only features that have a geometry that intersects the bounding box are selected.The bounding box is provided as four or six numbers, depending on whether the coordinate reference system includes a vertical axis (height or depth).', # noqa
558 'explode': False,
559 'in': 'query',
560 'name': 'bbox',
561 'required': True,
562 'schema': {
563 'items': {
564 'type': 'number'
565 },
566 'maxItems': 6,
567 'minItems': 4,
568 'type': 'array'
569 },
570 'style': 'form'
571 }
572 else:
573 spatial_parameter = {
574 '$ref': f"{OPENAPI_YAML['oaedr']}/parameters/{eqe['qt']}Coords.yaml" # noqa
575 }
577 dataset_formatters = get_dataset_formatters(v)
578 coll_f_parameter = deepcopy(get_oas_30_parameters(cfg, locale))['f'] # noqa
579 for key, value in dataset_formatters.items():
580 coll_f_parameter['schema']['enum'].append(value.f)
582 paths[eqe['path']] = {
583 'get': {
584 'summary': f"query {description} by {eqe['qt']}",
585 'description': description,
586 'tags': [k],
587 'operationId': eqe['op_id'],
588 'parameters': [
589 spatial_parameter,
590 {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
591 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/parameter-name.yaml"}, # noqa
592 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/z.yaml"}, # noqa
593 {'$ref': '#/components/parameters/crs'},
594 coll_f_parameter,
595 ],
596 'responses': {
597 '200': {
598 'description': 'Response',
599 'content': {
600 'application/prs.coverage+json': {
601 'schema': {
602 '$ref': f"{OPENAPI_YAML['oaedr']}/schemas/coverageJSON.yaml" # noqa
603 }
604 }
605 }
606 }
607 }
608 }
609 }
610 if 'instanceId' in eqe['path']:
611 paths[eqe['path']]['get']['parameters'].insert(0, {
612 '$ref': f"{OPENAPI_YAML['oaedr']}/parameters/instanceId.yaml"} # noqa
613 )
615 if 'instances' in ep.get_query_types():
616 paths[f'{collection_name_path}/instances'] = {
617 'get': {
618 'summary': f"Get pre-defined instances of {description}", # noqa
619 'description': description,
620 'tags': [k],
621 'operationId': f'getInstances{k.capitalize()}',
622 'parameters': [
623 {'$ref': '#/components/parameters/f'}
624 ],
625 'responses': {
626 '200': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/Features"}, # noqa
627 '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa
628 '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa
629 }
630 }
631 }
632 paths[f'{collection_name_path}/instances/{{instanceId}}'] = {
633 'get': {
634 'summary': f"Get {description} instance",
635 'description': description,
636 'tags': [k],
637 'operationId': f'getInstance{k.capitalize()}',
638 'parameters': [
639 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/instanceId.yaml"}, # noqa
640 {'$ref': '#/components/parameters/f'}
641 ],
642 'responses': {
643 '200': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/Features"}, # noqa
644 }
645 }
646 }
648 if 'locations' in ep.get_query_types():
649 paths[f'{collection_name_path}/locations'] = {
650 'get': {
651 'summary': f"Get pre-defined locations of {description}", # noqa
652 'description': description,
653 'tags': [k],
654 'operationId': f'getLocations{k.capitalize()}',
655 'parameters': [
656 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/bbox.yaml"}, # noqa
657 {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
658 {'$ref': '#/components/parameters/f'}
659 ],
660 'responses': {
661 '200': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/Features"}, # noqa
662 '400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa
663 '500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa
664 }
665 }
666 }
667 paths[f'{collection_name_path}/locations/{{locId}}'] = {
668 'get': {
669 'summary': f"query {description} by location",
670 'description': description,
671 'tags': [k],
672 'operationId': f'getLocation{k.capitalize()}',
673 'parameters': [
674 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/locationId.yaml"}, # noqa
675 {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
676 {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/parameter-name.yaml"}, # noqa
677 {'$ref': '#/components/parameters/crs'},
678 {'$ref': '#/components/parameters/f'}
679 ],
680 'responses': {
681 '200': {
682 'description': 'Response',
683 'content': {
684 'application/prs.coverage+json': {
685 'schema': {
686 '$ref': f"{OPENAPI_YAML['oaedr']}/schemas/coverageJSON.yaml" # noqa
687 }
688 }
689 }
690 }
691 }
692 }
693 }
695 return [{'name': 'edr'}], {'paths': paths}