From 4832993a2aa44d40a0b7e07f7530c125bdbecbee Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Sat, 16 Dec 2023 08:24:40 -0500 Subject: [PATCH 1/2] add OGC API - EDR client --- docs/source/usage.rst | 42 +++++++------ owslib/ogcapi/edr.py | 98 +++++++++++++++++++++++++++++++ tests/test_ogcapi_edr_pygeoapi.py | 43 ++++++++++++++ 3 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 owslib/ogcapi/edr.py create mode 100644 tests/test_ogcapi_edr_pygeoapi.py diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 8445a8dd..3043bae7 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -288,8 +288,6 @@ OGC API - Records - Part 1: Core 1.0 >>> w.collection_item_delete('my-catalogue', identifier) - - OGC API - Features - Part 4: Create, Replace, Update and Delete ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -300,34 +298,33 @@ OGC API - Features - Part 4: Create, Replace, Update and Delete .. code-block:: python - import json - from owslib.ogcapi.records import Records + >>> import json + >>> from owslib.ogcapi.records import Records - record_data = '/path/to/record.json' + >>> record_data = '/path/to/record.json' - url = 'http://localhost:8000' - collection_id = 'metadata:main' + >>> url = 'http://localhost:8000' + >>> collection_id = 'metadata:main' - r = Records(url) + >>> r = Records(url) - cat = r.collection(collection_id) + >>> cat = r.collection(collection_id) - with open(record_data) as fh: - data = json.load(fh) + >>> with open(record_data) as fh: + ... data = json.load(fh) - identifier = data['id'] + >>> identifier = data['id'] - r.collection_item_delete(collection_id, identifier) + >>> r.collection_item_delete(collection_id, identifier) # insert metadata - r.collection_item_create(collection_id, data) + >>> r.collection_item_create(collection_id, data) # update metadata - r.collection_item_update(collection_id, identifier, data) + >>> r.collection_item_update(collection_id, identifier, data) # delete metadata - r.collection_item_delete(collection_id, identifier) - + >>> r.collection_item_delete(collection_id, identifier) OGC API - Processes - Part 1: Core 1.0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -345,7 +342,6 @@ OGC API - Processes - Part 1: Core 1.0 >>> hello_world['title'] 'Hello World' - OGC API - Maps - Part 1: Core 1.0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -358,6 +354,16 @@ OGC API - Maps - Part 1: Core 1.0 >>> with open("output.png", "wb") as fh: ... fh.write(data.getbuffer()) +OGC API - Environmental Data Retrieval - Part 1: Core 1.0 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + >>> from owslib.ogcapi.edr import EnvironmentalDataRetrieval + >>> e = EnvironmentalDataRetrieval('http://localhost:5000') + >>> icoads_sst = m.collection('icoads-sst') + >>> data = e.query_data('icoads_sst', 'position', coords='POINT(-75 45)', parameter_names='SST,AIRT') + WCS --- diff --git a/owslib/ogcapi/edr.py b/owslib/ogcapi/edr.py new file mode 100644 index 00000000..3fa0eba0 --- /dev/null +++ b/owslib/ogcapi/edr.py @@ -0,0 +1,98 @@ +# ============================================================================= +# Copyright (c) 2023 Tom Kralidis +# +# Author: Tom Kralidis +# +# Contact email: tomkralidis@gmail.com +# ============================================================================= + +from io import BytesIO +import logging +from typing import BinaryIO + +from owslib.ogcapi.features import Features +from owslib.util import Authentication + +LOGGER = logging.getLogger(__name__) + + +class EnvironmentalDataRetrieval(Features): + """Abstraction for OGC API - Envirionmental Data Retrieval""" + + def __init__(self, url: str, json_: str = None, timeout: int = 30, + headers: dict = None, auth: Authentication = None): + __doc__ = Features.__doc__ # noqa + super().__init__(url, json_, timeout, headers, auth) + + def data(self) -> list: + """ + implements /collections filtered on EDR data resources + + @returns: `list` of filtered collections object + """ + + datas = [] + collections_ = super().collections() + + for c_ in collections_['collections']: + for l_ in c_['links']: + if 'data' in l_['rel']: + datas.append(c_['id']) + break + + return datas + + def query_data(self, collection_id: str, + query_type: str, **kwargs: dict) -> BinaryIO: + """ + implements /collection/{collectionId}/coverage/ + + @type collection_id: string + @param collection_id: id of collection + @type query_type: string + @param query_type: query type + @type bbox: list + @param bbox: list of minx,miny,maxx,maxy + @type coords: string + @param coords: well-known text geometry + @type datetime_: string + @type datetime_: string + @param datetime_: time extent or time instant + @type parameter_names: list + @param parameter_names: list of parameter names + + @returns: coverage data + """ + + kwargs_ = {} + + if 'bbox' in kwargs: + kwargs_['bbox'] = ','.join(list(map(str, kwargs['bbox']))) + if 'parameter_names' in kwargs: + kwargs_['parameter_names'] = ','.join(kwargs['parameter_names']) + + query_args_map = { + 'coords': 'coords', + 'corridor_width': 'corridor-width', + 'corridor_height': 'corridor-height', + 'crs': 'crs', + 'cube-z': 'cube-z', + 'datetime_': 'datetime', + 'height': 'height', + 'height_units': 'height-units', + 'resolution_x': 'resolution-x', + 'resolution_y': 'resolution-y', + 'resolution_z': 'resolution-z', + 'width': 'width', + 'width_units': 'width-units', + 'within': 'within', + 'z': 'z' + } + + for key, value in query_args_map.items(): + if key in kwargs: + kwargs_[value] = kwargs[key] + + path = f'collections/{collection_id}/{query_type}' + + return self._request(path=path, kwargs=kwargs_) diff --git a/tests/test_ogcapi_edr_pygeoapi.py b/tests/test_ogcapi_edr_pygeoapi.py new file mode 100644 index 00000000..898ff0fb --- /dev/null +++ b/tests/test_ogcapi_edr_pygeoapi.py @@ -0,0 +1,43 @@ +from tests.utils import service_ok + +import pytest + +from owslib.ogcapi.edr import EnvironmentalDataRetrieval + +SERVICE_URL = 'https://demo.pygeoapi.io/master/' + + +@pytest.mark.online +@pytest.mark.skipif(not service_ok(SERVICE_URL), + reason='service is unreachable') +def test_ogcapi_coverages_pygeoapi(): + w = EnvironmentalDataRetrieval(SERVICE_URL) + + assert w.url == SERVICE_URL + assert w.url_query_string is None + + api = w.api() + assert api['components']['parameters'] is not None + paths = api['paths'] + assert paths is not None + assert paths['/collections/icoads-sst'] is not None + + conformance = w.conformance() + assert len(conformance['conformsTo']) > 1 + + collections = w.collections() + assert len(collections) > 0 + + datas = w.data() + assert len(datas) > 0 + + icoads = w.collection('icoads-sst') + assert icoads['id'] == 'icoads-sst' + assert icoads['title'] == 'International Comprehensive Ocean-Atmosphere Data Set (ICOADS)' # noqa + assert icoads['description'] == 'International Comprehensive Ocean-Atmosphere Data Set (ICOADS)' # noqa + + parameter_names = icoads['parameter_names'].keys() + assert sorted(parameter_names) == ['AIRT', 'SST', 'UWND', 'VWND'] + + response = w.query_data('icoads-sst', 'position', coords='POINT(-75 45)') + assert isinstance(response, dict) From 42024c5d3e0f305a208e469e14ebd4c859320dd1 Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Mon, 18 Dec 2023 12:10:11 -0500 Subject: [PATCH 2/2] fixes --- owslib/ogcapi/edr.py | 2 +- tests/test_ogcapi_features_pygeoapi.py | 4 ++-- tests/test_ogcapi_processes_pygeoapi.py | 2 +- tests/test_ogcapi_records_pycsw.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/owslib/ogcapi/edr.py b/owslib/ogcapi/edr.py index 3fa0eba0..f910df6b 100644 --- a/owslib/ogcapi/edr.py +++ b/owslib/ogcapi/edr.py @@ -17,7 +17,7 @@ class EnvironmentalDataRetrieval(Features): - """Abstraction for OGC API - Envirionmental Data Retrieval""" + """Abstraction for OGC API - Environmental Data Retrieval""" def __init__(self, url: str, json_: str = None, timeout: int = 30, headers: dict = None, auth: Authentication = None): diff --git a/tests/test_ogcapi_features_pygeoapi.py b/tests/test_ogcapi_features_pygeoapi.py index 64293b65..38d6e52b 100644 --- a/tests/test_ogcapi_features_pygeoapi.py +++ b/tests/test_ogcapi_features_pygeoapi.py @@ -36,8 +36,8 @@ def test_ogcapi_features_pygeoapi(): assert lakes['title'] == 'Large Lakes' assert lakes['description'] == 'lakes of the world, public domain' - #lakes_queryables = w.collection_queryables('lakes') - #assert len(lakes_queryables['queryables']) == 6 + # lakes_queryables = w.collection_queryables('lakes') + # assert len(lakes_queryables['queryables']) == 6 # Minimum of limit param is 1 with pytest.raises(RuntimeError): diff --git a/tests/test_ogcapi_processes_pygeoapi.py b/tests/test_ogcapi_processes_pygeoapi.py index d09863a0..680aef2b 100644 --- a/tests/test_ogcapi_processes_pygeoapi.py +++ b/tests/test_ogcapi_processes_pygeoapi.py @@ -29,7 +29,7 @@ def test_ogcapi_processes_pygeoapi(): assert len(collections) > 0 processes = p.processes() - assert len(processes) == 5 + assert len(processes) == 6 hello_world = p.process('hello-world') assert hello_world['id'] == 'hello-world' diff --git a/tests/test_ogcapi_records_pycsw.py b/tests/test_ogcapi_records_pycsw.py index af82220e..db39aac6 100644 --- a/tests/test_ogcapi_records_pycsw.py +++ b/tests/test_ogcapi_records_pycsw.py @@ -23,7 +23,7 @@ def test_ogcapi_records_pycsw(): assert paths['/collections/{collectionId}'] is not None conformance = w.conformance() - assert len(conformance['conformsTo']) == 18 + assert len(conformance['conformsTo']) == 14 collections = w.collections() assert len(collections) > 0 @@ -40,7 +40,7 @@ def test_ogcapi_records_pycsw(): assert isinstance(w.response, dict) pycsw_cite_demo_queryables = w.collection_queryables('metadata:main') - assert len(pycsw_cite_demo_queryables['properties'].keys()) == 12 + assert len(pycsw_cite_demo_queryables['properties'].keys()) == 13 # Minimum of limit param is 1 with pytest.raises(RuntimeError):