Skip to content

Commit

Permalink
Create Intersection process
Browse files Browse the repository at this point in the history
  • Loading branch information
webb-ben committed Jun 30, 2023
1 parent d96e788 commit 1df6065
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 1 deletion.
3 changes: 2 additions & 1 deletion pygeoapi/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
'CSV': 'pygeoapi.formatter.csv_.CSVFormatter'
},
'process': {
'HelloWorld': 'pygeoapi.process.hello_world.HelloWorldProcessor'
'HelloWorld': 'pygeoapi.process.hello_world.HelloWorldProcessor',
'Intersector': 'pygeoapi.process.intersect.IntersectionProcessor'
},
'process_manager': {
'Dummy': 'pygeoapi.process.manager.dummy.DummyManager',
Expand Down
193 changes: 193 additions & 0 deletions pygeoapi/process/intersect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# =================================================================
#
# Authors: Benjamin Webb <bwebb@lincolninst.edu>
#
# Copyright (c) 2023 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 requests import get
from shapely import speedups

from pygeofilter.parsers.cql_json import parse as parse_cql_json

from pygeoapi.plugin import load_plugin
from pygeoapi.process.base import BaseProcessor, ProcessorExecuteError
from pygeoapi.util import (yaml_load, get_provider_default,
filter_dict_by_key_value)


LOGGER = logging.getLogger(__name__)
LOGGER.debug('Shapely speedups {}'.format(speedups.enabled))

with open(os.getenv('PYGEOAPI_CONFIG'), encoding='utf8') as fh:
CONFIG = yaml_load(fh)
COLLECTIONS = filter_dict_by_key_value(CONFIG['resources'],
'type', 'collection')
# TODO: Filter collections for those that support CQL
COLLLECTION_NAMES = list(COLLECTIONS.keys())


PROCESS_DEF = CONFIG['resources']['intersector']
PROCESS_DEF.update({
'version': '0.1.0',
'id': 'intersector',
'inputs': {
'url': {
'title': {
'en': 'Feature URL'
},
'description': {
'en': 'URL of valid feature geoJSON'
},
'keywords': {
'en': ['geojson', 'feature', 'url']
},
'schema': {
'type': 'string',
'default': None
},
'minOccurs': 1,
'maxOccurs': 1,
'metadata': None, # TODO how to use?
},
'collection': {
'title': {
'en': 'Feature Collection'
},
'description': {
'en': 'Feature Collection'
},
'keywords': {
'en': ['OGC API', 'collection']
},
'schema': {
'type': 'string',
'example': next(COLLLECTION_NAMES),
'enum': COLLLECTION_NAMES
},
'minOccurs': 1,
'maxOccurs': 1,
'metadata': None, # TODO how to use?
}
},
'outputs': {
'path': {
'title': {
'en': 'FeatureCollection'
},
'description': {
'en': 'A geoJSON FeatureCollection of the '\
'path generated by the intersection process'
},
'schema': {
'type': 'object',
'contentMediaType': 'application/json'
}
}
},
'example': {
'inputs': {
'url': 'https://demo.pygeoapi.io/master/collections/obs/items/238',
'collection': next(COLLLECTION_NAMES)
}
}
})


class IntersectionProcessor(BaseProcessor):
"""Intersection Processor"""

def __init__(self, processor_def):
"""
Initialize object
:param processor_def: provider definition
:returns: pygeoapi.process.intersect.Intersector
"""
LOGGER.debug('IntersectionProcesser init')
super().__init__(processor_def, PROCESS_DEF)

def execute(self, data):
"""
Execute Intersection Process
:param data: processor arguments
:returns: 'application/json'
"""
mimetype = 'application/json'

if not data.get('url') or not data.get('collection'):
raise ProcessorExecuteError(f'Invalid input: {data.items()}')
feature_url = data['url']
collection = data['collection']

LOGGER.debug(f'Fetching {feature_url}')
params = {'f': 'json'}
feature = get(feature_url, params=params).json()
if not feature.get('geometry'):
raise ProcessorExecuteError(f'Invalid geoJSON: {feature.items()}')

for cname, c in COLLECTIONS.items():
if str(collection) != cname:
continue

p = get_provider_default(c['providers'])
provider = load_plugin('provider', p)
LOGGER.debug(f'Intesecting {cname} with backend {provider}')
outputs = self._intersect(feature, provider)

LOGGER.debug('Returning response')
return mimetype, outputs

def _intersect(self, feature, provider):
"""
Private Function: Use feature to run CQL intersection
:param feature: feature
:param provider: OGC API Provider definition
:returns: List of GeoJSON Features
"""
filter_ = parse_cql_json({
'intersects': [
{
'property': 'shape'
},
feature['geometry']
]
})
LOGGER.debug(f'Making CQL query: {filter_}')
_ = provider.query(resulttype='hits', filterq=filter_)
fc = provider.query(limit=_['numberMatched'], filterq=filter_)

LOGGER.info(f'Returning {fc["numberReturned"]} intersections')
return fc

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

0 comments on commit 1df6065

Please sign in to comment.