Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -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
---
Expand Down
98 changes: 98 additions & 0 deletions owslib/ogcapi/edr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# =============================================================================
# Copyright (c) 2023 Tom Kralidis
#
# Author: Tom Kralidis <tomkralidis@gmail.com>
#
# 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 - Environmental Data Retrieval"""

def __init__(self, url: str, json_: str = None, timeout: int = 30,
headers: dict = None, auth: Authentication = None):
__doc__ = Features.__doc__ # noqa
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there a need for a custom docstring ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at this level, given the building blocks of OGC APIs (collections are the same across APIS). docstrings are more specific deeper into the endpoints.

super().__init__(url, json_, timeout, headers, auth)

def data(self) -> list:
"""
implements /collections filtered on EDR data resources
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
implements /collections filtered on EDR data resources
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/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From someone who's not familiar with the API, I suggest the docstring should start with what the function does in simple terms.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All owslib.ogcapi is documented in this manner (and much of owslib). I can cover this more fully in a subsequent PR.


@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_)
43 changes: 43 additions & 0 deletions tests/test_ogcapi_edr_pygeoapi.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions tests/test_ogcapi_features_pygeoapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ogcapi_processes_pygeoapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions tests/test_ogcapi_records_pycsw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down