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:
Tom Kralidis
2022-04-16 18:26:55 -04:00
committed by GitHub
parent e19bf5529d
commit 09ad6f8fce
15 changed files with 192 additions and 131 deletions
+41 -1
View File
@@ -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
-----------
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 %}
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -176,7 +176,7 @@ resources:
name: NetCDF
mimetype: application/x-netcdf
lakes:
naturalearth/lakes:
type: collection
title:
en: Large Lakes
+16 -4
View File
@@ -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'