4e77d75ea3
* Remove extra parameters from OAS-EDR for locations * Revert "Remove extra parameters from OAS-EDR for locations" This reverts commit cd84a3ce5ebdaea0e8b90f20f2ec63bb027d10c7.
362 lines
15 KiB
Python
362 lines
15 KiB
Python
# =================================================================
|
|
|
|
# Authors: Tom Kralidis <tomkralidis@gmail.com>
|
|
# Francesco Bartoli <xbartolone@gmail.com>
|
|
# Sander Schaminee <sander.schaminee@geocat.net>
|
|
# John A Stevenson <jostev@bgs.ac.uk>
|
|
# Colin Blackburn <colb@bgs.ac.uk>
|
|
# Ricardo Garcia Silva <ricardo.garcia.silva@geobeyond.it>
|
|
# Bernhard Mallinger <bernhard.mallinger@eox.at>
|
|
#
|
|
# 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
|
|
# Copyright (c) 2024 Bernhard Mallinger
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation
|
|
# files (the "Software"), to deal in the Software without
|
|
# restriction, including without limitation the rights to use,
|
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following
|
|
# conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
# OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
# =================================================================
|
|
|
|
|
|
from http import HTTPStatus
|
|
import logging
|
|
from typing import Tuple
|
|
import urllib
|
|
|
|
from shapely.errors import ShapelyError
|
|
from shapely.wkt import loads as shapely_loads
|
|
|
|
from pygeoapi import l10n
|
|
from pygeoapi.plugin import load_plugin, PLUGINS
|
|
from pygeoapi.provider.base import ProviderGenericError
|
|
from pygeoapi.util import (
|
|
filter_providers_by_type, get_provider_by_type, render_j2_template,
|
|
to_json, filter_dict_by_key_value
|
|
)
|
|
|
|
from . import (APIRequest, API, F_COVERAGEJSON, F_HTML, F_JSONLD,
|
|
validate_datetime, validate_bbox)
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
CONFORMANCE_CLASSES = [
|
|
'http://www.opengis.net/spec/ogcapi-edr-1/1.0/conf/core'
|
|
]
|
|
|
|
|
|
def get_collection_edr_query(api: API, request: APIRequest,
|
|
dataset, instance, query_type,
|
|
location_id=None) -> Tuple[dict, int, str]:
|
|
"""
|
|
Queries collection EDR
|
|
|
|
:param request: APIRequest instance with query params
|
|
:param dataset: dataset name
|
|
:param instance: instance name
|
|
:param query_type: EDR query type
|
|
:param location_id: location id of a /location/<location_id> query
|
|
|
|
:returns: tuple of headers, status code, content
|
|
"""
|
|
|
|
if not request.is_valid(PLUGINS['formatter'].keys()):
|
|
return api.get_format_exception(request)
|
|
headers = request.get_response_headers(api.default_locale,
|
|
**api.api_headers)
|
|
collections = filter_dict_by_key_value(api.config['resources'],
|
|
'type', 'collection')
|
|
|
|
if dataset not in collections.keys():
|
|
msg = 'Collection not found'
|
|
return api.get_exception(
|
|
HTTPStatus.NOT_FOUND, headers, request.format, 'NotFound', msg)
|
|
|
|
LOGGER.debug('Processing query parameters')
|
|
|
|
LOGGER.debug('Processing datetime parameter')
|
|
datetime_ = request.params.get('datetime')
|
|
try:
|
|
datetime_ = validate_datetime(collections[dataset]['extents'],
|
|
datetime_)
|
|
except ValueError as err:
|
|
msg = str(err)
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', msg)
|
|
|
|
LOGGER.debug('Processing parameter-name parameter')
|
|
parameternames = request.params.get('parameter-name') or []
|
|
if isinstance(parameternames, str):
|
|
parameternames = parameternames.split(',')
|
|
|
|
bbox = None
|
|
if query_type in ['cube', 'locations']:
|
|
LOGGER.debug('Processing cube bbox')
|
|
try:
|
|
bbox = validate_bbox(request.params.get('bbox'))
|
|
if not bbox and query_type == 'cube':
|
|
raise ValueError('bbox parameter required by cube queries')
|
|
except ValueError as err:
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', str(err))
|
|
|
|
LOGGER.debug('Processing coords parameter')
|
|
wkt = request.params.get('coords')
|
|
|
|
if wkt:
|
|
try:
|
|
wkt = shapely_loads(wkt)
|
|
except ShapelyError:
|
|
msg = 'invalid coords parameter'
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', msg)
|
|
elif query_type not in ['cube', 'locations']:
|
|
msg = 'missing coords parameter'
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', msg)
|
|
|
|
within = within_units = None
|
|
if query_type == 'radius':
|
|
LOGGER.debug('Processing within / within-units parameters')
|
|
within = request.params.get('within')
|
|
within_units = request.params.get('within-units')
|
|
|
|
LOGGER.debug('Processing z parameter')
|
|
z = request.params.get('z')
|
|
|
|
LOGGER.debug('Loading provider')
|
|
try:
|
|
p = load_plugin('provider', get_provider_by_type(
|
|
collections[dataset]['providers'], 'edr'))
|
|
except ProviderGenericError as err:
|
|
return api.get_exception(
|
|
err.http_status_code, headers, request.format,
|
|
err.ogc_exception_code, err.message)
|
|
|
|
if instance is not None and not p.get_instance(instance):
|
|
msg = 'Invalid instance identifier'
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers,
|
|
request.format, 'InvalidParameterValue', msg)
|
|
|
|
if query_type not in p.get_query_types():
|
|
msg = 'Unsupported query type'
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', msg)
|
|
|
|
if parameternames and not any((fld in parameternames)
|
|
for fld in p.get_fields().keys()):
|
|
msg = 'Invalid parameter-name'
|
|
return api.get_exception(
|
|
HTTPStatus.BAD_REQUEST, headers, request.format,
|
|
'InvalidParameterValue', msg)
|
|
|
|
query_args = dict(
|
|
query_type=query_type,
|
|
instance=instance,
|
|
format_=request.format,
|
|
datetime_=datetime_,
|
|
select_properties=parameternames,
|
|
wkt=wkt,
|
|
z=z,
|
|
bbox=bbox,
|
|
within=within,
|
|
within_units=within_units,
|
|
limit=int(api.config['server']['limit']),
|
|
location_id=location_id,
|
|
)
|
|
|
|
try:
|
|
data = p.query(**query_args)
|
|
except ProviderGenericError as err:
|
|
return api.get_exception(
|
|
err.http_status_code, headers, request.format,
|
|
err.ogc_exception_code, err.message)
|
|
|
|
if request.format == F_HTML: # render
|
|
uri = f'{api.get_collections_url()}/{dataset}/{query_type}'
|
|
serialized_query_params = ''
|
|
for k, v in request.params.items():
|
|
if k != 'f':
|
|
serialized_query_params += '&'
|
|
serialized_query_params += urllib.parse.quote(k, safe='')
|
|
serialized_query_params += '='
|
|
serialized_query_params += urllib.parse.quote(str(v), safe=',')
|
|
|
|
data['query_type'] = query_type.capitalize()
|
|
data['query_path'] = uri
|
|
data['dataset_path'] = '/'.join(uri.split('/')[:-1])
|
|
data['collections_path'] = api.get_collections_url()
|
|
|
|
data['links'] = [{
|
|
'rel': 'collection',
|
|
'title': collections[dataset]['title'],
|
|
'href': data['dataset_path']
|
|
}, {
|
|
'type': 'application/prs.coverage+json',
|
|
'rel': request.get_linkrel(F_COVERAGEJSON),
|
|
'title': l10n.translate('This document as CoverageJSON', request.locale), # noqa
|
|
'href': f'{uri}?f={F_COVERAGEJSON}{serialized_query_params}'
|
|
}, {
|
|
'type': 'application/ld+json',
|
|
'rel': 'alternate',
|
|
'title': l10n.translate('This document as JSON-LD', request.locale), # noqa
|
|
'href': f'{uri}?f={F_JSONLD}{serialized_query_params}'
|
|
}]
|
|
|
|
content = render_j2_template(api.tpl_config,
|
|
'collections/edr/query.html', data,
|
|
api.default_locale)
|
|
else:
|
|
content = to_json(data, api.pretty_print)
|
|
|
|
return headers, HTTPStatus.OK, content
|
|
|
|
|
|
def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, dict]]: # noqa
|
|
"""
|
|
Get OpenAPI fragments
|
|
|
|
:param cfg: `dict` of configuration
|
|
:param locale: `str` of locale
|
|
|
|
:returns: `tuple` of `list` of tag objects, and `dict` of path objects
|
|
"""
|
|
|
|
from pygeoapi.openapi import OPENAPI_YAML, get_visible_collections
|
|
|
|
LOGGER.debug('setting up edr endpoints')
|
|
|
|
paths = {}
|
|
|
|
collections = filter_dict_by_key_value(cfg['resources'],
|
|
'type', 'collection')
|
|
|
|
for k, v in get_visible_collections(cfg).items():
|
|
edr_extension = filter_providers_by_type(
|
|
collections[k]['providers'], 'edr')
|
|
|
|
if edr_extension:
|
|
collection_name_path = f'/collections/{k}'
|
|
|
|
ep = load_plugin('provider', edr_extension)
|
|
|
|
edr_query_endpoints = []
|
|
|
|
for qt in [qt for qt in ep.get_query_types() if qt != 'locations']:
|
|
edr_query_endpoints.append({
|
|
'path': f'{collection_name_path}/{qt}',
|
|
'qt': qt,
|
|
'op_id': f'query{qt.capitalize()}{k.capitalize()}'
|
|
})
|
|
if ep.instances:
|
|
edr_query_endpoints.append({
|
|
'path': f'{collection_name_path}/instances/{{instanceId}}/{qt}', # noqa
|
|
'qt': qt,
|
|
'op_id': f'query{qt.capitalize()}Instance{k.capitalize()}' # noqa
|
|
})
|
|
|
|
for eqe in edr_query_endpoints:
|
|
if eqe['qt'] == 'cube':
|
|
spatial_parameter = 'bbox'
|
|
else:
|
|
spatial_parameter = f"{eqe['qt']}Coords"
|
|
paths[eqe['path']] = {
|
|
'get': {
|
|
'summary': f"query {v['description']} by {eqe['qt']}",
|
|
'description': v['description'],
|
|
'tags': [k],
|
|
'operationId': eqe['op_id'],
|
|
'parameters': [
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/{spatial_parameter}.yaml"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/parameter-name.yaml"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/z.yaml"}, # noqa
|
|
{'$ref': '#/components/parameters/f'}
|
|
],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Response',
|
|
'content': {
|
|
'application/prs.coverage+json': {
|
|
'schema': {
|
|
'$ref': f"{OPENAPI_YAML['oaedr']}/schemas/coverageJSON.yaml" # noqa
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if 'locations' in ep.get_query_types():
|
|
paths[f'{collection_name_path}/locations'] = {
|
|
'get': {
|
|
'summary': f"Get pre-defined locations of {v['description']}", # noqa
|
|
'description': v['description'],
|
|
'tags': [k],
|
|
'operationId': f'queryLOCATIONS{k.capitalize()}',
|
|
'parameters': [
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/bbox.yaml"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
|
|
{'$ref': '#/components/parameters/f'}
|
|
],
|
|
'responses': {
|
|
'200': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/Features"}, # noqa
|
|
'400': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/InvalidParameter"}, # noqa
|
|
'500': {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/responses/ServerError"} # noqa
|
|
}
|
|
}
|
|
}
|
|
paths[f'{collection_name_path}/locations/{{locId}}'] = {
|
|
'get': {
|
|
'summary': f"query {v['description']} by location",
|
|
'description': v['description'],
|
|
'tags': [k],
|
|
'operationId': f'queryLOCATIONSBYID{k.capitalize()}',
|
|
'parameters': [
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/locationId.yaml"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
|
|
{'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/parameter-name.yaml"}, # noqa
|
|
{'$ref': '#/components/parameters/f'}
|
|
],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Response',
|
|
'content': {
|
|
'application/prs.coverage+json': {
|
|
'schema': {
|
|
'$ref': f"{OPENAPI_YAML['oaedr']}/schemas/coverageJSON.yaml" # noqa
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [{'name': 'edr'}], {'paths': paths}
|