diff --git a/.travis.yml b/.travis.yml index 8b9fe6d..0514704 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,12 @@ language: python dist: xenial sudo: false +env: + global: + - PYGEOAPI_CONFIG=pygeoapi-config.yml + - CPLUS_INCLUDE_PATH=/usr/include/gdal + - C_INCLUDE_PATH=/usr/include/gdal + python: - "3.6" - "3.7" @@ -14,24 +20,25 @@ services: addons: postgresql: 9.6 - before_install: + - sudo apt-add-repository ppa:ubuntugis/ubuntugis-unstable -y - sudo apt-get -qq update + - sudo apt-get install -qq build-essential python3-dev python3-setuptools - sudo apt-get install -y libsqlite3-mod-spatialite pandoc devscripts - sudo apt-get install -y postgresql-9.6-postgis-2.4 - + - sudo apt-get install -y libgdal-dev gdal-bin install: - - pip install -r requirements.txt - - pip install -r requirements-dev.txt - - python setup.py install + # follow GDAL installed version for Python bindings + - pip3 install GDAL==`gdalinfo --version | cut -d' ' -f2 | cut -d',' -f1` + - pip3 install -r requirements.txt + - pip3 install -r requirements-dev.txt + - python3 setup.py install -env: - - PYGEOAPI_CONFIG=pygeoapi-config.yml before_script: - sleep 20 - - python tests/load_es_data.py tests/data/ne_110m_populated_places_simple.geojson + - python3 tests/load_es_data.py tests/data/ne_110m_populated_places_simple.geojson - pygeoapi generate-openapi-document -c pygeoapi-config.yml > pygeoapi-openapi.yml - psql -U postgres -c 'create database test' - psql -U postgres -d test -c 'create extension postgis' @@ -40,8 +47,8 @@ before_script: script: - pytest --cov=pygeoapi - find . -type f -name "*.py" | xargs flake8 - - python setup.py --long-description | rst2html5.py + - python3 setup.py --long-description | rst2html5.py after_success: - - python setup.py sdist bdist_wheel --universal + - python3 setup.py sdist bdist_wheel --universal - debuild -b -uc -us diff --git a/pygeoapi-config.yml b/pygeoapi-config.yml index 4a47324..a2be7fc 100644 --- a/pygeoapi-config.yml +++ b/pygeoapi-config.yml @@ -226,6 +226,53 @@ datasets: id_field: osm_id table: hotosm_bdi_waterways + dutch_georef_stations: + title: Dutch Georef Stations via OGR WFS + description: Locations of RD/GNSS-reference stations from Dutch Kadaster PDOK a.k.a RDInfo. Uses MapServer WFS v2 backend via OGRProvider. + keywords: + - Netherlands + - GNSS + - Surveying + - Holland + - RD + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: http://www.nationaalgeoregister.nl/geonetwork/srv/dut/catalog.search#/metadata/3ebe56dc-5f09-4fb3-b224-55c2db4ca2fd?tab=general + hreflang: nl-NL + extents: + spatial: + bbox: [50.7539, 7.21097, 53.4658, 3.37087] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: WFS + source: WFS:http://geodata.nationaalgeoregister.nl/rdinfo/wfs? + source_srs: EPSG:28992 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: +# OGR_WFS_VERSION: 1.1.0 + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: gml_id + layer: rdinfo:stations + processes: hello-world: processor: diff --git a/pygeoapi/plugin.py b/pygeoapi/plugin.py index 8a907bb..7ca39b2 100644 --- a/pygeoapi/plugin.py +++ b/pygeoapi/plugin.py @@ -38,6 +38,7 @@ PLUGINS = { 'Elasticsearch': 'pygeoapi.provider.elasticsearch_.ElasticsearchProvider', # noqa 'GeoJSON': 'pygeoapi.provider.geojson.GeoJSONProvider', 'GeoPackage': 'pygeoapi.provider.geopackage.GeoPackageProvider', + 'OGR': 'pygeoapi.provider.ogr.OGRProvider', 'PostgreSQL': 'pygeoapi.provider.postgresql.PostgreSQLProvider', 'SQLite': 'pygeoapi.provider.sqlite.SQLiteProvider' }, diff --git a/pygeoapi/provider/ogr.py b/pygeoapi/provider/ogr.py new file mode 100644 index 0000000..2ca5b5b --- /dev/null +++ b/pygeoapi/provider/ogr.py @@ -0,0 +1,531 @@ +# ================================================================= +# +# Authors: Just van den Broecke +# +# Copyright (c) 2019 Just van den Broecke +# +# 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 importlib +import logging + +from osgeo import gdal as osgeo_gdal +from osgeo import ogr as osgeo_ogr +from osgeo import osr as osgeo_osr + +from pygeoapi.provider.base import (BaseProvider) + +LOGGER = logging.getLogger(__name__) + + +class OGRProvider(BaseProvider): + """OGR Provider""" + + # To deal with some OGR Source-Driver specifics. + SOURCE_HELPERS = { + 'WFS': 'pygeoapi.provider.ogr.WFSHelper', + 'ESRI Shapefile': 'pygeoapi.provider.ogr.ShapefileHelper', + 'ESRIJSON': 'pygeoapi.provider.ogr.ESRIJSONHelper', + 'GPKG': 'pygeoapi.provider.ogr.GPKGHelper' + } + + def __init__(self, provider_def): + """ + Initialize object + + # Typical OGRProvider YAML config: + + provider: + name: OGR + data: + source_type: WFS + source: WFS:http://geodata.nationaalgeoregister.nl/rdinfo/wfs? + source_srs: EPSG:28992 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: gml_id + layer: rdinfo:stations + + + :param provider_def: provider definition + + :returns: pygeoapi.providers.ogr.OGRProvider + """ + + BaseProvider.__init__(self, provider_def) + + self.ogr = osgeo_ogr + # http://trac.osgeo.org/gdal/wiki/PythonGotchas + self.gdal = osgeo_gdal + self.gdal.UseExceptions() + LOGGER.info("Using GDAL/OGR version: %d" + % int(osgeo_gdal.VersionInfo('VERSION_NUM'))) + + # GDAL error handler function + # http://pcjericks.github.io/py-gdalogr-cookbook/gdal_general.html + def gdal_error_handler(err_class, err_num, err_msg): + err_type = { + osgeo_gdal.CE_None: 'None', + osgeo_gdal.CE_Debug: 'Debug', + osgeo_gdal.CE_Warning: 'Warning', + osgeo_gdal.CE_Failure: 'Failure', + osgeo_gdal.CE_Fatal: 'Fatal' + } + err_msg = err_msg.replace('\n', ' ') + err_class = err_type.get(err_class, 'None') + LOGGER.error('Error Number: %s, Type: %s, Msg: %s' + % (err_num, err_class, err_msg)) + + # install error handler + self.gdal.PushErrorHandler(gdal_error_handler) + LOGGER.debug('Setting OGR properties') + + self.data_def = provider_def['data'] + + # Generic GDAL/OGR options (optional) + gdal_ogr_options = self.data_def.get('gdal_ogr_options', {}) + for key in gdal_ogr_options: + self.gdal.SetConfigOption(key, str(gdal_ogr_options[key])) + + # Driver-specific options (optional) + source_options = self.data_def.get('source_options', {}) + for key in source_options: + self.gdal.SetConfigOption(key, str(source_options[key])) + + self.source_capabilities = self.data_def.get('source_capabilities', + {'paging': False}) + + self.source_srs = int(self.data_def.get('source_srs', + 'EPSG:4326').split(':')[1]) + self.target_srs = int(self.data_def.get('target_srs', + 'EPSG:4326').split(':')[1]) + + # Optional coordinate transformation inward (requests) and + # outward (responses) when the source layers and WFS3 collections + # differ in EPSG-codes. + self.transform_in = None + self.transform_out = None + if self.source_srs != self.target_srs: + source = osgeo_osr.SpatialReference() + source.ImportFromEPSG(self.source_srs) + + target = osgeo_osr.SpatialReference() + target.ImportFromEPSG(self.target_srs) + + self.transform_in = \ + osgeo_osr.CoordinateTransformation(target, source) + self.transform_out = \ + osgeo_osr.CoordinateTransformation(source, target) + + self._load_source_helper(self.data_def['source_type']) + + # Init + self.driver = None + self.conn = None + self.layer_name = provider_def.get('layer', None) + + def _open(self): + source_type = self.data_def['source_type'] + self.driver = self.ogr.GetDriverByName(source_type) + if not self.driver: + msg = 'No Driver for Source: {}'.format(source_type) + LOGGER.error(msg) + raise Exception(msg) + + self.conn = self.driver.Open(self.data_def['source'], 0) + if not self.conn: + msg = 'Cannot open OGR Source: %s' % self.data_def['source'] + LOGGER.error(msg) + raise Exception(msg) + + # Always need to disable paging immediately after Open! + if self.source_capabilities['paging']: + self.source_helper.disable_paging() + + def _close(self): + self.conn = None + LOGGER.debug('closed self.conn') + + self.driver = None + + def _get_layer(self): + if not self.conn: + self._open() + + if not self.layer_name: + # E.g. Shapefiles may not have explicitly named Layers + layer = self.conn.GetLayer(0) + else: + layer = self.conn.GetLayerByName(self.layer_name) + + if not layer: + msg = 'Cannot get Layer {} from OGR Source'.format(self.layer_name) + LOGGER.error(msg) + raise Exception(msg) + + return layer + + def get_fields(self): + """ + Get provider field information (names, types) + + :returns: dict of fields + """ + + fields = {} + try: + layer_defn = self._get_layer().GetLayerDefn() + for fld in range(layer_defn.GetFieldCount()): + field_defn = layer_defn.GetFieldDefn(fld) + fieldName = field_defn.GetName() + fieldTypeCode = field_defn.GetType() + fieldType = field_defn.GetFieldTypeName(fieldTypeCode) + fields[fieldName] = fieldType.lower() + # fieldWidth = layer_defn.GetFieldDefn(fld).GetWidth() + # GetPrecision = layer_defn.GetFieldDefn(fld).GetPrecision() + + except Exception as err: + LOGGER.error(err) + + finally: + self._close() + + return fields + + def query(self, startindex=0, limit=10, resulttype='results', + bbox=[], time=None, properties=[], sortby=[]): + """ + Query OGR source + + :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) + :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) + + :returns: dict of 0..n GeoJSON features + """ + result = None + try: + if self.source_capabilities['paging']: + self.source_helper.enable_paging(startindex, limit) + + layer = self._get_layer() + + if bbox: + LOGGER.debug('processing bbox parameter') + minx, miny, maxx, maxy = bbox + + wkt = "POLYGON (({minx} {miny},{minx} {maxy},{maxx} {maxy}," \ + "{maxx} {miny},{minx} {miny}))".format( + minx=float(minx), miny=float(miny), + maxx=float(maxx), maxy=float(maxy)) + + polygon = self.ogr.CreateGeometryFromWkt(wkt) + + if self.transform_in: + polygon.Transform(self.transform_in) + + layer.SetSpatialFilter(polygon) + + # layer.SetSpatialFilterRect( + # float(minx), float(miny), float(maxx), float(maxy)) + if resulttype == 'hits': + LOGGER.debug('hits only specified') + result = self._response_feature_hits(layer) + elif resulttype == 'results': + LOGGER.debug('results specified') + result = self._response_feature_collection(layer, limit) + else: + LOGGER.error('Invalid resulttype: %s' % resulttype) + + except Exception as err: + LOGGER.error(err) + + finally: + self._close() + + return result + + def get(self, identifier): + """ + Get Feature by id + + :param identifier: feature id + + :returns: feature collection + """ + result = None + try: + LOGGER.debug('Fetching identifier {}'.format(identifier)) + layer = self._get_layer() + + layer.SetAttributeFilter("{field} = '{id}'".format( + field=self.id_field, id=identifier)) + + ogr_feature = layer.GetNextFeature() + result = self._ogr_feature_to_json(ogr_feature) + + except Exception as err: + LOGGER.error(err) + finally: + self._close() + + return result + + def __repr__(self): + return ' {}'.format(self.data) + + def _load_source_helper(self, source_type): + """ + Loads Source Helper by name. + + :param Source type: Source type name + + :returns: Source Helper object + """ + + if source_type not in OGRProvider.SOURCE_HELPERS.keys(): + msg = 'No Helper found for OGR Source type: {}'.format(source_type) + LOGGER.exception(msg) + raise InvalidHelperError(msg) + + # Create object from full package.class name string. + source_helper_class = OGRProvider.SOURCE_HELPERS[source_type] + + packagename, classname = source_helper_class.rsplit('.', 1) + module = importlib.import_module(packagename) + class_ = getattr(module, classname) + self.source_helper = class_(self) + + def _ogr_feature_to_json(self, ogr_feature): + geom = ogr_feature.GetGeometryRef() + if self.transform_out: + # Optionally reproject the geometry + geom.Transform(self.transform_out) + + json_feature = ogr_feature.ExportToJson(as_object=True) + json_feature['id'] = json_feature['properties'].pop(self.id_field) + return json_feature + + def _response_feature_collection(self, layer, limit): + """ + Assembles output from Layer query as + GeoJSON FeatureCollection structure. + + :returns: GeoJSON FeatureCollection + """ + + feature_collection = { + 'type': 'FeatureCollection', + 'features': [] + } + + # See https://github.com/OSGeo/gdal/blob/master/autotest/ + # ogr/ogr_wfs.py#L313 + layer.ResetReading() + + ogr_feature = layer.GetNextFeature() + count = 0 + while ogr_feature is not None: + json_feature = self._ogr_feature_to_json(ogr_feature) + + feature_collection['features'].append(json_feature) + + count += 1 + if count == limit: + break + + ogr_feature = layer.GetNextFeature() + + return feature_collection + + def _response_feature_hits(self, layer): + """ + Assembles GeoJSON hits from OGR Feature count + e.g: http://localhost:5000/collections/ + hotosm_bdi_waterways/items?resulttype=hits + + :returns: GeoJSON FeaturesCollection + """ + + return { + 'type': 'FeatureCollection', + 'numberMatched': layer.GetFeatureCount(), + 'features': [] + } + + +class InvalidHelperError(Exception): + """Invalid helper""" + pass + + +class SourceHelper: + def __init__(self, provider): + """ + Initialize object + + :param provider: provider instance + + :returns: pygeoapi.providers.ogr.SourceHelper + """ + self.provider = provider + + def enable_paging(self, startindex=-1, limit=-1): + """ + Enable paged access to dataset (OGR Driver-specific) + + """ + + pass + + def disable_paging(self): + """ + Disable paged access to dataset (OGR Driver-specific) + """ + + pass + + +class GPKGHelper(SourceHelper): + + def __init__(self, provider): + """ + Initialize object + + :param provider: provider instance + + :returns: pygeoapi.providers.ogr.SourceHelper + """ + self.provider = provider + SourceHelper.__init__(self, provider) + + +class ShapefileHelper(SourceHelper): + + def __init__(self, provider): + """ + Initialize object + + :param provider: provider instance + + :returns: pygeoapi.providers.ogr.SourceHelper + """ + self.provider = provider + SourceHelper.__init__(self, provider) + + +class ESRIJSONHelper(SourceHelper): + + def __init__(self, provider): + """ + Initialize object + + :param provider: provider instance + + :returns: pygeoapi.providers.ogr.SourceHelper + """ + self.provider = provider + SourceHelper.__init__(self, provider) + + def enable_paging(self, startindex=-1, limit=-1): + """ + Enable paged access to dataset (OGR Driver-specific) + + """ + + if startindex < 0: + return + + self.provider.gdal.SetConfigOption( + 'ESRIJSON_FEATURE_SERVER_PAGING', 'ON') + self.provider.gdal.SetConfigOption( + 'OGR_ESRIJSON_START_INDEX', str(startindex)) + self.provider.gdal.SetConfigOption( + 'OGR_ESRIJSON_PAGE_SIZE', str(limit)) + + def disable_paging(self): + """ + Disable paged access to dataset (OGR Driver-specific) + """ + + self.provider.gdal.SetConfigOption( + 'ESRIJSON_FEATURE_SERVER_PAGING', None) + self.provider.gdal.SetConfigOption( + 'OGR_ESRIJSON_PAGE_SIZE', None) + + +class WFSHelper(SourceHelper): + + def __init__(self, provider): + """ + Initialize object + + :param provider: provider instance + + :returns: pygeoapi.providers.ogr.SourceHelper + """ + self.provider = provider + SourceHelper.__init__(self, provider) + + def enable_paging(self, startindex=-1, limit=-1): + """ + Enable paged access to dataset (OGR Driver-specific) + + """ + + if startindex < 0: + return + + self.provider.gdal.SetConfigOption( + 'OGR_WFS_PAGING_ALLOWED', 'ON') + self.provider.gdal.SetConfigOption( + 'OGR_WFS_BASE_START_INDEX', str(startindex)) + self.provider.gdal.SetConfigOption( + 'OGR_WFS_PAGE_SIZE', str(limit)) + + def disable_paging(self): + """ + Disable paged access to dataset (OGR Driver-specific) + """ + + self.provider.gdal.SetConfigOption( + 'OGR_WFS_PAGING_ALLOWED', None) + self.provider.gdal.SetConfigOption( + 'OGR_WFS_PAGE_SIZE', None) diff --git a/requirements-dev.txt b/requirements-dev.txt index de6f345..6cf629e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,3 +15,4 @@ pytest-env pyOpenSSL==17.5.0 ndg-httpsclient==0.4.4 pyasn1==0.4.2 +GDAL>=2.2 diff --git a/tests/data/dutch_addresses_28992.gpkg b/tests/data/dutch_addresses_28992.gpkg new file mode 100644 index 0000000..e3340b3 Binary files /dev/null and b/tests/data/dutch_addresses_28992.gpkg differ diff --git a/tests/data/dutch_addresses_4326.gpkg b/tests/data/dutch_addresses_4326.gpkg new file mode 100644 index 0000000..252bb7f Binary files /dev/null and b/tests/data/dutch_addresses_4326.gpkg differ diff --git a/tests/data/dutch_addresses_shape_28992.zip b/tests/data/dutch_addresses_shape_28992.zip new file mode 100644 index 0000000..5081ab1 Binary files /dev/null and b/tests/data/dutch_addresses_shape_28992.zip differ diff --git a/tests/data/dutch_addresses_shape_4326/inspireadressen.dbf b/tests/data/dutch_addresses_shape_4326/inspireadressen.dbf new file mode 100644 index 0000000..0f7365b Binary files /dev/null and b/tests/data/dutch_addresses_shape_4326/inspireadressen.dbf differ diff --git a/tests/data/dutch_addresses_shape_4326/inspireadressen.prj b/tests/data/dutch_addresses_shape_4326/inspireadressen.prj new file mode 100644 index 0000000..a30c00a --- /dev/null +++ b/tests/data/dutch_addresses_shape_4326/inspireadressen.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/tests/data/dutch_addresses_shape_4326/inspireadressen.shp b/tests/data/dutch_addresses_shape_4326/inspireadressen.shp new file mode 100644 index 0000000..0cd6424 Binary files /dev/null and b/tests/data/dutch_addresses_shape_4326/inspireadressen.shp differ diff --git a/tests/data/dutch_addresses_shape_4326/inspireadressen.shx b/tests/data/dutch_addresses_shape_4326/inspireadressen.shx new file mode 100644 index 0000000..9d1b013 Binary files /dev/null and b/tests/data/dutch_addresses_shape_4326/inspireadressen.shx differ diff --git a/tests/pygeoapi-test-ogr-config.yml b/tests/pygeoapi-test-ogr-config.yml new file mode 100644 index 0000000..868334d --- /dev/null +++ b/tests/pygeoapi-test-ogr-config.yml @@ -0,0 +1,328 @@ +server: + bind: + host: 0.0.0.0 + port: 5000 + url: http://localhost:5000/ + mimetype: application/json; charset=UTF-8 + encoding: utf-8 + language: en-US + cors: true + pretty_print: true + limit: 10 + # templates: /path/to/templates + +logging: + level: ERROR + #logfile: /tmp/pygeoapi.log + +metadata: + identification: + 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/ + provider: + name: Organization Name + url: https://pygeoapi.io + contact: + name: Lastname, Firstname + position: Position Title + address: Mailing Address + city: City + stateorprovince: Administrative Area + postalcode: Zip or Postal Code + country: Country + phone: +xx-xxx-xxx-xxxx + fax: +xx-xxx-xxx-xxxx + email: you@example.org + url: Contact URL + hours: Hours of Service + instructions: During hours of service. Off on weekends. + role: pointOfContact + +datasets: + + dutch_georef_stations: + title: Dutch Georef Stations via OGR WFS + description: Locations of RD/GNSS-reference stations from Dutch Kadaster PDOK a.k.a RDInfo. Uses MapServer WFS v2 backend via OGRProvider. + keywords: + - Netherlands + - GNSS + - Surveying + - Holland + - RD + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: http://www.nationaalgeoregister.nl/geonetwork/srv/dut/catalog.search#/metadata/3ebe56dc-5f09-4fb3-b224-55c2db4ca2fd?tab=general + hreflang: nl-NL + extents: + spatial: + bbox: [50.7539, 7.21097, 53.4658, 3.37087] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: WFS + source: WFS:http://geodata.nationaalgeoregister.nl/rdinfo/wfs? + source_srs: EPSG:28992 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: +# OGR_WFS_VERSION: 1.1.0 + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: gml_id + layer: rdinfo:stations + + # Warning: this layer contains about 10 million addresses, the backend WFS seems not optimized + dutch_addresses: + title: Dutch Addresses via OGR WFS + description: All Dutch addresses as derived from the key registry BAG. Uses GeoServer WFS v2 backend via OGRProvider. SLOW BACKEND! + keywords: + - Netherlands + - Addresses + - Europe + - Holland + - BAG + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: http://www.nationaalgeoregister.nl/geonetwork/srv/dut/catalog.search#/metadata/3a97fbe4-2b0d-4e9c-9644-276883400dd7 + hreflang: nl-NL + extents: + spatial: + bbox: [50.7539, 7.21097, 53.4658, 3.37087] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: WFS + source: WFS:http://geodata.nationaalgeoregister.nl/inspireadressen/wfs? + source_srs: EPSG:28992 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: +# OGR_WFS_VERSION: 2.0.0 + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: gml_id + layer: inspireadressen:inspireadressen + + utah_city_locations: + title: Cities in Utah via OGR WFS + description: Data from the state of Utah. Standard demo dataset from the deegree WFS server that is used as backend WFS. + keywords: + - USA + - deegree + - Utah + - Demo data + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: http://download.deegree.org/documentation/3.3.20/html/lightly.html#example-workspace-2-utah-webmapping-services + hreflang: en-US + extents: + spatial: + bbox: [-112.108489, 39.854053, -111.028628, 40.460098] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: WFS + source: WFS:http://demo.deegree.org/utah-workspace/services/wfs?TYPENAME=app:SGID93_LOCATION_UDOTMap_CityLocations + source_srs: EPSG:26912 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: +# OGR_WFS_VERSION: 2.0.0 + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: NAME + layer: app:SGID93_LOCATION_UDOTMap_CityLocations + + unesco_pois_italy: + title: Unesco POIs in Italy via OGR WFS + description: Unesco Points of Interest in Italy. Using GeoSolutions GeoServer WFS demo-server as backend WFS. + keywords: + - Italy + - Unesco + - Demo + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: https://mapstore2.geo-solutions.it/mapstore/#/dashboard/5593 + hreflang: en-US + extents: + spatial: + bbox: [36.0, 17.0, 46.0, 18.0] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: WFS + source: WFS:https://demo.geo-solutions.it/geoserver/wfs + source_srs: EPSG:32632 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + source_options: +# OGR_WFS_VERSION: 1.1.0 + OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN: NO + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: gml_id + layer: unesco:Unesco_point + + ogr_gpkg_poi: + title: Portuguese Points of Interest via OGR GPKG + description: Portuguese Points of Interest obtained from OpenStreetMap. Dataset includes Madeira and Azores islands. Uses GeoPackage backend via OGR provider. + keywords: + - Portugal + - POI + - Point of Interrest + - Madeira + - Azores + - OSM + - Open Street Map + - NaturaGIS + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: https://wiki.openstreetmap.org/wiki/Points_of_interest/ + hreflang: en-US + extents: + spatial: + bbox: [-31.2687 32.5898 -6.18992 42.152] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: GPKG + source: tests/data/poi_portugal.gpkg + source_srs: EPSG:4326 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: osm_id + layer: poi_portugal + + + sf_311incidents: + title: SF 311Incidents via OGR ESRI Feature Server + description: OGR Provider - ESRI Feature Server - SF 311Incidents + keywords: + - USA + - ESRI + crs: + - CRS84 + links: + - type: text/html + rel: canonical + title: information + href: http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/FeatureServer/0 + hreflang: en-US + extents: + spatial: + bbox: [-180, -90, 180, 90] + temporal: + begin: None + end: now # or empty + provider: + name: OGR + data: + source_type: ESRIJSON + source: ESRIJSON:http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/FeatureServer/0/query?where=objectid+%3D+objectid&outfields=*&f=json + source_srs: EPSG:4326 + target_srs: EPSG:4326 + source_capabilities: + paging: True + + gdal_ogr_options: + EMPTY_AS_NULL: NO + GDAL_CACHEMAX: 64 + # GDAL_HTTP_PROXY: (optional proxy) + # GDAL_PROXY_AUTH: (optional auth for remote WFS) + CPL_DEBUG: NO + + id_field: objectid + +processes: + hello-world: + processor: + name: HelloWorld diff --git a/tests/test_ogr_gpkg_provider.py b/tests/test_ogr_gpkg_provider.py new file mode 100644 index 0000000..e26b710 --- /dev/null +++ b/tests/test_ogr_gpkg_provider.py @@ -0,0 +1,251 @@ +# Needs to be run like: python3 -m pytest + +import logging + +import pytest + +from pygeoapi.provider.ogr import OGRProvider + +LOGGER = logging.getLogger(__name__) + + +@pytest.fixture() +def config_poi_portugal(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'GPKG', + 'source': './tests/data/poi_portugal.gpkg', + 'source_srs': 'EPSG:4326', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + }, + 'id_field': 'osm_id', + 'layer': 'poi_portugal' + } + + +def test_query(config_poi_portugal): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_poi_portugal) + feature_collection = p.query() + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert features is not None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_get(config_poi_portugal): + p = OGRProvider(config_poi_portugal) + result = p.get(5156778016) + assert result['id'] == 5156778016 + assert 'tourist_info' in result['properties']['fclass'] + + +# Testing with GeoPackage files with identical features +# (all 2481 addresses in Otterlo Netherlands) +# in different projections. + +@pytest.fixture() +def config_gpkg_4326(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'GPKG', + 'source': + './tests/data/dutch_addresses_4326.gpkg', + 'source_srs': 'EPSG:4326', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + }, + 'id_field': 'id' + } + + +# Note that this Shapefile is zipped, as OGR supports /vsizip/! +@pytest.fixture() +def config_gpkg_28992(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'GPKG', + 'source': + './tests/data/dutch_addresses_28992.gpkg', + 'source_srs': 'EPSG:28992', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + }, + 'id_field': 'id' + } + + +def test_get_fields_4326(config_gpkg_4326): + """Testing field types""" + p = OGRProvider(config_gpkg_4326) + results = p.get_fields() + assert results['straatnaam'] == 'string' + assert results['huisnummer'] == 'string' + + +def test_get_28992(config_gpkg_28992): + """Testing query for a specific object""" + p = OGRProvider(config_gpkg_28992) + result = p.get('inspireadressen.1747652') + assert result['id'] == 'inspireadressen.1747652' + assert 'Mosselsepad' in result['properties']['straatnaam'] + + +def test_get_4326(config_gpkg_4326): + """Testing query for a specific object""" + p = OGRProvider(config_gpkg_4326) + result = p.get('inspireadressen.1747652') + assert result['id'] == 'inspireadressen.1747652' + assert 'Mosselsepad' in result['properties']['straatnaam'] + + +def test_query_hits_28992(config_gpkg_28992): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_gpkg_28992) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits == 2481 + + +def test_query_hits_4326(config_gpkg_4326): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_gpkg_4326) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits == 2481 + + +def test_query_bbox_hits_4326(config_gpkg_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_gpkg_4326) + # feature_collection = p.query( + # bbox=[120000, 480000, 124000, 487000], resulttype='hits') + feature_collection = p.query( + bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_hits_28992(config_gpkg_28992): + """Testing query for a valid JSON object with geometry, single address""" + + p = OGRProvider(config_gpkg_28992) + # feature_collection = p.query( + # bbox=(180800, 452500, 181200, 452700), resulttype='hits') + feature_collection = p.query( + bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') + + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_28992(config_gpkg_28992): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_gpkg_28992) + # feature_collection = p.query( + # bbox=[180800, 452500, 181200, 452700], resulttype='results') + feature_collection = p.query( + bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + assert properties['straatnaam'] == 'Planken Wambuisweg' + + +def test_query_bbox_4326(config_gpkg_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_gpkg_4326) + # feature_collection = p.query( + # bbox=[180800, 452500, 181200, 452700], resulttype='results') + feature_collection = p.query( + bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + assert properties['straatnaam'] == 'Planken Wambuisweg' + + +def test_query_with_limit_28992(config_gpkg_28992): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_gpkg_28992) + feature_collection = p.query(limit=2, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 2 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_query_with_limit_4326(config_gpkg_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_gpkg_4326) + feature_collection = p.query(limit=5, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 5 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None diff --git a/tests/test_ogr_shapefile_provider.py b/tests/test_ogr_shapefile_provider.py new file mode 100644 index 0000000..50cb7af --- /dev/null +++ b/tests/test_ogr_shapefile_provider.py @@ -0,0 +1,209 @@ +# Needs to be run like: python3 -m pytest + +import logging + +import pytest + +from pygeoapi.provider.ogr import OGRProvider + +LOGGER = logging.getLogger(__name__) + + +# Testing with Shapefiles with identical features +# (all 2481 addresses in Otterlo Netherlands) +# in different projections. + +@pytest.fixture() +def config_shapefile_4326(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'ESRI Shapefile', + 'source': + './tests/data/dutch_addresses_shape_4326/inspireadressen.shp', + 'source_capabilities': { + 'paging': True + }, + }, + 'id_field': 'id' + } + + +# Note that this Shapefile is zipped, as OGR supports /vsizip/! +@pytest.fixture() +def config_shapefile_28992(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'ESRI Shapefile', + 'source': + '/vsizip/./tests/data/dutch_addresses_shape_28992.zip', + 'source_srs': 'EPSG:28992', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + }, + 'id_field': 'id' + } + + +def test_get_fields_4326(config_shapefile_4326): + """Testing field types""" + p = OGRProvider(config_shapefile_4326) + results = p.get_fields() + assert results['straatnaam'] == 'string' + assert results['huisnummer'] == 'string' + + +def test_get_28992(config_shapefile_28992): + """Testing query for a specific object""" + p = OGRProvider(config_shapefile_28992) + result = p.get('inspireadressen.1747652') + assert result['id'] == 'inspireadressen.1747652' + assert 'Mosselsepad' in result['properties']['straatnaam'] + + +def test_get_4326(config_shapefile_4326): + """Testing query for a specific object""" + p = OGRProvider(config_shapefile_4326) + result = p.get('inspireadressen.1747652') + assert result['id'] == 'inspireadressen.1747652' + assert 'Mosselsepad' in result['properties']['straatnaam'] + + +def test_query_hits_28992(config_shapefile_28992): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_shapefile_28992) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits == 2481 + + +def test_query_hits_4326(config_shapefile_4326): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_shapefile_4326) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits == 2481 + + +def test_query_bbox_hits_4326(config_shapefile_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_shapefile_4326) + # feature_collection = p.query( + # bbox=[120000, 480000, 124000, 487000], resulttype='hits') + feature_collection = p.query( + bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_hits_28992(config_shapefile_28992): + """Testing query for a valid JSON object with geometry, single address""" + + p = OGRProvider(config_shapefile_28992) + # feature_collection = p.query( + # bbox=(180800, 452500, 181200, 452700), resulttype='hits') + feature_collection = p.query( + bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') + + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_28992(config_shapefile_28992): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_shapefile_28992) + # feature_collection = p.query( + # bbox=[180800, 452500, 181200, 452700], resulttype='results') + feature_collection = p.query( + bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + assert properties['straatnaam'] == 'Planken Wambuisweg' + + +def test_query_bbox_4326(config_shapefile_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_shapefile_4326) + # feature_collection = p.query( + # bbox=[180800, 452500, 181200, 452700], resulttype='results') + feature_collection = p.query( + bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + assert properties['straatnaam'] == 'Planken Wambuisweg' + + +def test_query_with_limit_28992(config_shapefile_28992): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_shapefile_28992) + feature_collection = p.query(limit=2, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 2 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_query_with_limit_4326(config_shapefile_4326): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_shapefile_4326) + feature_collection = p.query(limit=5, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 5 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None diff --git a/tests/test_ogr_wfs_provider.py b/tests/test_ogr_wfs_provider.py new file mode 100644 index 0000000..e100cc2 --- /dev/null +++ b/tests/test_ogr_wfs_provider.py @@ -0,0 +1,335 @@ +# Needs to be run like: python3 -m pytest + +import logging + +import pytest +from pygeoapi.provider.ogr import OGRProvider + +LOGGER = logging.getLogger(__name__) + + +@pytest.fixture() +def config_MapServer_WFS(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'WFS', + 'source': 'WFS:http://geodata.nationaalgeoregister.nl/rdinfo/wfs?', + 'source_srs': 'EPSG:28992', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + 'source_options': { + 'OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN': 'NO' + }, + 'gdal_ogr_options': { + + 'GDAL_CACHEMAX': '64', + # 'GDAL_HTTP_PROXY': (optional proxy) + # 'GDAL_PROXY_AUTH': (optional auth for remote WFS) + 'CPL_DEBUG': 'NO' + }, + }, + 'id_field': 'gml_id', + 'layer': 'rdinfo:stations' + } + + +@pytest.fixture() +def config_GeoServer_WFS(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'WFS', + 'source': + 'WFS:https://geodata.nationaalgeoregister.nl' + + '/inspireadressen/wfs?', + 'source_srs': 'EPSG:28992', + 'target_srs': 'EPSG:28992', + 'source_capabilities': { + 'paging': True + }, + 'source_options': { + 'OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN': 'NO' + }, + 'gdal_ogr_options': { + + 'GDAL_CACHEMAX': '64', + # 'GDAL_HTTP_PROXY': (optional proxy) + # 'GDAL_PROXY_AUTH': (optional auth for remote WFS) + 'CPL_DEBUG': 'NO' + }, + }, + 'id_field': 'gml_id', + 'layer': 'inspireadressen:inspireadressen' + } + + +@pytest.fixture() +def config_geosol_gs_WFS(): + return { + 'name': 'OGR', + 'data': { + 'source_type': 'WFS', + 'source': + 'WFS:https://demo.geo-solutions.it/geoserver/wfs?', + 'source_srs': 'EPSG:32632', + 'target_srs': 'EPSG:4326', + 'source_capabilities': { + 'paging': True + }, + 'source_options': { + 'OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN': 'NO' + }, + 'gdal_ogr_options': { + + 'GDAL_CACHEMAX': '64', + # 'GDAL_HTTP_PROXY': (optional proxy) + # 'GDAL_PROXY_AUTH': (optional auth for remote WFS) + 'CPL_DEBUG': 'NO' + }, + }, + 'id_field': 'gml_id', + 'layer': 'unesco:Unesco_point' + } + + +def test_get_fields_gs(config_GeoServer_WFS): + """Testing field types""" + p = OGRProvider(config_GeoServer_WFS) + results = p.get_fields() + assert results['straatnaam'] == 'string' + assert results['huisnummer'] == 'integer' + + +def test_get_ms(config_MapServer_WFS): + """Testing query for a specific object""" + p = OGRProvider(config_MapServer_WFS) + result = p.get('stations.4403') + assert result['id'] == 'stations.4403' + assert '01' in result['properties']['station'] + + +def test_get_geosol_gs(config_geosol_gs_WFS): + """Testing query for a specific object""" + p = OGRProvider(config_geosol_gs_WFS) + result = p.get('Unesco_point.123') + assert result['id'] == 'Unesco_point.123' + assert 'Centro storico di San Gimignano' in result['properties']['sito'] + + +def test_get_gs(config_GeoServer_WFS): + """Testing query for a specific object""" + p = OGRProvider(config_GeoServer_WFS) + result = p.get('inspireadressen.1747652') + assert result['id'] == 'inspireadressen.1747652' + assert 'Mosselsepad' in result['properties']['straatnaam'] + + +def test_query_hits_ms(config_MapServer_WFS): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_MapServer_WFS) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits > 5000 + + +def test_query_hits_geosol_gs(config_geosol_gs_WFS): + """Testing query on entire collection for hits""" + + p = OGRProvider(config_geosol_gs_WFS) + feature_collection = p.query(resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + assert hits == 186 + + +# OK, but backend WFS takes too much time.... +# def test_query_hits_gs(config_GeoServer_WFS): +# """Testing query on entire collection for hits""" +# +# p = OGRProvider(config_GeoServer_WFS) +# feature_collection = p.query(resulttype='hits') +# assert feature_collection.get('type', None) == 'FeatureCollection' +# features = feature_collection.get('features', None) +# assert len(features) is 0 +# hits = feature_collection.get('numberMatched', None) +# assert hits is not None +# assert hits > 8000000 + + +def test_query_bbox_hits_ms(config_MapServer_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_MapServer_WFS) + # feature_collection = p.query( + # bbox=[120000, 480000, 124000, 487000], resulttype='hits') + feature_collection = p.query( + bbox=[4.874016, 52.306852, 4.932020, 52.370004], resulttype='hits') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits > 1 + + +def test_query_bbox_hits_gs(config_GeoServer_WFS): + """Testing query for a valid JSON object with geometry, single address""" + + p = OGRProvider(config_GeoServer_WFS) + feature_collection = p.query( + bbox=(180800, 452500, 181200, 452700), resulttype='hits') + # feature_collection = p.query(bbox=( + # 5.763409, 52.060197, 5.769256, 52.061976), resulttype='hits') + + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_hits_geosol_gs(config_geosol_gs_WFS): + """Testing query for a valid JSON object with geometry, single address""" + + p = OGRProvider(config_geosol_gs_WFS) + feature_collection = p.query( + bbox=(681417.0, 4849032.0, 681417.3, 4849032.3), resulttype='hits') + # feature_collection = p.query(bbox=( + # 5.763409, 52.060197, 5.769256, 52.061976), resulttype='hits') + + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) is 0 + hits = feature_collection.get('numberMatched', None) + assert hits is not None + print('hits={}'.format(hits)) + assert hits is 1 + + +def test_query_bbox_ms(config_MapServer_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_MapServer_WFS) + # feature_collection = p.query( + # bbox=[120000, 480000, 124000, 487000], resulttype='results') + feature_collection = p.query( + bbox=[4.874016, 52.306852, 4.932020, 52.370004], resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) > 0 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_query_bbox_gs(config_GeoServer_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_GeoServer_WFS) + feature_collection = p.query( + bbox=[180800, 452500, 181200, 452700], resulttype='results') + # feature_collection = p.query( + # bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + assert properties['straatnaam'] == 'Planken Wambuisweg' + + +def test_query_bbox_geosol_gs(config_geosol_gs_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_geosol_gs_WFS) + # feature_collection = p.query( + # bbox=[120000, 480000, 124000, 487000], resulttype='results') + feature_collection = p.query( + bbox=(681417.0, 4849032.0, 681417.3, 4849032.3), + resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 1 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + assert properties['sito'] == 'Centro storico di Firenze' + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_query_with_limit_ms(config_MapServer_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_MapServer_WFS) + feature_collection = p.query(limit=2, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 2 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + geometry = feature.get('geometry', None) + assert geometry is not None + + +def test_query_with_startindex(config_MapServer_WFS): + """Testing query for a valid JSON object with geometry""" + + p = OGRProvider(config_MapServer_WFS) + feature_collection = p.query(startindex=20, limit=5, resulttype='results') + assert feature_collection.get('type', None) == 'FeatureCollection' + features = feature_collection.get('features', None) + assert len(features) == 5 + hits = feature_collection.get('numberMatched', None) + assert hits is None + feature = features[0] + properties = feature.get('properties', None) + assert properties is not None + assert feature['id'] == 'stations.21' + assert '101696.68' in properties['xrd'] + geometry = feature.get('geometry', None) + assert geometry is not None +# +# # OK, but backend GeoServer PDOK WFS takes too much time.... +# # def test_query_with_limit_gs(config_GeoServer_WFS): +# # +# # p = OGRProvider(config_GeoServer_WFS) +# # feature_collection = p.query(limit=5, resulttype='results') +# # assert feature_collection.get('type', None) == 'FeatureCollection' +# # features = feature_collection.get('features', None) +# # assert len(features) == 5 +# # hits = feature_collection.get('numberMatched', None) +# # assert hits is None +# # feature = features[0] +# # properties = feature.get('properties', None) +# # assert properties is not None +# # geometry = feature.get('geometry', None) +# # assert geometry is not None