7c6993719d
* OGC API - Features Part 2 (groundwork+CRS-BBOX) from PR #1155 - contributes to issue #1128 * #1128 provide conformance class for OAPIF Part 2 in /conformance page * #1128 bitten by flake8... * #1128 configurability CRS Feature Providers with syntax, defaults and tests * #1128 configurability CRS Feature Providers refine for default values * #1128 display supported CRSs in HTML Collection template * #1128 config, mmetadata and tests for storageCRS and storageCrsCoordinateEpoch * #1128 WIP for bbox-crs parameter support * #1128 utility function and tests for default/mandatory supprted CRS list * #1128 default supported CRS adaptation to OAPIF Part 2 standard * #1128 grr flake8 whitespace * #1128 start adding full API tests OGR for bbox-crs and crs parms * #1128 fix flake8 * #1128 fix flake8 - install GDAL in workflow main for OGR tests * #1128 fix flake8 - install GDAL in workflow main for OGR tests - need pip package? * #1128 fix flake8 - install GDAL in workflow main for OGR tests - using libgdal-dev gdal-bin * #1128 fix SensorThings test for main.yml Workflow * #1128 fix SensorThings test for main.yml Workflow nr 2 * #1128 make all OGR tests working again * #1128 make all OGR tests working again - flake8 * #1128 make all OGR tests working again - GeoSolutions WFS bbox * #1128 #1155 add documentation for OGC OAPIF Part 2 CRS CRS BBOX support * #1128 #1155 refine documentation for OGC OAPIF Part 2 CRS CRS BBOX support * #1128 #1155 refine documentation to align with #1149 * #1128 #1155 rework from review OAS and pygeoapi config schema * #1128 #1155 minor: compile Re for CRS URI only once as global var * #1128 merge in changes from PR #1173 - fix missing import * WIP Ogcapi features part 2 - Support for crs query parameter (#1149) * feat(ogcapi_features_crs): start implementing crs support from ogcapi features part2 * Pass input and output CRSs WKT instead of crs transformation object * fix longs lines and blank lines * fix typo * fix import for type annotation not supported by python version * fix variable visibility in local scope * fix tabs/spaces indentations * Add support for the crs parameter to OGRProvider * make flake8 happy * Make crs transformation mechanism more consistent between PostgreSQL and OGR providers * test(util): add two test functions in util.py New functions: test_get_crs_from_uri and test_get_transform_from_crs * fix too long lines... * Update get_crs_from_uri and corresponding test function * fix(get_crs_from_uri): make the error more explicit in if wrong crs uri format * flake8 again... * Keep support for source_srs/target_srs in config for OGRProvider * revert changes made to pygeoapi-config-0.x.yml, overlap with PR 1155 * test: add test data and update test config file * Extract 'crs' and 'storage_crs' and provider level instead of collection level * feat(crs): new decorator to support coordinates transformation of feature collections * feat(crs): 'crs' query parameter for CSVProvider * test(crs): add tests for 'crs' query parameter * test: update number of collections in test_describe_collections * test: update number of collections in test_filter_dict_by_key_value * fix(crs_transform): change the crs transformation decorator Change the logic of the decorator so that it works for both functions that return FeatureCollections and for functions tha return single Features. * test: add tests for get_collection_item end-point with 'crs' parameter * fix(test_get_collection_item_crs): id as path parameter, not query parameter * test: unpack coordinates to create point geometry * feat(crs): add suuport for crs query parameter for all providers of type 'feature' * docs(crs): add documentation to illustrate use of 'crs' query parameters * docs(crs): more data access examples * fix typo and add new line * refactor: specify None as default value for crs_transform_out parameter in _sqlalchemy_to_feature method * changes for PR 1149, test_api and style formatting * CRS84 as default crs also for test_get_collection_items_crs * test(crs): test coordinates transformation implementation of PostgreSQLProvider * test(crs): move tests to test_postgresql_provider * fix test function calls * change test to ensure returned features are the same * add json format to request object * test(crs): test coordinates transformation implementation of OGRProvider * refactor(crs): make more compact get_collection_item and get_collection_items Define two new static methods in API class, to create crs_transform_wkt and setting content-crs header. These methods can be re-used in both get_collection_item and get_collection_items methods and removes code duplication. --------- Co-authored-by: Just van den Broecke <just@justobjects.nl> * #1178 fix flake8 error * #1178 use EPSG:28992 i.s.o. 32631 - fix unit test OGR Shapefile * #1174 use CRS-compliant Axis ordering for crs support * #1174 fix and honour CRS 4258disable native CRS Transform in OGR Provider - Axis ordering not honoured... * #1174 remove ADR tests rom test_util.py * #1174 enable native CRS transform again in OGR Provider * #1174 enable native CRS transform again in OGR Provider - fix config * #1174 remove support for source/target_srs in OGRProvider - enforce transforms always based on storageCRS * #1174 fix tests Postgresql Provider for Transforms * #1174 fix tests Postgresql Provider for Transforms * #1174 add tests for OGR Transformation and Axis Order * #1174 Suppress potential axis-swapping in OGR ExportToJSON * #1174 minor fix test - unassign spatialref before setgeom infeat * #1174 minor fix test - unassign spatialref before setgeom infeat - flake8 * #1174 solve CI WFS test failures with GDAL HTTP config options * #1174 bbox and bbox-crs defs local in openapi.py for CITE validators * #1174 merge master - #1152 #1203 etc * #1174 small doc changes * #1174 move GeomObject typedef to beginning of util.py * #1174 added debug logging in transform Decorator func --------- Co-authored-by: Mathieu Tachon <92298764+MTachon@users.noreply.github.com>
292 lines
11 KiB
ReStructuredText
292 lines
11 KiB
ReStructuredText
.. _plugins:
|
||
|
||
Customizing pygeoapi: plugins
|
||
=============================
|
||
|
||
In this section we will explain how pygeoapi provides plugin architecture for data providers, formatters and processes.
|
||
|
||
Plugin development requires knowledge of how to program in Python as well as Python's package/module system.
|
||
|
||
Overview
|
||
--------
|
||
|
||
pygeoapi provides a robust plugin architecture that enables developers to extend functionality. Infact,
|
||
pygeoapi itself implements numerous formats, data providers and the process functionality as plugins.
|
||
|
||
The pygeoapi architecture supports the following subsystems:
|
||
|
||
* data providers
|
||
* output formats
|
||
* processes
|
||
* process manager
|
||
|
||
The core pygeoapi plugin registry can be found in ``pygeoapi.plugin.PLUGINS``.
|
||
|
||
Each plugin type implements its relevant base class as the API contract:
|
||
|
||
* data providers: ``pygeoapi.provider.base``
|
||
* output formats: ``pygeoapi.formatter.base``
|
||
* processes: ``pygeoapi.process.base``
|
||
* process_manager: ``pygeoapi.process.manager.base``
|
||
|
||
.. todo:: link PLUGINS to API doc
|
||
|
||
Plugins can be developed outside of the pygeoapi codebase and be dynamically loaded
|
||
by way of the pygeoapi configuration. This allows your custom plugins to live outside
|
||
pygeoapi for easier maintenance of software updates.
|
||
|
||
.. note::
|
||
It is recommended to store pygeoapi plugins outside of pygeoapi for easier software
|
||
updates and package management
|
||
|
||
|
||
Connecting plugins to pygeoapi
|
||
------------------------------
|
||
|
||
The following methods are options to connect a plugin to pygeoapi:
|
||
|
||
**Option 1**: implement outside of pygeoapi and add to configuration (recommended)
|
||
|
||
* Create a Python package with the plugin code (see `Cookiecutter`_ as an example)
|
||
* Install this Python package onto your system (``python3 setup.py install``). At this point your new package
|
||
should be in the ``PYTHONPATH`` of your pygeoapi installation
|
||
* Specify the main plugin class as the ``name`` of the relevant type in the
|
||
pygeoapi configuration. For example, for a new vector data provider:
|
||
|
||
.. code-block:: yaml
|
||
|
||
providers:
|
||
- type: feature
|
||
# name may refer to an external Python class, that is loaded by pygeoapi at runtime
|
||
name: mycooldatapackage.mycoolvectordata.MyCoolVectorDataProvider
|
||
data: /path/to/file
|
||
id_field: stn_id
|
||
|
||
|
||
.. note:: The United States Geological Survey has created a Cookiecutter project for creating pygeoapi plugins. See the `pygeoapi-plugin-cookiecutter`_ project to get started.
|
||
|
||
**Option 2**: Update in core pygeoapi:
|
||
|
||
* Copy your plugin code into the pygeoapi source code directory - for example, if it is a provider plugin, copy it
|
||
to ``pygeoapi/provider``
|
||
* Update the plugin registry in ``pygeoapi/plugin.py:PLUGINS['provider']`` with the plugin's
|
||
shortname (say ``MyCoolVectorData``) and dotted path to the class (i.e. ``pygeoapi.provider.mycoolvectordata.MyCoolVectorDataProvider``)
|
||
* Specify in your dataset provider configuration as follows:
|
||
|
||
.. code-block:: yaml
|
||
|
||
providers:
|
||
- type: feature
|
||
# name may also refer to a known core pygeopai plugin
|
||
name: MyCoolVectorData
|
||
data: /path/to/file
|
||
id_field: stn_id
|
||
|
||
|
||
Customizing pygeoapi process manager
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The pygeoapi process manager may also be customized. Similarly to the provider plugins, you may use the pygeoapi
|
||
configuration's ``server.manager.name`` to indicate either the dotted path to the python package and the relevant
|
||
manager class (*i.e.* similar to option 1 above) or the name of a known core pygeoapi plugin (*i.e.*, similar to
|
||
option 2 above).
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Example: custom pygeoapi vector data provider
|
||
---------------------------------------------
|
||
|
||
Lets consider the steps for a vector data provider plugin (source code is located here: :ref:`data Provider`).
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycoolvectordata.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
from pygeoapi.provider.base import BaseProvider
|
||
|
||
class MyCoolVectorDataProvider(BaseProvider):
|
||
"""My cool vector data provider"""
|
||
|
||
def __init__(self, provider_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__(provider_def)
|
||
|
||
def get_fields(self):
|
||
|
||
# open dat file and return fields and their datatypes
|
||
return {
|
||
'field1': 'string',
|
||
'field2': 'string'
|
||
}
|
||
|
||
def query(self, offset=0, limit=10, resulttype='results',
|
||
bbox=[], datetime_=None, properties=[], sortby=[],
|
||
select_properties=[], skip_geometry=False, **kwargs):
|
||
|
||
# optionally specify the output filename pygeoapi can use as part
|
||
# of the response (HTTP Content-Disposition header)
|
||
self.filename = "my-cool-filename.dat"
|
||
|
||
# open data file (self.data) and process, return
|
||
return {
|
||
'type': 'FeatureCollection',
|
||
'features': [{
|
||
'type': 'Feature',
|
||
'id': '371',
|
||
'geometry': {
|
||
'type': 'Point',
|
||
'coordinates': [ -75, 45 ]
|
||
},
|
||
'properties': {
|
||
'stn_id': '35',
|
||
'datetime': '2001-10-30T14:24:55Z',
|
||
'value': '89.9'
|
||
}
|
||
}]
|
||
}
|
||
|
||
def get_schema():
|
||
# return a `dict` of a JSON schema (inline or reference)
|
||
return ('application/geo+json', {'$ref': 'https://geojson.org/schema/Feature.json'})
|
||
|
||
|
||
For brevity, the above code will always return the single feature of the dataset. In reality, the plugin
|
||
developer would connect to a data source with capabilities to run queries and return a relevant result set,
|
||
as well as implement the ``get`` method accordingly. As long as the plugin implements the API contract of
|
||
its base provider, all other functionality is left to the provider implementation.
|
||
|
||
Each base class documents the functions, arguments and return types required for implementation.
|
||
|
||
.. note:: You can add language support to your plugin using :ref:`these guides<language>`.
|
||
|
||
.. note:: You can let the pygeoapi core do coordinate transformation for `crs` queries using the `@crs_transform` Decorator on `query()` and `get()` methods. See :ref:`crs`.
|
||
|
||
|
||
Example: custom pygeoapi raster data provider
|
||
---------------------------------------------
|
||
|
||
Lets consider the steps for a raster data provider plugin (source code is located here: :ref:`data Provider`).
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycoolrasterdata.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
from pygeoapi.provider.base import BaseProvider
|
||
|
||
class MyCoolRasterDataProvider(BaseProvider):
|
||
"""My cool raster data provider"""
|
||
|
||
def __init__(self, provider_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__(provider_def)
|
||
self.num_bands = 4
|
||
self.axes = ['Lat', 'Long']
|
||
|
||
def get_coverage_domainset(self):
|
||
# return a CIS JSON DomainSet
|
||
|
||
def get_coverage_rangetype(self):
|
||
# return a CIS JSON RangeType
|
||
|
||
def query(self, bands=[], subsets={}, format_='json', **kwargs):
|
||
# process bands and subsets parameters
|
||
# query/extract coverage data
|
||
|
||
# optionally specify the output filename pygeoapi can use as part
|
||
of the response (HTTP Content-Disposition header)
|
||
self.filename = "my-cool-filename.dat"
|
||
|
||
if format_ == 'json':
|
||
# return a CoverageJSON representation
|
||
return {'type': 'Coverage', ...} # trimmed for brevity
|
||
else:
|
||
# return default (likely binary) representation
|
||
return bytes(112)
|
||
|
||
For brevity, the above code will always JSON for metadata and binary or CoverageJSON for the data. In reality, the plugin
|
||
developer would connect to a data source with capabilities to run queries and return a relevant result set,
|
||
As long as the plugin implements the API contract of its base provider, all other functionality is left to the provider
|
||
implementation.
|
||
|
||
Each base class documents the functions, arguments and return types required for implementation.
|
||
|
||
|
||
Example: custom pygeoapi formatter
|
||
----------------------------------
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycooljsonformat.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
import json
|
||
from pygeoapi.formatter.base import BaseFormatter
|
||
|
||
class MyCoolJSONFormatter(BaseFormatter):
|
||
"""My cool JSON formatter"""
|
||
|
||
def __init__(self, formatter_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__({'name': 'cooljson', 'geom': None})
|
||
self.mimetype = 'application/json; subtype:mycooljson'
|
||
|
||
def write(self, options={}, data=None):
|
||
"""custom writer"""
|
||
|
||
out_data {'rows': []}
|
||
|
||
for feature in data['features']:
|
||
out_data.append(feature['properties'])
|
||
|
||
return out_data
|
||
|
||
|
||
Processing plugins
|
||
------------------
|
||
|
||
Processing plugins are following the OGC API - Processes development. Given that the specification is
|
||
under development, the implementation in ``pygeoapi/process/hello_world.py`` provides a suitable example
|
||
for the time being.
|
||
|
||
|
||
Featured plugins
|
||
----------------
|
||
|
||
The following plugins provide useful examples of pygeoapi plugins implemented
|
||
by downstream applications.
|
||
|
||
.. csv-table::
|
||
:header: "Plugin(s)", "Organization/Project","Description"
|
||
:align: left
|
||
|
||
`msc-pygeoapi`_,Meteorological Service of Canada,processes for weather/climate/water data workflows
|
||
`pygeoapi-kubernetes-papermill`_,Euro Data Cube,processes for executing Jupyter notebooks via Kubernetes
|
||
`local-outlier-factor-plugin`_,Manaaki Whenua – Landcare Research,processes for local outlier detection
|
||
`ogc-edc`_,Euro Data Cube,coverage provider atop the EDC API
|
||
`nldi_xstool`_,United States Geological Survey,Water data processing
|
||
`pygeometa-plugin`_,pygeometa project,pygeometa as a service
|
||
|
||
|
||
.. _`Cookiecutter`: https://github.com/audreyfeldroy/cookiecutter-pypackage
|
||
.. _`msc-pygeoapi`: https://github.com/ECCC-MSC/msc-pygeoapi
|
||
.. _`pygeoapi-kubernetes-papermill`: https://github.com/eurodatacube/pygeoapi-kubernetes-papermill
|
||
.. _`local-outlier-factor-plugin`: https://github.com/manaakiwhenua/local-outlier-factor-plugin
|
||
.. _`ogc-edc`: https://github.com/eurodatacube/ogc-edc/tree/oapi/edc_ogc/pygeoapi
|
||
.. _`nldi_xstool`: https://code.usgs.gov/wma/nhgf/toolsteam/nldi-xstool
|
||
.. _`pygeoapi-plugin-cookiecutter`: https://code.usgs.gov/wma/nhgf/pygeoapi-plugin-cookiecutter
|
||
.. _`pygeometa-plugin`: https://geopython.github.io/pygeometa/pygeoapi-plugin
|