Hierarchical collections (#885)
* add support for hierarchical collections * fix JSON rendering in docs * fix JSON rendering in docs * fix tests * update docs * update HTML templates with collections path * fix template error * add test
This commit is contained in:
@@ -119,7 +119,8 @@ The ``metadata`` section provides settings for overall service metadata and desc
|
||||
``resources``
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The ``resources`` section lists 1 or more dataset collections to be published by the server.
|
||||
The ``resources`` section lists 1 or more dataset collections to be published by the server. The
|
||||
key of the resource name is the advertised collection identifier.
|
||||
|
||||
The ``resource.type`` property is required. Allowed types are:
|
||||
|
||||
@@ -228,6 +229,45 @@ Below is an example of how to integrate system environment variables in pygeoapi
|
||||
port: ${MY_PORT}
|
||||
|
||||
|
||||
Hierarchical collections
|
||||
------------------------
|
||||
|
||||
Collections defined in the the ``resources`` section are identified by the resource key. The
|
||||
key of the resource name is the advertised collection identifier. For example, given the following:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
resources:
|
||||
lakes:
|
||||
...
|
||||
|
||||
|
||||
The resulting collection will be made available at http://localhost:5000/collections/lakes
|
||||
|
||||
All collections are published by default to http://localhost:5000/collections. To enable
|
||||
hierarchical collections, provide the hierarchy in the resource key. Given the following:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
resources:
|
||||
naturalearth/lakes:
|
||||
...
|
||||
|
||||
The resulting collection will then be made available at http://localhost:5000/collections/naturalearth/lakes
|
||||
|
||||
.. note::
|
||||
|
||||
This functionality may change in the future given how hierarchical collection extension specifications
|
||||
evolve at OGC.
|
||||
|
||||
.. note::
|
||||
|
||||
Collection grouping is not available. This means that while URLs such as http://localhost:5000/collections/naturalearth/lakes
|
||||
function as expected, URLs such as http://localhost:5000/collections/naturalearth will not provide
|
||||
aggregate collection listing or querying. This functionality is also to be determined based on
|
||||
the evolution of hierarchical collection extension specifications at OGC.
|
||||
|
||||
|
||||
Linked Data
|
||||
-----------
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ File examples
|
||||
"href": "./eo4ce/catalog.json",
|
||||
"type": "application/json"
|
||||
},
|
||||
...
|
||||
{
|
||||
"rel": "child",
|
||||
"href": "./dem/catalog.json",
|
||||
@@ -147,7 +146,6 @@ Collections are similar to Catalogs with extra fields.
|
||||
"href": "./arcticdem-frontiere-0/arcticdem-frontiere-0.json",
|
||||
"type": "application/json"
|
||||
},
|
||||
...
|
||||
{
|
||||
"rel": "item",
|
||||
"href": "./arcticdem-frontiere-9/arcticdem-frontiere-9.json",
|
||||
|
||||
+75
-64
@@ -678,7 +678,7 @@ class API:
|
||||
'rel': 'data',
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'title': 'Collections',
|
||||
'href': '{}/collections'.format(self.config['server']['url'])
|
||||
'href': self.get_collections_url()
|
||||
}, {
|
||||
'rel': 'http://www.opengis.net/def/rel/ogc/1.0/processes',
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
@@ -878,22 +878,22 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': request.get_linkrel(F_JSON),
|
||||
'title': 'This document as JSON',
|
||||
'href': '{}/collections/{}?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_JSONLD],
|
||||
'rel': request.get_linkrel(F_JSONLD),
|
||||
'title': 'This document as RDF (JSON-LD)',
|
||||
'href': '{}/collections/{}?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSONLD)
|
||||
'href': '{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSONLD)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': request.get_linkrel(F_HTML),
|
||||
'title': 'This document as HTML',
|
||||
'href': '{}/collections/{}?f={}'.format(
|
||||
self.config['server']['url'], k, F_HTML)
|
||||
'href': '{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, F_HTML)
|
||||
})
|
||||
|
||||
if collection_data_type in ['feature', 'record', 'tile']:
|
||||
@@ -904,36 +904,36 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': 'queryables',
|
||||
'title': 'Queryables for this collection as JSON',
|
||||
'href': '{}/collections/{}/queryables?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}/queryables?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': 'queryables',
|
||||
'title': 'Queryables for this collection as HTML',
|
||||
'href': '{}/collections/{}/queryables?f={}'.format(
|
||||
self.config['server']['url'], k, F_HTML)
|
||||
'href': '{}/{}queryables?f={}'.format(
|
||||
self.get_collections_url(), k, F_HTML)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': 'application/geo+json',
|
||||
'rel': 'items',
|
||||
'title': 'items as GeoJSON',
|
||||
'href': '{}/collections/{}/items?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}/items?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_JSONLD],
|
||||
'rel': 'items',
|
||||
'title': 'items as RDF (GeoJSON-LD)',
|
||||
'href': '{}/collections/{}/items?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSONLD)
|
||||
'href': '{}/{}/items?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSONLD)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': 'items',
|
||||
'title': 'Items as HTML',
|
||||
'href': '{}/collections/{}/items?f={}'.format(
|
||||
self.config['server']['url'], k, F_HTML)
|
||||
'href': '{}/{}/items?f={}'.format(
|
||||
self.get_collections_url(), k, F_HTML)
|
||||
})
|
||||
|
||||
elif collection_data_type == 'coverage':
|
||||
@@ -943,18 +943,18 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': 'collection',
|
||||
'title': 'Detailed Coverage metadata in JSON',
|
||||
'href': '{}/collections/{}?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': 'collection',
|
||||
'title': 'Detailed Coverage metadata in HTML',
|
||||
'href': '{}/collections/{}?f={}'.format(
|
||||
self.config['server']['url'], k, F_HTML)
|
||||
'href': '{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, F_HTML)
|
||||
})
|
||||
coverage_url = '{}/collections/{}/coverage'.format(
|
||||
self.config['server']['url'], k)
|
||||
coverage_url = '{}/{}/coverage'.format(
|
||||
self.get_collections_url(), k)
|
||||
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
@@ -984,8 +984,8 @@ class API:
|
||||
'type': 'application/prs.coverage+json',
|
||||
'rel': '{}/coverage'.format(OGC_RELTYPES_BASE),
|
||||
'title': 'Coverage data',
|
||||
'href': '{}/collections/{}/coverage?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}/coverage?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
if collection_data_format is not None:
|
||||
collection['links'].append({
|
||||
@@ -993,8 +993,8 @@ class API:
|
||||
'rel': '{}/coverage'.format(OGC_RELTYPES_BASE),
|
||||
'title': 'Coverage data as {}'.format(
|
||||
collection_data_format['name']),
|
||||
'href': '{}/collections/{}/coverage?f={}'.format(
|
||||
self.config['server']['url'], k,
|
||||
'href': '{}/{}/coverage?f={}'.format(
|
||||
self.get_collections_url(), k,
|
||||
collection_data_format['name'])
|
||||
})
|
||||
if dataset is not None:
|
||||
@@ -1027,15 +1027,15 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': 'tiles',
|
||||
'title': 'Tiles as JSON',
|
||||
'href': '{}/collections/{}/tiles?f={}'.format(
|
||||
self.config['server']['url'], k, F_JSON)
|
||||
'href': '{}/{}/tiles?f={}'.format(
|
||||
self.get_collections_url(), k, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': 'tiles',
|
||||
'title': 'Tiles as HTML',
|
||||
'href': '{}/collections/{}/tiles?f={}'.format(
|
||||
self.config['server']['url'], k, F_HTML)
|
||||
'href': '{}/{}/tiles?f={}'.format(
|
||||
self.get_collections_url(), k, F_HTML)
|
||||
})
|
||||
|
||||
try:
|
||||
@@ -1060,15 +1060,15 @@ class API:
|
||||
'type': 'text/json',
|
||||
'rel': 'data',
|
||||
'title': '{} query for this collection as JSON'.format(qt), # noqa
|
||||
'href': '{}/collections/{}/{}?f={}'.format(
|
||||
self.config['server']['url'], k, qt, F_JSON)
|
||||
'href': '{}/{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, qt, F_JSON)
|
||||
})
|
||||
collection['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': 'data',
|
||||
'title': '{} query for this collection as HTML'.format(qt), # noqa
|
||||
'href': '{}/collections/{}/{}?f={}'.format(
|
||||
self.config['server']['url'], k, qt, F_HTML)
|
||||
'href': '{}/{}/{}?f={}'.format(
|
||||
self.get_collections_url(), k, qt, F_HTML)
|
||||
})
|
||||
except ProviderConnectionError:
|
||||
msg = 'connection error (check logs)'
|
||||
@@ -1089,25 +1089,23 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': request.get_linkrel(F_JSON),
|
||||
'title': 'This document as JSON',
|
||||
'href': '{}/collections?f={}'.format(
|
||||
self.config['server']['url'], F_JSON)
|
||||
'href': '{}?f={}'.format(self.get_collections_url(), F_JSON)
|
||||
})
|
||||
fcm['links'].append({
|
||||
'type': FORMAT_TYPES[F_JSONLD],
|
||||
'rel': request.get_linkrel(F_JSONLD),
|
||||
'title': 'This document as RDF (JSON-LD)',
|
||||
'href': '{}/collections?f={}'.format(
|
||||
self.config['server']['url'], F_JSONLD)
|
||||
'href': '{}?f={}'.format(self.get_collections_url(), F_JSONLD)
|
||||
})
|
||||
fcm['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': request.get_linkrel(F_HTML),
|
||||
'title': 'This document as HTML',
|
||||
'href': '{}/collections?f={}'.format(
|
||||
self.config['server']['url'], F_HTML)
|
||||
'href': '{}?f={}'.format(self.get_collections_url(), F_HTML)
|
||||
})
|
||||
|
||||
if request.format == F_HTML: # render
|
||||
fcm['collections_path'] = self.get_collections_url()
|
||||
if dataset is not None:
|
||||
content = render_j2_template(self.config,
|
||||
'collections/collection.html',
|
||||
@@ -1182,8 +1180,8 @@ class API:
|
||||
self.config['resources'][dataset]['title'], request.locale),
|
||||
'properties': {},
|
||||
'$schema': 'http://json-schema.org/draft/2019-09/schema',
|
||||
'$id': '{}/collections/{}/queryables'.format(
|
||||
self.config['server']['url'], dataset)
|
||||
'$id': '{}/{}/queryables'.format(
|
||||
self.get_collections_url(), dataset)
|
||||
}
|
||||
|
||||
if p.fields:
|
||||
@@ -1210,6 +1208,9 @@ class API:
|
||||
if request.format == F_HTML: # render
|
||||
queryables['title'] = l10n.translate(
|
||||
self.config['resources'][dataset]['title'], request.locale)
|
||||
|
||||
queryables['collections_path'] = self.get_collections_url()
|
||||
|
||||
content = render_j2_template(self.config,
|
||||
'collections/queryables.html',
|
||||
queryables, request.locale)
|
||||
@@ -1443,8 +1444,7 @@ class API:
|
||||
serialized_query_params += urllib.parse.quote(str(v), safe=',')
|
||||
|
||||
# TODO: translate titles
|
||||
uri = '{}/collections/{}/items'.format(
|
||||
self.config['server']['url'], dataset)
|
||||
uri = '{}/{}/items'.format(self.get_collections_url(), dataset)
|
||||
content['links'] = [{
|
||||
'type': 'application/geo+json',
|
||||
'rel': request.get_linkrel(F_JSON),
|
||||
@@ -1511,7 +1511,8 @@ class API:
|
||||
|
||||
content['items_path'] = uri
|
||||
content['dataset_path'] = '/'.join(uri.split('/')[:-1])
|
||||
content['collections_path'] = '/'.join(uri.split('/')[:-2])
|
||||
content['collections_path'] = self.get_collections_url()
|
||||
|
||||
content['offset'] = offset
|
||||
|
||||
content['id_field'] = p.id_field
|
||||
@@ -1883,8 +1884,8 @@ class API:
|
||||
'NotFound', msg)
|
||||
|
||||
uri = content['properties'].get(p.uri_field) if p.uri_field else \
|
||||
'{}/collections/{}/items/{}'.format(
|
||||
self.config['server']['url'], dataset, identifier)
|
||||
'{}/{}/items/{}'.format(
|
||||
self.get_collections_url(), dataset, identifier)
|
||||
|
||||
content['links'] = [{
|
||||
'rel': request.get_linkrel(F_JSON),
|
||||
@@ -1906,24 +1907,24 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'title': l10n.translate(collections[dataset]['title'],
|
||||
request.locale),
|
||||
'href': '{}/collections/{}'.format(
|
||||
self.config['server']['url'], dataset)
|
||||
'href': '{}/{}'.format(
|
||||
self.get_collections_url(), dataset)
|
||||
}]
|
||||
|
||||
if 'prev' in content:
|
||||
content['links'].append({
|
||||
'rel': 'prev',
|
||||
'type': FORMAT_TYPES[request.format],
|
||||
'href': '{}/collections/{}/items/{}?f={}'.format(
|
||||
self.config['server']['url'], dataset,
|
||||
'href': '{}/{}/items/{}?f={}'.format(
|
||||
self.get_collections_url(), dataset,
|
||||
content['prev'], request.format)
|
||||
})
|
||||
if 'next' in content:
|
||||
content['links'].append({
|
||||
'rel': 'next',
|
||||
'type': FORMAT_TYPES[request.format],
|
||||
'href': '{}/collections/{}/items/{}?f={}'.format(
|
||||
self.config['server']['url'], dataset,
|
||||
'href': '{}/{}/items/{}?f={}'.format(
|
||||
self.get_collections_url(), dataset,
|
||||
content['next'], request.format)
|
||||
})
|
||||
|
||||
@@ -1940,6 +1941,7 @@ class API:
|
||||
content['uri_field'] = p.uri_field
|
||||
if p.title_field is not None:
|
||||
content['title_field'] = p.title_field
|
||||
content['collections_path'] = self.get_collections_url()
|
||||
|
||||
content = render_j2_template(self.config,
|
||||
'collections/items/item.html',
|
||||
@@ -2135,6 +2137,7 @@ class API:
|
||||
data['title'] = l10n.translate(
|
||||
self.config['resources'][dataset]['title'],
|
||||
self.default_locale)
|
||||
data['collections_path'] = self.get_collections_url()
|
||||
content = render_j2_template(self.config,
|
||||
'collections/coverage/domainset.html',
|
||||
data, self.default_locale)
|
||||
@@ -2188,6 +2191,7 @@ class API:
|
||||
data['title'] = l10n.translate(
|
||||
self.config['resources'][dataset]['title'],
|
||||
self.default_locale)
|
||||
data['collections_path'] = self.get_collections_url()
|
||||
content = render_j2_template(self.config,
|
||||
'collections/coverage/rangetype.html',
|
||||
data, self.default_locale)
|
||||
@@ -2252,29 +2256,31 @@ class API:
|
||||
'type': FORMAT_TYPES[F_JSON],
|
||||
'rel': request.get_linkrel(F_JSON),
|
||||
'title': 'This document as JSON',
|
||||
'href': '{}/collections/{}/tiles?f={}'.format(
|
||||
self.config['server']['url'], dataset, F_JSON)
|
||||
'href': '{}/{}/tiles?f={}'.format(
|
||||
self.get_collections_url(), dataset, F_JSON)
|
||||
})
|
||||
tiles['links'].append({
|
||||
'type': FORMAT_TYPES[F_JSONLD],
|
||||
'rel': request.get_linkrel(F_JSONLD),
|
||||
'title': 'This document as RDF (JSON-LD)',
|
||||
'href': '{}/collections/{}/tiles?f={}'.format(
|
||||
self.config['server']['url'], dataset, F_JSONLD)
|
||||
'href': '{}/{}/tiles?f={}'.format(
|
||||
self.get_collections_url(), dataset, F_JSONLD)
|
||||
})
|
||||
tiles['links'].append({
|
||||
'type': FORMAT_TYPES[F_HTML],
|
||||
'rel': request.get_linkrel(F_HTML),
|
||||
'title': 'This document as HTML',
|
||||
'href': '{}/collections/{}/tiles?f={}'.format(
|
||||
self.config['server']['url'], dataset, F_HTML)
|
||||
'href': '{}/{}/tiles?f={}'.format(
|
||||
self.get_collections_url(), dataset, F_HTML)
|
||||
})
|
||||
|
||||
for service in p.get_tiles_service(
|
||||
tile_services = p.get_tiles_service(
|
||||
baseurl=self.config['server']['url'],
|
||||
servicepath='/collections/{}/tiles/{{{}}}/{{{}}}/{{{}}}/{{{}}}?f=mvt' # noqa
|
||||
.format(dataset, 'tileMatrixSetId',
|
||||
'tileMatrix', 'tileRow', 'tileCol'))['links']:
|
||||
servicepath='{}/{}/tiles/{{{}}}/{{{}}}/{{{}}}/{{{}}}?f=mvt'
|
||||
.format(self.get_collections_url(), dataset, 'tileMatrixSetId',
|
||||
'tileMatrix', 'tileRow', 'tileCol'))
|
||||
|
||||
for service in tile_services['links']:
|
||||
tiles['links'].append(service)
|
||||
|
||||
tiles['tileMatrixSetLinks'] = p.get_tiling_schemes()
|
||||
@@ -2291,6 +2297,7 @@ class API:
|
||||
self.config['resources'][dataset]['extents']['spatial']['bbox']
|
||||
tiles['minzoom'] = p.options['zoom']['min']
|
||||
tiles['maxzoom'] = p.options['zoom']['max']
|
||||
tiles['collections_path'] = self.get_collections_url()
|
||||
|
||||
content = render_j2_template(self.config,
|
||||
'collections/tiles/index.html', tiles,
|
||||
@@ -2459,6 +2466,7 @@ class API:
|
||||
self.config['resources'][dataset]['title'], request.locale)
|
||||
metadata['tileset'] = matrix_id
|
||||
metadata['format'] = metadata_format
|
||||
metadata['collections_path'] = self.get_collections_url()
|
||||
|
||||
content = render_j2_template(self.config,
|
||||
'collections/tiles/metadata.html',
|
||||
@@ -3227,6 +3235,9 @@ class API:
|
||||
return self.get_exception(
|
||||
400, headers, request.format, 'InvalidParameterValue', msg)
|
||||
|
||||
def get_collections_url(self):
|
||||
return '{}/collections'.format((self.config['server']['url']))
|
||||
|
||||
|
||||
def validate_bbox(value=None) -> list:
|
||||
"""
|
||||
|
||||
+22
-22
@@ -151,7 +151,7 @@ def conformance():
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections')
|
||||
@BLUEPRINT.route('/collections/<collection_id>')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>')
|
||||
def collections(collection_id=None):
|
||||
"""
|
||||
OGC API collections endpoint
|
||||
@@ -163,7 +163,7 @@ def collections(collection_id=None):
|
||||
return get_response(api_.describe_collections(request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/queryables')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/queryables')
|
||||
def collection_queryables(collection_id=None):
|
||||
"""
|
||||
OGC API collections querybles endpoint
|
||||
@@ -175,8 +175,8 @@ def collection_queryables(collection_id=None):
|
||||
return get_response(api_.get_collection_queryables(request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/items', methods=['GET', 'POST'])
|
||||
@BLUEPRINT.route('/collections/<collection_id>/items/<item_id>')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/items', methods=['GET', 'POST']) # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/items/<item_id>')
|
||||
def collection_items(collection_id, item_id=None):
|
||||
"""
|
||||
OGC API collections items endpoint
|
||||
@@ -199,7 +199,7 @@ def collection_items(collection_id, item_id=None):
|
||||
api_.get_collection_item(request, collection_id, item_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/coverage')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/coverage')
|
||||
def collection_coverage(collection_id):
|
||||
"""
|
||||
OGC API - Coverages coverage endpoint
|
||||
@@ -211,7 +211,7 @@ def collection_coverage(collection_id):
|
||||
return get_response(api_.get_collection_coverage(request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/coverage/domainset')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/coverage/domainset')
|
||||
def collection_coverage_domainset(collection_id):
|
||||
"""
|
||||
OGC API - Coverages coverage domainset endpoint
|
||||
@@ -224,7 +224,7 @@ def collection_coverage_domainset(collection_id):
|
||||
request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/coverage/rangetype')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/coverage/rangetype')
|
||||
def collection_coverage_rangetype(collection_id):
|
||||
"""
|
||||
OGC API - Coverages coverage rangetype endpoint
|
||||
@@ -237,7 +237,7 @@ def collection_coverage_rangetype(collection_id):
|
||||
request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/tiles')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/tiles')
|
||||
def get_collection_tiles(collection_id=None):
|
||||
"""
|
||||
OGC open api collections tiles access point
|
||||
@@ -250,7 +250,7 @@ def get_collection_tiles(collection_id=None):
|
||||
request, collection_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/tiles/<tileMatrixSetId>/metadata') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/tiles/<tileMatrixSetId>/metadata') # noqa
|
||||
def get_collection_tiles_metadata(collection_id=None, tileMatrixSetId=None):
|
||||
"""
|
||||
OGC open api collection tiles service metadata
|
||||
@@ -264,7 +264,7 @@ def get_collection_tiles_metadata(collection_id=None, tileMatrixSetId=None):
|
||||
request, collection_id, tileMatrixSetId))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/tiles/\
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/tiles/\
|
||||
<tileMatrixSetId>/<tileMatrix>/<tileRow>/<tileCol>')
|
||||
def get_collection_tiles_data(collection_id=None, tileMatrixSetId=None,
|
||||
tileMatrix=None, tileRow=None, tileCol=None):
|
||||
@@ -284,7 +284,7 @@ def get_collection_tiles_data(collection_id=None, tileMatrixSetId=None,
|
||||
|
||||
|
||||
@BLUEPRINT.route('/processes')
|
||||
@BLUEPRINT.route('/processes/<process_id>')
|
||||
@BLUEPRINT.route('/processes/<path:process_id>')
|
||||
def get_processes(process_id=None):
|
||||
"""
|
||||
OGC API - Processes description endpoint
|
||||
@@ -317,7 +317,7 @@ def get_jobs(job_id=None):
|
||||
return get_response(api_.get_jobs(request, job_id))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/processes/<process_id>/execution', methods=['POST'])
|
||||
@BLUEPRINT.route('/processes/<path:process_id>/execution', methods=['POST'])
|
||||
def execute_process_jobs(process_id):
|
||||
"""
|
||||
OGC API - Processes execution endpoint
|
||||
@@ -358,16 +358,16 @@ def get_job_result_resource(job_id, resource):
|
||||
request, job_id, resource))
|
||||
|
||||
|
||||
@BLUEPRINT.route('/collections/<collection_id>/position')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/area')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/cube')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/trajectory')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/corridor')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/instances/<instance_id>/position') # noqa
|
||||
@BLUEPRINT.route('/collections/<collection_id>/instances/<instance_id>/area')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/instances/<instance_id>/cube')
|
||||
@BLUEPRINT.route('/collections/<collection_id>/instances/<instance_id>/trajectory') # noqa
|
||||
@BLUEPRINT.route('/collections/<collection_id>/instances/<instance_id>/corridor') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/position')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/area')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/cube')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/trajectory')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/corridor')
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/instances/<instance_id>/position') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/instances/<instance_id>/area') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/instances/<instance_id>/cube') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/instances/<instance_id>/trajectory') # noqa
|
||||
@BLUEPRINT.route('/collections/<path:collection_id>/instances/<instance_id>/corridor') # noqa
|
||||
def get_collection_edr_query(collection_id, instance_id=None):
|
||||
"""
|
||||
OGC EDR API endpoints
|
||||
|
||||
+25
-25
@@ -139,8 +139,8 @@ async def conformance(request: Request):
|
||||
|
||||
@app.route('/collections')
|
||||
@app.route('/collections/')
|
||||
@app.route('/collections/{collection_id}')
|
||||
@app.route('/collections/{collection_id}/')
|
||||
@app.route('/collections/{path:collection_id}')
|
||||
@app.route('/collections/{path:collection_id}/')
|
||||
async def collections(request: Request, collection_id=None):
|
||||
"""
|
||||
OGC API collections endpoint
|
||||
@@ -155,8 +155,8 @@ async def collections(request: Request, collection_id=None):
|
||||
return get_response(api_.describe_collections(request, collection_id))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/queryables')
|
||||
@app.route('/collections/{collection_id}/queryables/')
|
||||
@app.route('/collections/{path:collection_id}/queryables')
|
||||
@app.route('/collections/{path:collection_id}/queryables/')
|
||||
async def collection_queryables(request: Request, collection_id=None):
|
||||
"""
|
||||
OGC API collections queryables endpoint
|
||||
@@ -220,10 +220,10 @@ async def get_collection_items_tiles(request: Request, name=None,
|
||||
request, name, tileMatrixSetId, tile_matrix, tileRow, tileCol))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/items', methods=['GET', 'POST'])
|
||||
@app.route('/collections/{collection_id}/items/', methods=['GET', 'POST'])
|
||||
@app.route('/collections/{collection_id}/items/{item_id}')
|
||||
@app.route('/collections/{collection_id}/items/{item_id}/')
|
||||
@app.route('/collections/{path:collection_id}/items', methods=['GET', 'POST'])
|
||||
@app.route('/collections/{path:collection_id}/items/', methods=['GET', 'POST'])
|
||||
@app.route('/collections/{path:collection_id}/items/{item_id}')
|
||||
@app.route('/collections/{path:collection_id}/items/{item_id}/')
|
||||
async def collection_items(request: Request, collection_id=None, item_id=None):
|
||||
"""
|
||||
OGC API collections items endpoint
|
||||
@@ -252,7 +252,7 @@ async def collection_items(request: Request, collection_id=None, item_id=None):
|
||||
request, collection_id, item_id))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/coverage')
|
||||
@app.route('/collections/{path:collection_id}/coverage')
|
||||
async def collection_coverage(request: Request, collection_id=None):
|
||||
"""
|
||||
OGC API - Coverages coverage endpoint
|
||||
@@ -268,7 +268,7 @@ async def collection_coverage(request: Request, collection_id=None):
|
||||
return get_response(api_.get_collection_coverage(request, collection_id))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/coverage/domainset')
|
||||
@app.route('/collections/{path:collection_id}/coverage/domainset')
|
||||
async def collection_coverage_domainset(request: Request, collection_id=None):
|
||||
"""
|
||||
OGC API - Coverages coverage domainset endpoint
|
||||
@@ -285,7 +285,7 @@ async def collection_coverage_domainset(request: Request, collection_id=None):
|
||||
request, collection_id))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/coverage/rangetype')
|
||||
@app.route('/collections/{path:collection_id}/coverage/rangetype')
|
||||
async def collection_coverage_rangetype(request: Request, collection_id=None):
|
||||
"""
|
||||
OGC API - Coverages coverage rangetype endpoint
|
||||
@@ -304,8 +304,8 @@ async def collection_coverage_rangetype(request: Request, collection_id=None):
|
||||
|
||||
@app.route('/processes')
|
||||
@app.route('/processes/')
|
||||
@app.route('/processes/{process_id}')
|
||||
@app.route('/processes/{process_id}/')
|
||||
@app.route('/processes/{path:process_id}')
|
||||
@app.route('/processes/{path:process_id}/')
|
||||
async def get_processes(request: Request, process_id=None):
|
||||
"""
|
||||
OGC API - Processes description endpoint
|
||||
@@ -346,8 +346,8 @@ async def get_jobs(request: Request, job_id=None):
|
||||
return get_response(api_.get_jobs(request, job_id))
|
||||
|
||||
|
||||
@app.route('/processes/{process_id}/execution', methods=['POST'])
|
||||
@app.route('/processes/{process_id}/execution/', methods=['POST'])
|
||||
@app.route('/processes/{path:process_id}/execution', methods=['POST'])
|
||||
@app.route('/processes/{path:process_id}/execution/', methods=['POST'])
|
||||
async def execute_process_jobs(request: Request, process_id=None):
|
||||
"""
|
||||
OGC API - Processes jobs endpoint
|
||||
@@ -407,16 +407,16 @@ async def get_job_result_resource(request: Request,
|
||||
request, job_id, resource))
|
||||
|
||||
|
||||
@app.route('/collections/{collection_id}/position')
|
||||
@app.route('/collections/{collection_id}/area')
|
||||
@app.route('/collections/{collection_id}/cube')
|
||||
@app.route('/collections/{collection_id}/trajectory')
|
||||
@app.route('/collections/{collection_id}/corridor')
|
||||
@app.route('/collections/{collection_id}/instances/{instance_id}/position')
|
||||
@app.route('/collections/{collection_id}/instances/{instance_id}/area')
|
||||
@app.route('/collections/{collection_id}/instances/{instance_id}/cube')
|
||||
@app.route('/collections/{collection_id}/instances/{instance_id}/trajectory')
|
||||
@app.route('/collections/{collection_id}/instances/{instance_id}/corridor')
|
||||
@app.route('/collections/{path:collection_id}/position')
|
||||
@app.route('/collections/{path:collection_id}/area')
|
||||
@app.route('/collections/{path:collection_id}/cube')
|
||||
@app.route('/collections/{path:collection_id}/trajectory')
|
||||
@app.route('/collections/{path:collection_id}/corridor')
|
||||
@app.route('/collections/{path:collection_id}/instances/{instance_id}/position') # noqa
|
||||
@app.route('/collections/{path:collection_id}/instances/{instance_id}/area')
|
||||
@app.route('/collections/{path:collection_id}/instances/{instance_id}/cube')
|
||||
@app.route('/collections/{path:collection_id}/instances/{instance_id}/trajectory') # noqa
|
||||
@app.route('/collections/{path:collection_id}/instances/{instance_id}/corridor') # noqa
|
||||
async def get_collection_edr_query(request: Request, collection_id=None, instance_id=None): # noqa
|
||||
"""
|
||||
OGC EDR API endpoints
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="./{{ data['id'] }}">{{ data['title'] | truncate( 25 ) }}</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<a title="{% trans %}Browse Items{% endtrans %}" href="{{ config['server']['url'] }}/collections/{{ data['id'] }}/items">
|
||||
<a title="{% trans %}Browse Items{% endtrans %}" href="{{ data['collections_path'] }}/{{ data['id'] }}/items">
|
||||
{% trans %}Browse through the items of{% endtrans %} "{{ data['title'] }}"</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -41,7 +41,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<a title="{% trans %}Display Queryables{% endtrans %}" href="{{ config['server']['url'] }}/collections/{{ data['id'] }}/queryables">
|
||||
<a title="{% trans %}Display Queryables{% endtrans %}" href="{{ data['collections_path'] }}/{{ data['id'] }}/queryables">
|
||||
{% trans %}Display Queryables of{% endtrans %} "{{ data['title'] }}"</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -51,7 +51,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<a title="{% trans %}Display Tiles{% endtrans %}" href="{{ config['server']['url'] }}/collections/{{ data['id'] }}/tiles">{% trans %}Display Tiles of{% endtrans %} "{{ data['title'] }}"</a>
|
||||
<a title="{% trans %}Display Tiles{% endtrans %}" href="{{ data['collections_path'] }}/{{ data['id'] }}/tiles">{% trans %}Display Tiles of{% endtrans %} "{{ data['title'] }}"</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="../../../collections/{{ data['id'] }}">{{ data['title'] }}</a>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="../../../collections/{{ data['id'] }}">{{ data['title'] }}</a>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {% trans %}Collections{% endtrans %}{% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="./collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<section id="collections">
|
||||
@@ -19,7 +19,7 @@
|
||||
<tr>
|
||||
<td data-label="name">
|
||||
<a title="{{ col['title'] | striptags | truncate }}"
|
||||
href="{{ config['server']['url'] }}/collections/{{ col.id }}">
|
||||
href="{{ data['collections_path'] }}/{{ col.id }}">
|
||||
<span>{{ col['title'] | striptags | truncate }}</span></a>
|
||||
</td>
|
||||
<td data-label="type">{{ col["itemType"] }}</td>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{%- endmacro %}
|
||||
{% block title %}{{ ptitle }} - {{ super() }}{% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../../collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
{% for link in data['links'] %}
|
||||
{% if link.rel == 'collection' %}
|
||||
/ <a href="{{ link['href'] }}">{{ link['title'] | truncate( 25 ) }}</a>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="./{{ data['id'] }}">{{ data['title'] | truncate( 25 ) }}</a>
|
||||
/ <a href="./{{ data['id'] }}queryables">{% trans %}Queryables{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="../{{ data['id'] }}">{{ data['title'] }}</a>
|
||||
/ <a href="../{{ data['id'] }}/tiles">{% trans %}Tiles{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ super() }} {{ data['title'] }} {% endblock %}
|
||||
{% block crumbs %}{{ super() }}
|
||||
/ <a href="../../../../collections">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="{{ data['collections_path'] }}">{% trans %}Collections{% endtrans %}</a>
|
||||
/ <a href="../../../{{ data['id'] }}">{{ data['title'] }}</a>
|
||||
/ <a href="../../../{{ data['id'] }}/tiles">{% trans %}Tiles{% endtrans %}</a>
|
||||
/ <a href="../../../{{ data['id'] }}/tiles/{{ data['tileset'] }}/metadata">Tile Metadata</a>
|
||||
|
||||
@@ -176,7 +176,7 @@ resources:
|
||||
name: NetCDF
|
||||
mimetype: application/x-netcdf
|
||||
|
||||
lakes:
|
||||
naturalearth/lakes:
|
||||
type: collection
|
||||
title:
|
||||
en: Large Lakes
|
||||
|
||||
+16
-4
@@ -235,6 +235,8 @@ def test_api(config, api_, openapi):
|
||||
assert rsp_headers['Content-Language'] == 'en-US'
|
||||
assert code == 400
|
||||
|
||||
assert api_.get_collections_url() == 'http://localhost:5000/collections'
|
||||
|
||||
|
||||
def test_api_exception(config, api_):
|
||||
req = mock_request({'f': 'foo'})
|
||||
@@ -491,6 +493,12 @@ def test_describe_collections(config, api_):
|
||||
assert collection['id'] == 'gdps-temperature'
|
||||
assert len(collection['links']) == 12
|
||||
|
||||
# hiearchical collections
|
||||
rsp_headers, code, response = api_.describe_collections(
|
||||
req, 'naturalearth/lakes')
|
||||
collection = json.loads(response)
|
||||
assert collection['id'] == 'naturalearth/lakes'
|
||||
|
||||
|
||||
def test_get_collection_queryables(config, api_):
|
||||
req = mock_request()
|
||||
@@ -775,7 +783,8 @@ def test_get_collection_items(config, api_):
|
||||
assert code == 200
|
||||
|
||||
req = mock_request({'scalerank': 1})
|
||||
rsp_headers, code, response = api_.get_collection_items(req, 'lakes')
|
||||
rsp_headers, code, response = api_.get_collection_items(
|
||||
req, 'naturalearth/lakes')
|
||||
features = json.loads(response)
|
||||
|
||||
assert len(features['features']) == 10
|
||||
@@ -783,7 +792,8 @@ def test_get_collection_items(config, api_):
|
||||
assert features['numberReturned'] == 10
|
||||
|
||||
req = mock_request({'datetime': '2005-04-22'})
|
||||
rsp_headers, code, response = api_.get_collection_items(req, 'lakes')
|
||||
rsp_headers, code, response = api_.get_collection_items(
|
||||
req, 'naturalearth/lakes')
|
||||
|
||||
assert code == 400
|
||||
|
||||
@@ -1081,12 +1091,14 @@ def test_get_collection_tiles(config, api_):
|
||||
rsp_headers, code, response = api_.get_collection_tiles(req, 'obs')
|
||||
assert code == 400
|
||||
|
||||
rsp_headers, code, response = api_.get_collection_tiles(req, 'lakes')
|
||||
rsp_headers, code, response = api_.get_collection_tiles(
|
||||
req, 'naturalearth/lakes')
|
||||
assert code == 200
|
||||
|
||||
# Language settings should be ignored (return system default)
|
||||
req = mock_request({'lang': 'fr'})
|
||||
rsp_headers, code, response = api_.get_collection_tiles(req, 'lakes')
|
||||
rsp_headers, code, response = api_.get_collection_tiles(
|
||||
req, 'naturalearth/lakes')
|
||||
assert rsp_headers['Content-Language'] == 'en-US'
|
||||
content = json.loads(response)
|
||||
assert content['description'] == 'lakes of the world, public domain'
|
||||
|
||||
Reference in New Issue
Block a user