Skip to content

Commit

Permalink
Merge pull request #2 from webb-ben/main
Browse files Browse the repository at this point in the history
Separate plugin from pygeoapi master
  • Loading branch information
Kyle Onda authored Sep 9, 2021
2 parents 48faddc + 8781df5 commit 34f8634
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 1 deletion.
6 changes: 5 additions & 1 deletion pygeoapi/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM webbben/pygeoapi-river-runner
FROM geopython/pygeoapi:latest

#Add data directory
RUN mkdir /data
Expand All @@ -10,3 +10,7 @@ ADD https://www.hydroshare.org/resource/4a22e88e689949afa1cf71ae009eaf1b/data/co
COPY ./pygeoapi.config.yml /pygeoapi/local.config.yml
COPY ./schemas.opengis.net /opt/schemas.opengis.net
COPY ./pygeoapi-skin-dashboard /skin-dashboard

#Add river runner plugin
COPY ./plugin.py /pygeoapi/pygeoapi/plugin.py
COPY ./river_runner.py /pygeoapi/pygeoapi/process/river_runner.py
113 changes: 113 additions & 0 deletions pygeoapi/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# =================================================================
#
# Authors: Tom Kralidis <[email protected]>
#
# Copyright (c) 2021 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.
#
# =================================================================
"""Plugin loader"""

import importlib
import logging

LOGGER = logging.getLogger(__name__)

#: Loads provider plugins to be used by pygeoapi,\
#: formatters and processes available
PLUGINS = {
'provider': {
'CSV': 'pygeoapi.provider.csv_.CSVProvider',
'Elasticsearch': 'pygeoapi.provider.elasticsearch_.ElasticsearchProvider', # noqa
'ElasticsearchCatalogue': 'pygeoapi.provider.elasticsearch_.ElasticsearchCatalogueProvider', # noqa
'GeoJSON': 'pygeoapi.provider.geojson.GeoJSONProvider',
'OGR': 'pygeoapi.provider.ogr.OGRProvider',
'PostgreSQL': 'pygeoapi.provider.postgresql.PostgreSQLProvider',
'SQLiteGPKG': 'pygeoapi.provider.sqlite.SQLiteGPKGProvider',
'MongoDB': 'pygeoapi.provider.mongo.MongoProvider',
'FileSystem': 'pygeoapi.provider.filesystem.FileSystemProvider',
'rasterio': 'pygeoapi.provider.rasterio_.RasterioProvider',
'xarray': 'pygeoapi.provider.xarray_.XarrayProvider',
'MVT': 'pygeoapi.provider.mvt.MVTProvider',
'TinyDBCatalogue': 'pygeoapi.provider.tinydb_.TinyDBCatalogueProvider',
'SensorThings': 'pygeoapi.provider.sensorthings.SensorThingsProvider',
'xarray-edr': 'pygeoapi.provider.xarray_edr.XarrayEDRProvider'
},
'formatter': {
'CSV': 'pygeoapi.formatter.csv_.CSVFormatter'
},
'process': {
'RiverRunner': 'pygeoapi.process.river_runner.RiverRunnerProcessor',
'HelloWorld': 'pygeoapi.process.hello_world.HelloWorldProcessor'
},
'process_manager': {
'Dummy': 'pygeoapi.process.manager.dummy.DummyManager',
'TinyDB': 'pygeoapi.process.manager.tinydb_.TinyDBManager'
}
}


def load_plugin(plugin_type, plugin_def):
"""
loads plugin by name
:param plugin_type: type of plugin (provider, formatter)
:param plugin_def: plugin definition
:returns: plugin object
"""

name = plugin_def['name']

if plugin_type not in PLUGINS.keys():
msg = 'Plugin type {} not found'.format(plugin_type)
LOGGER.exception(msg)
raise InvalidPluginError(msg)

plugin_list = PLUGINS[plugin_type]

LOGGER.debug('Plugins: {}'.format(plugin_list))

if '.' not in name and name not in plugin_list.keys():
msg = 'Plugin {} not found'.format(name)
LOGGER.exception(msg)
raise InvalidPluginError(msg)

if '.' in name: # dotted path
packagename, classname = name.rsplit('.', 1)
else: # core formatter
packagename, classname = plugin_list[name].rsplit('.', 1)

LOGGER.debug('package name: {}'.format(packagename))
LOGGER.debug('class name: {}'.format(classname))

module = importlib.import_module(packagename)
class_ = getattr(module, classname)
plugin = class_(plugin_def)

return plugin


class InvalidPluginError(Exception):
"""Invalid plugin"""
pass
200 changes: 200 additions & 0 deletions pygeoapi/river_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# =================================================================
#
# Authors: Benjamin Webb <[email protected]>
#
# Copyright (c) 2021 Benjamin Webb
#
# 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 os
import logging

from pygeoapi.util import yaml_load
from pygeoapi.plugin import load_plugin
from pygeoapi.process.base import BaseProcessor, ProcessorExecuteError


LOGGER = logging.getLogger(__name__)
CONFIG_ = ''

with open(os.getenv('PYGEOAPI_CONFIG'), encoding='utf8') as fh:
CONFIG_ = yaml_load(fh)

PROVIDER_DEF = CONFIG_['resources']['merit']['providers'][0]
P = 'properties'
#: Process metadata and description
PROCESS_METADATA = {
'version': '0.1.0',
'id': 'river-runner',
'title': {
'en': 'River Runner'
},
'description': {
'en': 'A simple process that takes a lat/long as input, and returns '
'it back as output. Intended to demonstrate a simple '
'process with a single literal input.'
},
'keywords': ['river runner', 'rivers'],
'links': [{
'type': 'text/html',
'rel': 'canonical',
'title': 'information',
'href': 'https://example.org/process',
'hreflang': 'en-US'
}],
'inputs': {
'bbox': {
'title': 'Boundary Box',
'description': 'A set of four coordinates',
'schema': {
'type': 'object',
},
'minOccurs': 0,
'maxOccurs': 1,
'metadata': None, # TODO how to use?
'keywords': ['coordinates', 'geography']
},
'lat': {
'title': 'Latitude',
'description': 'Latitude of a point',
'schema': {
'type': 'number',
},
'minOccurs': 0,
'maxOccurs': 1,
'metadata': None, # TODO how to use?
'keywords': ['coordinates', 'longitude']
},
'long': {
'title': 'Longitude',
'description': 'Longitude of a point',
'schema': {
'type': 'number',
},
'minOccurs': 0,
'maxOccurs': 1,
'metadata': None, # TODO how to use?
'keywords': ['coordinates', 'latitude']
}
},
'outputs': {
'echo': {
'title': 'Feature Collection',
'description': 'A geoJSON Feature Collection of River Runner',
'schema': {
'type': 'Object',
'contentMediaType': 'application/json'
}
}
},
'example': {
'inputs': {
'bbox': [-86.2, 39.7, -86.15, 39.75]
}
}
}


class RiverRunnerProcessor(BaseProcessor):
"""River Runner Processor example"""

def __init__(self, processor_def):
"""
Initialize object
:param processor_def: provider definition
:returns: pygeoapi.process.river_runner.RiverRunnerProcessor
"""
self.p = load_plugin('provider', PROVIDER_DEF)
super().__init__(processor_def, PROCESS_METADATA)

def execute(self, data):
mimetype = 'application/json'
if len(data.get('bbox', [])) != 4 and \
not data.get('lat', '') and \
not data.get('long', ''):
raise ProcessorExecuteError(f'Invalid input: { {{data.items()}} }')

if data.get('bbox', []):
bbox = data['bbox']
else:
bbox = self._expand_bbox((data['long'], data['lat'])*2)

value = self.p.query(bbox=bbox)
i = 1
while len(value['features']) < 1:
LOGGER.debug(f'No features in bbox {bbox}, expanding')
bbox = self._expand_bbox(bbox, e=0.125*i)
value = self.p.query(bbox=bbox)
i = i + 1

LOGGER.debug('fetching downstream features')
mh = self._compare(value, 'hydroseq', min)
out, trim = [], []
for i in (mh[P]['levelpathi'],
*mh[P]['down_levelpaths'].split(',')):
try:
i = int(float(i))
except ValueError:
LOGGER.error(f'No Downstem Rivers found {i}')
continue

down = self.p.query(
properties=[('levelpathi', i), ], limit=2000
)

out.extend(down['features'])
m = self._compare(down, 'hydroseq', min)
trim.append((m[P]['dnlevelpat'], m[P]['dnhydroseq']))

LOGGER.debug('keeping only mainstem flowpath')
trim.append((mh[P]['levelpathi'], mh[P]['hydroseq']))
outm = []
for seg in out:
for i in trim:
if seg[P]['levelpathi'] == i[0] and \
seg[P]['hydroseq'] <= i[1]:
outm.append(seg)

value['features'] = outm
outputs = {
'id': 'echo',
'value': value
}
return mimetype, outputs

def _compare(self, fc, prop, dir):
val = fc['features'][0]
for f in fc['features']:
if dir(f[P][prop], val[P][prop]) != val[P][prop]:
val = f
return val

def _expand_bbox(self, bbox, e=0.125):
return [b + e if i < 2 else b - e
for (i, b) in enumerate(bbox)]

def __repr__(self):
return '<RiverRunnerProcessor> {}'.format(self.name)

0 comments on commit 34f8634

Please sign in to comment.