From 9015b16e6d96bfd44afd2d1ca841365f68593672 Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Fri, 23 Mar 2018 09:41:43 -0400 Subject: [PATCH 1/9] implement OpenAPI from single config --- .gitignore | 3 + README.md | 24 +- openapi/wfs/0.0.1/pygeoapi-openapi.yml | 411 ------------------------- pygeoapi-config.yml | 6 +- pygeoapi/__init__.py | 2 + pygeoapi/flask_app.py | 103 +++++-- pygeoapi/openapi.py | 187 +++++++++++ pygeoapi/provider/csv_.py | 28 +- pygeoapi/provider/elasticsearch_.py | 10 +- pygeoapi/provider/geojson.py | 8 +- pygeoapi/static/css/default.css | 2 +- pygeoapi/templates/service.html | 6 +- pygeoapi/util.py | 49 +++ pygeoapi/views.py | 270 ++++++++++------ requirements.txt | 1 - 15 files changed, 544 insertions(+), 566 deletions(-) delete mode 100644 openapi/wfs/0.0.1/pygeoapi-openapi.yml create mode 100644 pygeoapi/openapi.py create mode 100644 pygeoapi/util.py diff --git a/.gitignore b/.gitignore index a9a8351..a397b86 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,6 @@ ENV/ # pygeoapi artifacts local.config.yml local.swagger.yml + +# misc +*.swp diff --git a/README.md b/README.md index f177364..2648fee 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,21 @@ pygeoapi provides an API to geospatial data ## Installation ```bash -virtualenv -p python3 pygeoapi +virtualenv -p python pygeoapi cd pygeoapi . bin/activate git clone https://github.com/geopython/pygeoapi.git cd pygeoapi -pip3 install -r requirements.txt -pip3 install -r requirements-dev.txt -pip3 install -e . -cp openapi/wfs/0.0.1/pygeoapi-openapi.yml local.swagger.yml +pip install -r requirements.txt +pip install -r requirements-dev.txt +pip install -e . cp pygeoapi-config.yml local.config.yml vi local.config.yml # TODO: what is most important to edit? -vi local.swagger.yml -# TODO: what is most important to edit? export PYGEOAPI_CONFIG=/path/to/local.config.yml -export PYGEOAPI_SWAGGER=/path/to/local.swagger.yml +# generate OpenAPI Document +pygeoapi generate_openapi_document -c local.config.yml > openapi.yml +export PYGEOAPI_SWAGGER=/path/to/openapi.yml pygeoapi serve ``` @@ -40,3 +39,12 @@ curl http://localhost:5000/obs # feature curl http://localhost:5000/obs/371 ``` + +## Testing against Swagger UI + +```bash +docker pull swaggerapi/swagger-ui +docker run -p 80:8080 swaggerapi/swagger-ui +# go to http://localhost +# enter http://localhost:5000/api and click 'Explore' +``` diff --git a/openapi/wfs/0.0.1/pygeoapi-openapi.yml b/openapi/wfs/0.0.1/pygeoapi-openapi.yml deleted file mode 100644 index 2c450a8..0000000 --- a/openapi/wfs/0.0.1/pygeoapi-openapi.yml +++ /dev/null @@ -1,411 +0,0 @@ -############################################################################## -# -# The MIT License (MIT) -# Copyright (c) 2018 Tom Kralidis -# -# 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. -# -############################################################################## - -swagger: '2.0' -info: - title: A sample API conforming to the OGC Web Feature Service standard - version: 0.0.1 - description: |- - WARNING - THIS IS WORK IN PROGRESS\ - WARNING - This is a Swagger / OpenAPI 2.0 variant. WFS 3.0 is expected to use OAS 3.0, not 2.0.\ - TODO - Add a description of the sample service.\ - TODO - Add security elements in a separate example.\ - TODO - Connect to a live service. - contact: - name: Acme Corporation - email: info@example.org - url: 'http://example.org/' - license: - name: CC-BY 4.0 license - url: 'https://creativecommons.org/licenses/by/4.0/' -schemes: - - http -host: {{host}} -basePath: / -paths: - /: - get: - summary: describe the feature collections in the dataset - operationId: pygeoapi.views.describe_collections - tags: - - Capabilities - parameters: - - $ref: '#/parameters/f' - produces: - - application/json - - text/html - responses: - '200': - description: The feature collections shared by this API. - schema: - $ref: '#/definitions/content' - default: - description: An error occured. - schema: - $ref: '#/definitions/exception' - /api: - get: - summary: the API description - this document - operationId: pygeoapi.views.get_specification - tags: - - Capabilities - parameters: - - $ref: '#/parameters/f' - produces: - - application/openapi+json;version=2.0 - - text/html - responses: - '200': - description: >- - The formal documentation of this API according to the OpenAPI - specification, version 3.0. I.e., this document. - schema: - type: object - default: - description: An error occured. - schema: - $ref: '#/definitions/exception' - -## START OF DATASET DEFINITION - TO BE REPLACED BY CLI TOOLING in the local swagger file - /obs: - get: - summary: retrieve observation features - description: >- - Sample observations - operationId: pygeoapi.views.get_features - tags: - - Features - parameters: - - name: dataset # constant parameter - in: query - type: string - enum: - - obs - default: obs - - $ref: '#/parameters/startIndex' - - $ref: '#/parameters/count' - - $ref: '#/parameters/resultType' - - $ref: '#/parameters/bbox' - - $ref: '#/parameters/f' - produces: - - application/geo+json - - text/html - responses: - '200': - description: >- - Information about the feature collection plus the first features - matching the selection parameters. - schema: - $ref: '#/definitions/featureCollection' - default: - description: An error occured. - schema: - $ref: '#/definitions/exception' - '/obs/{id}': - get: - summary: retrieve a feature - operationId: pygeoapi.views.get_feature - tags: - - Features - parameters: - - name: dataset # constant parameter - in: query - type: string - enum: - - obs - default: obs - - $ref: '#/parameters/id' - - $ref: '#/parameters/f' - produces: - - application/geo+json - - text/html - responses: - '200': - description: A feature. - schema: - $ref: '#/definitions/observation' - '404': - description: 'The feature with id {id} does not exist.' - schema: - $ref: '#/definitions/exception' - default: - description: An error occured. - schema: - $ref: '#/definitions/exception' -## END OF DATASET DEFINITION - -parameters: - f: - name: f - in: query - description: >- - The format of the response. If no value is provided, the standard http - rules apply, i.e., the accept header shall be used to determine the - format.\ - - Pre-defined values are "json" and "html". The response to other values is - determined by the server. - type: string - enum: - - json - - html - startIndex: - name: start_index - in: query - description: >- - The optional startIndex parameter indicates the index within the result - set from which the server shall begin presenting results in the response - document. The first element has an index of 0.\ - - Minimum = 0.\ - - Default = 0. - type: integer - minimum: 0 - default: 0 - count: - name: count - in: query - description: >- - The optional count parameter limits the number of items that are presented - in the response document.\ - - Only items are counted that are on the first level of the collection in - the response document. Nested objects contained within the explicitly - requested items shall not be counted.\ - - Minimum = 1.\ - - Maximum = 10000.\ - - Default = 10. - type: integer - minimum: 1 - maximum: 10000 - default: 10 - bbox: - name: bbox - in: query - description: >- - Only features that have a geometry that intersects the bounding box are - selected. The bounding box is provided as four numbers: - - * Lower corner, coordinate axis 1 (minimum longitude) * Lower corner, - coordinate axis 2 (minimum latitude) * Upper corner, coordinate axis 1 - (maximum longitude) * Upper corner, coordinate axis 2 (maximum latitude) - type: array - items: - type: number - minItems: 4 - maxItems: 4 - minimum: -180 - maximum: 180 - collectionFormat: csv - resultType: - name: result_type - in: query - description: >- - This service will respond to a query in one of two ways (excluding an - exception response). It may either generate a complete response document - containing resources that satisfy the operation or it may simply generate - an empty response container that indicates the count of the total number - of resources that the operation would return. Which of these two responses - is generated is determined by the value of the optional resultType - parameter.\ - - The allowed values for this parameter are "results" and "hits".\ - - If the value of the resultType parameter is set to "results", the server - will generate a complete response document containing resources that - satisfy the operation.\ - - If the value of the resultType attribute is set to "hits", the server will - generate an empty response document containing no resource instances.\ - - Default = "results". - type: string - enum: - - hits - - results - default: results - id: - name: id - in: path - description: The id of a feature - required: true - type: string -definitions: - exception: - type: object - required: - - code - properties: - code: - type: string - description: - type: string - bbox: - type: object - required: - - bbox - properties: - crs: - type: string - enum: - - 'http://www.opengis.net/def/crs/OGC/1.3/CRS84' - default: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84' - bbox: - description: >- - minimum longitude, minimum latitude, maximum longitude, maximum - latitude - type: array - items: - minItems: 4 - maxItems: 4 - type: number - minimum: -180 - maximum: 180 - example: - - -180 - - -90 - - 180 - - 90 - content: - type: object - required: - - collections - properties: - collections: - type: array - items: - $ref: '#/definitions/collectionInfo' - collectionInfo: - type: object - required: - - name - - links - properties: - name: - type: string - example: address - title: - type: string - example: address - description: - type: string - example: An address. - links: - type: array - items: - $ref: '#/definitions/link' - example: - - href: 'http://data.example.com/observations' - rel: item - - href: 'http://example.com/concepts/observations.html' - rel: describedBy - type: text/html - extent: - $ref: '#/definitions/bbox' - crs: - description: TODO - add description ... first crs is the default crs - type: array - items: - type: string - default: - - 'http://www.opengis.net/def/crs/OGC/1.3/CRS84' - example: - - 'http://www.opengis.net/def/crs/OGC/1.3/CRS84' - - 'http://www.opengis.net/def/crs/EPSG/0/4326' - link: - type: object - required: - - href - properties: - href: - type: string - rel: - type: string - example: prev - type: - type: string - example: application/geo+json - hreflang: - type: string - example: en - featureCollection: - type: object - required: - - features - properties: - features: - type: array - items: - $ref: '#/definitions/observation' - Point: - type: object - required: - - type - properties: - type: - type: string - enum: - - Point - coordinates: - type: array - items: - type: number - minItems: 2 - observation: - type: object - required: - - id - - type - - geometry - - properties - properties: - type: - type: string - enum: - - Feature - geometry: - $ref: '#/definitions/Point' - id: - type: string - properties: - type: object - properties: - stn_id: - type: string - datetime: - type: string - value: - type: number -tags: - - name: Capabilities - description: >- - Essential characteristics of this API including information about the - data. - - name: Features - description: Access to data (features). diff --git a/pygeoapi-config.yml b/pygeoapi-config.yml index 28a734d..a3c2bdc 100644 --- a/pygeoapi-config.yml +++ b/pygeoapi-config.yml @@ -1,6 +1,7 @@ server: host: localhost port: 5000 + basepath: / mimetype: application/json; charset=UTF-8 encoding: utf-8 language: en-US @@ -14,14 +15,15 @@ logging: metadata: identification: - title: pygeoapi - description: pygeoapi provides an API to geospatial data + title: pygeoapi default instance + description: pygeoapi provides an API to geospatial data keywords: - geospatial - data - api keywords_type: theme terms_of_service: None + url: http://example.org license: name: CC-BY 4.0 license url: https://creativecommons.org/licenses/by/4.0/ diff --git a/pygeoapi/__init__.py b/pygeoapi/__init__.py index 7e7a38d..5de5d5d 100644 --- a/pygeoapi/__init__.py +++ b/pygeoapi/__init__.py @@ -30,6 +30,7 @@ import click from pygeoapi.flask_app import serve +from pygeoapi.openapi import generate_openapi_document __version__ = '0.1.dev0' @@ -41,3 +42,4 @@ def cli(): cli.add_command(serve) +cli.add_command(generate_openapi_document) diff --git a/pygeoapi/flask_app.py b/pygeoapi/flask_app.py index 0f57259..d22e6b0 100644 --- a/pygeoapi/flask_app.py +++ b/pygeoapi/flask_app.py @@ -28,38 +28,95 @@ # # ================================================================= -import click -import connexion +import os -from pygeoapi.log import setup_logger +import yaml +import click + +from flask import Flask, make_response, request +from flask_cors import CORS + +from pygeoapi import views from pygeoapi.config import settings +from pygeoapi.log import setup_logger +from pygeoapi.util import get_url + +APP = Flask(__name__) + + +@APP.route('/') +def describe_collections(): + headers, status_code, content = views.describe_collections( + request.headers, request.args, APP.config['PYGEOAPI_BASEURL']) + + response = make_response(content, status_code) + if headers: + response.headers = headers + + return response + + +@APP.route('/api') +def api(): + with open(os.environ.get('PYGEOAPI_OPENAPI')) as ff: + openapi = yaml.load(ff) + + headers, status_code, content = views.api(request.headers, request.args, + openapi) + + response = make_response(content, status_code) + if headers: + response.headers = headers + + return response + + +@APP.route('/api/conformance') +def api_conformance(): + headers, status_code, content = views.api_conformance(request.headers, + request.args) + + response = make_response(content, status_code) + if headers: + response.headers = headers + + return response + + +@APP.route('//') +@APP.route('//') +def dataset(feature_collection, feature=None): + if feature is None: + headers, status_code, content = views.get_features( + request.headers, request.args, feature_collection) + else: + headers, status_code, content = views.get_feature( + request.headers, request.args, feature_collection, feature) + + response = make_response(content, status_code) + + if headers: + response.headers = headers + + return response @click.command() @click.pass_context -@click.option('--host', '-h', default='localhost', help='Hostname') -@click.option('--port', '-p', default=5000, help='port') @click.option('--debug', '-d', default=False, is_flag=True, help='debug') -def serve(ctx, host, port, debug=False): +def serve(ctx, debug=False): """Serve pygeoapi via Flask""" - if port is not None: - port_ = port - else: - port_ = settings['server']['port'] - if host is not None: - host_ = host - else: - host_ = settings['server']['host'] + if not settings['server']['pretty_print']: + APP.config['JSONIFY_PRETTYPRINT_REGULAR'] = False - app = connexion.FlaskApp(__name__, port=port_, specification_dir='.') - - hostport = '{}:{}'.format(host_, port_) - - api = app.add_api(settings['swagger'], debug=debug, strict_validation=True, - arguments={'host': hostport, 'config': settings}) - - settings['api'] = api.specification + if settings['server']['cors']: + CORS(APP) setup_logger() - app.run() + # TODO: get scheme + BASEURL = get_url('http', settings['server']['host'], + settings['server']['port'], + settings['server']['basepath']) + APP.config['PYGEOAPI_BASEURL'] = BASEURL + APP.run(debug=True) diff --git a/pygeoapi/openapi.py b/pygeoapi/openapi.py new file mode 100644 index 0000000..6147d13 --- /dev/null +++ b/pygeoapi/openapi.py @@ -0,0 +1,187 @@ +# ================================================================= +# +# Authors: Tom Kralidis +# +# Copyright (c) 2018 Tom Kralidis +# +# 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. +# +# ================================================================= + +import logging + +import click +import yaml + +LOGGER = logging.getLogger(__name__) + + +def get_oas_30(cfg): + """ + Generates an OpenAPI 3.0 Document + + :param cfg: configuration object + + :returns: OpenAPI definition YAML dict + """ + + paths = {} + LOGGER.debug('setting up server info') + oas = { + 'openapi': '3.0.1', + 'tags': [] + } + info = { + 'title': cfg['metadata']['identification']['title'], + 'description': cfg['metadata']['identification']['description'], + 'x-keywords': cfg['metadata']['identification']['keywords'], + 'termsOfService': + cfg['metadata']['identification']['terms_of_service'], + 'contact': { + 'name': cfg['metadata']['provider']['name'], + 'url': cfg['metadata']['provider']['url'], + 'email': cfg['metadata']['contact']['email'] + }, + 'license': { + 'name': cfg['metadata']['license']['name'], + 'url': cfg['metadata']['license']['url'] + }, + 'version': '3.0.1' + } + oas['info'] = info + + url = 'http://{}'.format(cfg['server']['host']) + if cfg['server']['port'] not in [80, 443]: + url = '{}:{}'.format(url, cfg['server']['port']) + oas['servers'] = [{ + 'url': url, + 'description': cfg['metadata']['identification']['description'] + }] + + paths['/'] = { + 'get': { + 'summary': 'Feature Collections', + 'descriptions': 'Feature Collections', + 'tags': ['server'], + 'responses': { + 200: { + 'description': 'successful operation' + } + } + } + } + paths['/api'] = { + 'get': { + 'summary': 'This document', + 'description': 'This document', + 'tags': ['server'], + 'responses': { + 200: { + 'description': 'successful operation' + } + } + } + } + paths['/api/conformance'] = { + 'get': { + 'summary': 'API conformance definition', + 'description': 'API conformance definition', + 'tags': ['server'], + 'responses': { + 200: { + 'description': 'successful operation' + } + } + } + } + oas['tags'].append({ + 'name': 'server', + 'description': cfg['metadata']['identification']['description'], + 'externalDocs': { + 'description': 'information', + 'url': cfg['metadata']['identification']['url']} + } + ) + LOGGER.debug('setting up datasets') + for k, v in cfg['datasets'].items(): + tag = { + 'name': k, + 'description': v['description'], + 'externalDocs': {} + } + for link in v['links']: + if link['type'] == 'information': + tag['externalDocs']['description'] = link['type'] + tag['externalDocs']['url'] = link['url'] + break + oas['tags'].append(tag) + paths['/{}'.format(k)] = { + 'get': { + 'summary': 'Get {} features'.format(v['title']), + 'description': v['description'], + 'tags': [k], + 'responses': { + 200: { + 'description': 'successful operation' + }, + 400: { + 'description': 'Invalid ID supplied' + }, + 404: { + 'description': 'not found' + } + } + } + } + + oas['paths'] = paths + + return oas + + +def get_oas(cfg, version='3.0'): + """ + Stub to generate OpenAPI Document + + :param cfg: configuration object + :param version: version of OpenAPI (default 3.0) + + :returns: OpenAPI definition YAML dict + """ + + if version == '3.0': + return get_oas_30(cfg) + else: + raise RuntimeError('OpenAPI version not supported') + + +@click.command() +@click.pass_context +@click.option('--config', '-c', 'config_file', help='configuration file') +def generate_openapi_document(ctx, config_file): + """Generate OpenAPI Document""" + + if config_file is None: + raise click.ClickException('--config/-c required') + with open(config_file) as ff: + s = yaml.load(ff) + click.echo(yaml.dump(get_oas(s), default_flow_style=False)) diff --git a/pygeoapi/provider/csv_.py b/pygeoapi/provider/csv_.py index 78370a9..56860ec 100644 --- a/pygeoapi/provider/csv_.py +++ b/pygeoapi/provider/csv_.py @@ -55,53 +55,61 @@ class CSVProvider(BaseProvider): BaseProvider.__init__(self, name, data, id_field) - def _load(self, startindex=0, count=10, resulttype='results', + def _load(self, startindex=0, limit=10, resulttype='results', identifier=None): """ Load CSV data :param startindex: starting record to return (default 0) - :param count: number of records to return (default 10) - :param resulttype: return results or hit count (default results) + :param limit: number of records to return (default 10) + :param resulttype: return results or hit limit (default results) :returns: dict of GeoJSON FeatureCollection """ + found = False + result = None feature_collection = { 'type': 'FeatureCollection', 'features': [] } + with open(self.data) as ff: LOGGER.debug('Serializing DictReader') data_ = csv.DictReader(ff) if resulttype == 'hits': - LOGGER('Retrurning hits only') + LOGGER('Returning hits only') feature_collection['numberMatched'] = len(list(data_)) return feature_collection LOGGER.debug('Slicing CSV rows') - for row in itertools.islice(data_, startindex, startindex+count): + for row in itertools.islice(data_, startindex, startindex+limit): feature = {'type': 'Feature'} feature['ID'] = row.pop('id') feature['geometry'] = mapping(wkt.loads(row.pop('geom'))) feature['properties'] = row if identifier is not None and feature['ID'] == identifier: - return feature + found = True + result = feature feature_collection['features'].append(feature) + if identifier is not None and not found: + return None + elif identifier is not None and found: + return result return feature_collection - def query(self, startindex=0, count=10, resulttype='results'): + def query(self, startindex=0, limit=10, resulttype='results'): """ CSV query :param startindex: starting record to return (default 0) - :param count: number of records to return (default 10) - :param resulttype: return results or hit count (default results) + :param limit: number of records to return (default 10) + :param resulttype: return results or hit limit (default results) :returns: dict of GeoJSON FeatureCollection """ - return self._load(startindex, count, resulttype) + return self._load(startindex, limit, resulttype) def get(self, identifier): """ diff --git a/pygeoapi/provider/elasticsearch_.py b/pygeoapi/provider/elasticsearch_.py index b674ae3..5c9d08e 100644 --- a/pygeoapi/provider/elasticsearch_.py +++ b/pygeoapi/provider/elasticsearch_.py @@ -65,13 +65,13 @@ class ElasticsearchProvider(BaseProvider): LOGGER.debug('Connecting to Elasticsearch') self.es = Elasticsearch(self.es_host) - def query(self, startindex=0, count=10, resulttype='results'): + def query(self, startindex=0, limit=10, resulttype='results'): """ query Elasticsearch index :param startindex: starting record to return (default 0) - :param count: number of records to return (default 10) - :param resulttype: return results or hit count (default results) + :param limit: number of records to return (default 10) + :param resulttype: return results or hit limit (default results) :returns: dict of 0..n GeoJSON features """ @@ -84,9 +84,9 @@ class ElasticsearchProvider(BaseProvider): LOGGER.debug('Querying Elasticsearch') if resulttype == 'hits': LOGGER.debug('hits only specified') - count = 0 + limit = 0 results = self.es.search(index=self.index_name, from_=startindex, - size=count) + size=limit) if resulttype == 'hits': feature_collection['numberMatched'] = results['hits']['total'] return feature_collection diff --git a/pygeoapi/provider/geojson.py b/pygeoapi/provider/geojson.py index 9063f8a..98304d1 100644 --- a/pygeoapi/provider/geojson.py +++ b/pygeoapi/provider/geojson.py @@ -70,8 +70,8 @@ class GeoJSONProvider(BaseProvider): Yes loading from disk, deserializing and validation happens on every request. This is not efficient. """ - if os.path.exists(self.path): - with open(self.path) as src: + if os.path.exists(self.data): + with open(self.data) as src: data = json.loads(src.read()) else: data = { @@ -85,7 +85,7 @@ class GeoJSONProvider(BaseProvider): return data - def query(self, startindex=0, count=10, resulttype='results'): + def query(self, startindex=0, limit=10, resulttype='results'): """ query the provider @@ -99,7 +99,7 @@ class GeoJSONProvider(BaseProvider): data['numberMatched'] = len(data['features']) data['features'] = [] else: - data['features'] = data['features'][startindex:startindex + count] + data['features'] = data['features'][startindex:startindex+limit] return data diff --git a/pygeoapi/static/css/default.css b/pygeoapi/static/css/default.css index 8e69e72..8ee2565 100644 --- a/pygeoapi/static/css/default.css +++ b/pygeoapi/static/css/default.css @@ -36,4 +36,4 @@ table { float: left; margin: 0px 15px 15px 0px; border: 0; -} \ No newline at end of file +} diff --git a/pygeoapi/templates/service.html b/pygeoapi/templates/service.html index a0a60f3..01f3a7d 100644 --- a/pygeoapi/templates/service.html +++ b/pygeoapi/templates/service.html @@ -8,7 +8,7 @@ - +