update ES backend, add HTML templates
This commit is contained in:
+42
-8
@@ -34,8 +34,9 @@ import os
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from pygeoapi import __version__
|
||||
from pygeoapi.provider import load_provider
|
||||
from pygeoapi.provider.base import ProviderConnectionError
|
||||
from pygeoapi.provider.base import ProviderConnectionError, ProviderQueryError
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -106,14 +107,13 @@ class API(object):
|
||||
'rel': 'self',
|
||||
'type': 'text/html',
|
||||
'title': 'the OpenAPI definition as HTML',
|
||||
'href': '{}?f=html'.format(self.config['server']['url'])
|
||||
'href': '{}api?f=html'.format(self.config['server']['url'])
|
||||
}
|
||||
]
|
||||
|
||||
if format_ == 'html': # render
|
||||
headers_['Content-type'] = 'text/html'
|
||||
content = _render_j2_template(self.config, 'service.html', fcm)
|
||||
|
||||
content = _render_j2_template(self.config, 'root.html', fcm)
|
||||
return headers_, 200, content
|
||||
|
||||
return headers_, 200, json.dumps(fcm)
|
||||
@@ -149,6 +149,17 @@ class API(object):
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
|
||||
formats = ['json', 'html']
|
||||
|
||||
format_ = args.get('f')
|
||||
if format_ is not None and format_ not in formats:
|
||||
exception = {
|
||||
'code': 'InvalidParameterValue',
|
||||
'description': 'Invalid format'
|
||||
}
|
||||
LOGGER.error(exception)
|
||||
return headers_, 400, json.dumps(exception)
|
||||
|
||||
conformance = {
|
||||
'conformsTo': [
|
||||
'http://www.opengis.net/spec/wfs-1/3.0/req/core',
|
||||
@@ -158,6 +169,12 @@ class API(object):
|
||||
]
|
||||
}
|
||||
|
||||
if format_ == 'html': # render
|
||||
headers_['Content-type'] = 'text/html'
|
||||
content = _render_j2_template(self.config, 'conformance.html',
|
||||
conformance)
|
||||
return headers_, 200, content
|
||||
|
||||
return headers_, 200, json.dumps(conformance)
|
||||
|
||||
def describe_collections(self, headers, args, dataset=None):
|
||||
@@ -219,13 +236,19 @@ class API(object):
|
||||
collection['links'].append(lnk)
|
||||
|
||||
if dataset is not None and k == dataset:
|
||||
return headers_, 200, json.dumps(collection)
|
||||
fcm = collection
|
||||
break
|
||||
|
||||
fcm['collections'].append(collection)
|
||||
|
||||
if format_ == 'html': # render
|
||||
headers_['Content-type'] = 'text/html'
|
||||
content = _render_j2_template(self.config, 'service.html', fcm)
|
||||
if dataset is not None:
|
||||
content = _render_j2_template(self.config, 'collection.html',
|
||||
fcm)
|
||||
else:
|
||||
content = _render_j2_template(self.config, 'collections.html',
|
||||
fcm)
|
||||
|
||||
return headers_, 200, content
|
||||
|
||||
@@ -242,10 +265,12 @@ class API(object):
|
||||
:returns: tuple of headers, status code, content
|
||||
"""
|
||||
|
||||
print(args)
|
||||
headers_ = {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
|
||||
LOGGER.debug('Processing query parameters')
|
||||
try:
|
||||
startindex = int(args.get('startindex'))
|
||||
except TypeError:
|
||||
@@ -256,6 +281,8 @@ class API(object):
|
||||
limit = self.config['server']['limit']
|
||||
|
||||
resulttype = args.get('resulttype') or 'results'
|
||||
bbox = args.get('bbox')
|
||||
time = args.get('time')
|
||||
|
||||
if dataset not in self.config['datasets'].keys():
|
||||
exception = {
|
||||
@@ -274,8 +301,15 @@ class API(object):
|
||||
|
||||
try:
|
||||
content = p.query(startindex=int(startindex), limit=int(limit),
|
||||
resulttype=resulttype)
|
||||
resulttype=resulttype, bbox=bbox, time=time)
|
||||
except ProviderConnectionError:
|
||||
exception = {
|
||||
'code': 'NoApplicableCode',
|
||||
'description': 'connection error (check logs)'
|
||||
}
|
||||
LOGGER.error(exception)
|
||||
return headers_, 500, json.dumps(exception)
|
||||
except ProviderQueryError:
|
||||
exception = {
|
||||
'code': 'NoApplicableCode',
|
||||
'description': 'query error (check logs)'
|
||||
@@ -371,4 +405,4 @@ def _render_j2_template(config, template, data):
|
||||
env = Environment(loader=FileSystemLoader(TEMPLATES))
|
||||
print(TEMPLATES)
|
||||
template = env.get_template(template)
|
||||
return template.render(config=config, data=data)
|
||||
return template.render(config=config, data=data, version=__version__)
|
||||
|
||||
@@ -47,6 +47,7 @@ class BaseProvider(object):
|
||||
self.name = provider_def['name']
|
||||
self.data = provider_def['data']
|
||||
self.id_field = provider_def['id_field']
|
||||
self.time_field = provider_def.get('time_field')
|
||||
|
||||
def query(self):
|
||||
"""
|
||||
@@ -97,3 +98,8 @@ class BaseProvider(object):
|
||||
class ProviderConnectionError(Exception):
|
||||
"""query / backend error"""
|
||||
pass
|
||||
|
||||
|
||||
class ProviderQueryError(Exception):
|
||||
"""query / backend error"""
|
||||
pass
|
||||
|
||||
@@ -31,7 +31,8 @@ import logging
|
||||
|
||||
from elasticsearch import Elasticsearch, exceptions
|
||||
|
||||
from pygeoapi.provider.base import BaseProvider, ProviderConnectionError
|
||||
from pygeoapi.provider.base import (BaseProvider, ProviderConnectionError,
|
||||
ProviderQueryError)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -63,33 +64,82 @@ class ElasticsearchProvider(BaseProvider):
|
||||
LOGGER.debug('Connecting to Elasticsearch')
|
||||
self.es = Elasticsearch(self.es_host)
|
||||
|
||||
def query(self, startindex=0, limit=10, resulttype='results'):
|
||||
def query(self, startindex=0, limit=10, resulttype='results',
|
||||
bbox=None, time=None):
|
||||
"""
|
||||
query Elasticsearch index
|
||||
|
||||
:param startindex: starting record to return (default 0)
|
||||
:param limit: number of records to return (default 10)
|
||||
:param resulttype: return results or hit limit (default results)
|
||||
:param bbox: bounding box (minx,miny,maxx,maxy)
|
||||
:param time: temporal (datestamp or extent)
|
||||
|
||||
:returns: dict of 0..n GeoJSON features
|
||||
"""
|
||||
|
||||
query = {'query': {'bool': {'filter': []}}}
|
||||
filter_ = []
|
||||
|
||||
feature_collection = {
|
||||
'type': 'FeatureCollection',
|
||||
'features': []
|
||||
}
|
||||
|
||||
LOGGER.debug('Querying Elasticsearch')
|
||||
if resulttype == 'hits':
|
||||
LOGGER.debug('hits only specified')
|
||||
limit = 0
|
||||
|
||||
if bbox is not None:
|
||||
LOGGER.debug('processing bbox parameter')
|
||||
minx, miny, maxx, maxy = bbox.split(',')
|
||||
bbox_filter = {
|
||||
'geo_shape': {
|
||||
'geometry': {
|
||||
'shape': {
|
||||
'type': 'envelope',
|
||||
'coordinates': [[minx, miny], [maxx, maxy]]
|
||||
},
|
||||
'relation': 'intersects'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query['query']['bool']['filter'].append(bbox_filter)
|
||||
|
||||
if time is not None:
|
||||
LOGGER.debug('processing time parameter')
|
||||
if self.time_field is None:
|
||||
LOGGER.error('time_field not enabled for collection')
|
||||
raise ProviderQueryError()
|
||||
|
||||
time_field = 'properties.{}'.format(self.time_field)
|
||||
|
||||
if '/' in time: # envelope
|
||||
range_ = {'range': time_field}
|
||||
time_begin, time_end = time.split('/')
|
||||
|
||||
if time_begin == '': # until
|
||||
range_['range'][time_field]['lte'] = time_end
|
||||
if time_end == '': # from
|
||||
range_['range'][time_field]['gte'] = time_begin
|
||||
filter_.append(range_)
|
||||
|
||||
else: # time instant
|
||||
filter_.append({'match': {time_field: time}})
|
||||
|
||||
query['query']['bool']['filter'].append(filter_)
|
||||
|
||||
try:
|
||||
LOGGER.debug('Querying Elasticsearch')
|
||||
results = self.es.search(index=self.index_name, from_=startindex,
|
||||
size=limit)
|
||||
size=limit, body=query)
|
||||
except exceptions.ConnectionError as err:
|
||||
LOGGER.error(err)
|
||||
raise ProviderConnectionError()
|
||||
except exceptions.RequestError as err:
|
||||
LOGGER.error(err)
|
||||
raise ProviderQueryError()
|
||||
|
||||
feature_collection['numberMatched'] = results['hits']['total']
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="{{ config['server']['encoding'] }}">
|
||||
<title>{{ config['metadata']['identification']['title'] }} - data['title']</title>
|
||||
|
||||
<meta name="language" content="{{ config['server']['language'] }}">
|
||||
<meta name="description" content="{{ config['metadata']['identification']['title'] }}">
|
||||
<meta name="keywords" content="{{ config['metadata']['identification']['keywords']|join(',') }}">
|
||||
|
||||
<link rel="stylesheet" href="static/css/default.css">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a title="{{ config['metadata']['identification']['title'] }}" href="{{ config['server']['url'] }}">{{ config['metadata']['identification']['title'] }}</a></h1>
|
||||
<span itemprop="description">{{ config['metadata']['identification']['description'] }}</span>
|
||||
</header>
|
||||
<section id="collection">
|
||||
<h2>{{ data['title'] }}</h2>
|
||||
<span>{{ data['description'] }}</span>
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
{% for link in data['links'] %}
|
||||
<li><a title="{{ link['rel'] }}" href="{{ link['href'] }}">{{ link['href'] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<hr/>
|
||||
<footer>Powered by <a title="pygeoapi" href="https://github.com/geopython/pygeoapi">pygeoapi</a> {{ version }}</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="{{ config['server']['encoding'] }}">
|
||||
<title>{{ config['metadata']['identification']['title'] }} - Collections</title>
|
||||
|
||||
<meta name="language" content="{{ config['server']['language'] }}">
|
||||
<meta name="description" content="{{ config['metadata']['identification']['title'] }}">
|
||||
<meta name="keywords" content="{{ config['metadata']['identification']['keywords']|join(',') }}">
|
||||
|
||||
<link rel="stylesheet" href="static/css/default.css">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a title="{{ config['metadata']['identification']['title'] }}" href="{{ config['server']['url'] }}">{{ config['metadata']['identification']['title'] }}</a></h1>
|
||||
<span itemprop="description">{{ config['metadata']['identification']['description'] }}</span>
|
||||
</header>
|
||||
<section id="collections">
|
||||
<h2>Collections</h2>
|
||||
<ul>
|
||||
{% for k, v in config['datasets'].items()%}
|
||||
<li><a title="{{ v['title'] }}" href="{{ config['server']['url'] }}/collections/{{ k }}?f=html">{{ v['title'] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<hr/>
|
||||
<footer>Powered by <a title="pygeoapi" href="https://github.com/geopython/pygeoapi">pygeoapi</a> {{ version }}</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="{{ config['server']['encoding'] }}">
|
||||
<title>{{ config['metadata']['identification']['title'] }} - Conformance</title>
|
||||
|
||||
<meta name="language" content="{{ config['server']['language'] }}">
|
||||
<meta name="description" content="{{ config['metadata']['identification']['title'] }}">
|
||||
<meta name="keywords" content="{{ config['metadata']['identification']['keywords']|join(',') }}">
|
||||
|
||||
<link rel="stylesheet" href="static/css/default.css">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a title="{{ config['metadata']['identification']['title'] }}" href="{{ config['server']['url'] }}">{{ config['metadata']['identification']['title'] }}</a></h1>
|
||||
<span itemprop="description">{{ config['metadata']['identification']['description'] }}</span>
|
||||
</header>
|
||||
<section id="conformance">
|
||||
<h2>Conformance</h2>
|
||||
<ul>
|
||||
{% for link in data['conformsTo'] %}
|
||||
<li><a title="{{ link }}" href="{{ link }}">{{ link }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<hr/>
|
||||
<footer>Powered by <a title="pygeoapi" href="https://github.com/geopython/pygeoapi">pygeoapi</a> {{ version }}</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,7 +16,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a title="{{ config['metadata']['identification']['title'] }}" href="{{ config['server']['url'] }}">{{ config['metadata']['identification']['title'] }}</a></h1>
|
||||
<h1><a title="{{ config['metadata']['identification']['title'] }}" href="{{ config['server']['url'] }}/">{{ config['metadata']['identification']['title'] }}</a></h1>
|
||||
<span itemprop="description">{{ config['metadata']['identification']['description'] }}</span>
|
||||
</header>
|
||||
<section id="service-metadata">
|
||||
@@ -28,11 +28,17 @@
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
<section id="datasets">
|
||||
<h2>Datasets</h2>
|
||||
<section id="conformance">
|
||||
<h2><a href="conformance?f=html">Conformance</a></h2>
|
||||
</section>
|
||||
<section id="collections">
|
||||
<h2><a href="collections?f=html">Collections</a></h2>
|
||||
</section>
|
||||
<section id="links">
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
{% for k, v in config['datasets'].items()%}
|
||||
<li><a title="{{ v['title'] }}" href="{{ config['server']['url'] }}{{ k }}">{{ v['title'] }}</a></li>
|
||||
{% for link in data['links'] %}
|
||||
<li><a title="{{ link['title'] }}" href="{{ link['href'] }}">{{ link['title'] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
Reference in New Issue
Block a user