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

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# ================================================================= 

39 

40from copy import deepcopy 

41from http import HTTPStatus 

42import logging 

43from typing import Tuple 

44import urllib 

45 

46from pyproj.exceptions import CRSError 

47from shapely.errors import ShapelyError 

48from shapely.wkt import loads as shapely_loads 

49 

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) 

63 

64from . import APIRequest, API, validate_datetime, validate_bbox 

65 

66LOGGER = logging.getLogger(__name__) 

67 

68CONFORMANCE_CLASSES = [ 

69 'http://www.opengis.net/spec/ogcapi-edr-1/1.0/conf/core' 

70] 

71 

72 

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 

79 

80 :param request: APIRequest instance with query params 

81 :param dataset: dataset name 

82 

83 :returns: tuple of headers, status code, content 

84 """ 

85 

86 data = { 

87 'instances': [], 

88 'links': [] 

89 } 

90 

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') 

97 

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) 

102 

103 uri = f'{api.get_collections_url()}/{dataset}' 

104 

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) 

113 

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() 

124 

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 } 

149 

150 for qt in p.get_query_types(): 

151 if qt == 'instances': 

152 continue 

153 

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 } 

161 

162 if request.format is not None and request.format == 'json': 

163 data_query['link']['type'] = 'application/vnd.cov+json' 

164 

165 instance_dict['data_queries'][qt] = data_query 

166 

167 data['instances'].append(instance_dict) 

168 

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' 

175 

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 }]) 

186 

187 if request.format == F_HTML: # render 

188 tpl_config = api.get_dataset_templates(dataset) 

189 

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=',') 

197 

198 if instance_id is None: 

199 uri = f'{uri}/instances' 

200 else: 

201 uri = f'{uri}/instances/{instance_id}' 

202 

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() 

209 

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' 

216 

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 }] 

238 

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) 

243 

244 return headers, HTTPStatus.OK, content 

245 

246 

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 

254 

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 

260 

261 :returns: tuple of headers, status code, content 

262 """ 

263 

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') 

268 

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) 

273 

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) 

283 

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) 

289 

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) 

295 

296 LOGGER.debug('Validating requested format') 

297 dataset_formatters = get_dataset_formatters(collections[dataset]) 

298 

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()}) 

304 

305 LOGGER.debug(f'Request format: {request.format}') 

306 

307 if not request.is_valid(dataset_formatters.keys()): 

308 return api.get_format_exception(request) 

309 

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) 

324 

325 LOGGER.debug('Processing query parameters') 

326 

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) 

337 

338 LOGGER.debug('Processing parameter-name parameter') 

339 parameternames = request.params.get('parameter-name') or [] 

340 if isinstance(parameternames, str): 

341 parameternames = parameternames.split(',') 

342 

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)) 

354 

355 LOGGER.debug('Processing coords parameter') 

356 wkt = request.params.get('coords') 

357 

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) 

371 

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') 

377 

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') 

386 

387 LOGGER.debug('Processing z parameter') 

388 try: 

389 z = get_typed_value(request.params.get('z')) 

390 except TypeError: 

391 z = None 

392 

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) 

399 

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)) 

414 

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 ) 

434 

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) 

441 

442 if request.format == F_HTML: # render 

443 tpl_config = api.get_dataset_templates(dataset) 

444 

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=',') 

453 

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() 

458 

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 }] 

474 

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] 

481 

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) 

496 

497 if formatter.attachment: 

498 filename = f'{dataset}.{formatter.extension}' 

499 cd = f'attachment; filename="{filename}"' 

500 headers['Content-Disposition'] = cd 

501 

502 else: 

503 headers['Content-Type'] = 'application/vnd.cov+json' 

504 content = to_json(data, api.pretty_print) 

505 

506 return headers, HTTPStatus.OK, content 

507 

508 

509def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, dict]]: # noqa 

510 """ 

511 Get OpenAPI fragments 

512 

513 :param cfg: `dict` of configuration 

514 :param locale: `str` of locale 

515 

516 :returns: `tuple` of `list` of tag objects, and `dict` of path objects 

517 """ 

518 

519 from pygeoapi.openapi import OPENAPI_YAML, get_visible_collections 

520 

521 LOGGER.debug('setting up edr endpoints') 

522 

523 paths = {} 

524 

525 collections = filter_dict_by_key_value(cfg['resources'], 

526 'type', 'collection') 

527 

528 for k, v in get_visible_collections(cfg).items(): 

529 edr_extension = filter_providers_by_type( 

530 collections[k]['providers'], 'edr') 

531 

532 description = l10n.translate(v['description'], locale) 

533 

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}' 

536 

537 ep = load_plugin('provider', edr_extension) 

538 

539 edr_query_endpoints = [] 

540 

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 }) 

553 

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 } 

576 

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) 

581 

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 ) 

614 

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 } 

647 

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 } 

694 

695 return [{'name': 'edr'}], {'paths': paths}