Skip to content

Commit

Permalink
fix OGC API coverages subset and datetime not forwarded (#967)
Browse files Browse the repository at this point in the history
* fix OGC API coverages subset not forwarded

* add OGC API coverages datetime support

* add list type for coverages datetime

* fix more OGC API Coverages params + add unittest for them

* patch online marker to fix CI

* undo gitignore .run dir
  • Loading branch information
fmigneault authored Jan 21, 2025
1 parent 534d309 commit 555b785
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 20 deletions.
82 changes: 62 additions & 20 deletions owslib/ogcapi/coverages.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,84 @@ def coverage(self, collection_id: str, **kwargs: dict) -> BinaryIO:
@type collection_id: string
@param collection_id: id of collection
@type properties: list
@type properties: tuple | list
@param properties: range subset
@type subset: list of tuples
@param subset: [(name, lower bound, upper bound)]
@type scale_size: list of tuples
@param scale_size: [(axis name, number)]
@type subset: list or dict of tuples/lists
@param subset:
[(name, lower bound, upper bound)]
[[name, lower bound, upper bound]]
{name: (lower bound, upper bound)}
{name: [lower bound, upper bound]}
@type scale_size: list of tuples or dict
@param scale_size: [(axis name, number)] | {axis name: number}
@type scale_factor: int
@param scale_factor: factor by which to scale the resulting coverage
@type scale_axes: list of tuples
@param scale_axes: [(axis name, number)]
@type scale_axes: list of tuples or dict
@param scale_axes: [(axis name, number)] | {axis name: number}
@type datetime: tuple | list | str
@param datetime:
tuple or list of start/end datetimes, or as 'start/end' string
start and end datetimes can be ".." for unbounded value
@returns: coverage data
"""

kwargs_ = {}

if 'properties' in kwargs:
kwargs_['properties'] = ','.join(
[str(x) for x in kwargs['properties']])
if isinstance(kwargs.get('properties'), (tuple, list)):
kwargs_['properties'] = ','.join([
str(x) for x in kwargs['properties']
])

for p in ['scale_axes', 'scale_size']:
if p in kwargs:
p2 = p.replace('_', '-')
if isinstance(kwargs[p], (tuple, list)):
items = kwargs[p]
elif isinstance(kwargs[p], dict):
items = [
(name, value)
for name, value
in kwargs[p].items()
]
else:
continue
kwargs_[p2] = []
for s in kwargs[p2]:
val = f'{s[0]}({s[1]},{s[2]})'
kwargs_[p2].append(val)

if 'subset' in kwargs:
subsets_list = []
for s in kwargs['subset']:
subsets_list.append(f'{s[0]}({s[1]}:{s[2]})')
kwargs['subset'] = ','.join(subsets_list)
kwargs_[p2] = ",".join(
f'{s[0]}({s[1]})'
for s in items
)

if 'scale_factor' in kwargs:
kwargs_['scale-factor'] = int(kwargs['scale_factor'])
scale_f = float(kwargs['scale_factor'])
scale_i = int(scale_f)
if scale_i == scale_f:
kwargs_['scale-factor'] = scale_i
else:
kwargs_['scale-factor'] = scale_f

if 'subset' in kwargs:
subset_items = []
subset_values = kwargs['subset']
if isinstance(subset_values, (tuple, list)):
subset_items = subset_values
elif isinstance(subset_values, dict):
subset_items = [
(name, *values)
for name, values
in subset_values.items()
]
if subset_items:
kwargs_['subset'] = ','.join([
f'{s[0]}({s[1]}:{s[2]})'
for s in subset_items
])

if 'datetime' in kwargs:
if isinstance(kwargs['datetime'], (tuple, list)):
kwargs_['datetime'] = '/'.join(kwargs['datetime'][:2])
else:
kwargs_['datetime'] = str(kwargs['datetime'])

path = f'collections/{collection_id}/coverage'

Expand Down
114 changes: 114 additions & 0 deletions tests/test_ogcapi_coverages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import json
import pytest

from owslib.ogcapi.coverages import Coverages


class MockCoverages(Coverages):
def __init__(self, *args, **kwargs):
kwargs["json_"] = '{}' # avoid init API request
super(MockCoverages, self).__init__(*args, **kwargs)

def _request(self, **kwargs):
json_args = json.dumps(kwargs)
return json_args.encode("utf-8")


@pytest.mark.parametrize(
["kwargs", "expect"],
[
(
{"unknown": "dropped-param"},
{}
),
(
{"properties": ["B04"]},
{"properties": "B04"},
),
(
{"properties": ["B04", "B08"]},
{"properties": "B04,B08"},
),
(
{"scale_axes": [("Lat", 1), ("Lon", 2)]},
{"scale-axes": "Lat(1),Lon(2)"},
),
(
{"scale_axes": (("Lat", 1), ("Lon", 2))},
{"scale-axes": "Lat(1),Lon(2)"},
),
(
{"scale_axes": [["Lat", 1], ["Lon", 2]]},
{"scale-axes": "Lat(1),Lon(2)"},
),
(
{"scale_axes": {"Lat": 1, "Lon": 2}},
{"scale-axes": "Lat(1),Lon(2)"},
),
(
{"scale_size": [("Lat", 100), ("Lon", 200)]},
{"scale-size": "Lat(100),Lon(200)"},
),
(
{"scale_size": (("Lat", 100), ("Lon", 200))},
{"scale-size": "Lat(100),Lon(200)"},
),
(
{"scale_size": [["Lat", 100], ["Lon", 200]]},
{"scale-size": "Lat(100),Lon(200)"},
),
(
{"scale_size": {"Lat": 100, "Lon": 200}},
{"scale-size": "Lat(100),Lon(200)"},
),
(
{"scale_factor": 1.23},
{"scale-factor": 1.23},
),
(
{"scale_factor": 2},
{"scale-factor": 2},
),
(
{"scale_factor": 0.5},
{"scale-factor": 0.5},
),
(
{"subset": {"Lat": [10, 20], "Lon": [30, 40]}},
{"subset": "Lat(10:20),Lon(30:40)"},
),
(
{"subset": {"Lat": (10, 20), "Lon": (30, 40)}},
{"subset": "Lat(10:20),Lon(30:40)"},
),
(
{"subset": [("Lat", 10, 20), ("Lon", 30, 40)]},
{"subset": "Lat(10:20),Lon(30:40)"},
),
(
{"subset": [["Lat", 10, 20], ["Lon", 30, 40]]},
{"subset": "Lat(10:20),Lon(30:40)"},
),
(
{"datetime": ("2025-01-01", "2025-01-02")},
{"datetime": "2025-01-01/2025-01-02"},
),
(
{"datetime": ["2025-01-01", "2025-01-02"]},
{"datetime": "2025-01-01/2025-01-02"},
),
(
{"datetime": "2025-01-01/2025-01-02"},
{"datetime": "2025-01-01/2025-01-02"},
),
]
)
def test_coverages_coverage_kwargs(kwargs, expect):
"""
Validate that additional keywords for coverages are parsed as intended.
"""
cov = MockCoverages("")
result = cov.coverage("test", **kwargs)
args = result.read()
params = json.loads(args)
assert params["kwargs"] == expect
1 change: 1 addition & 0 deletions tests/test_ogcapi_records_pycsw.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_ogcapi_records_pycsw():
assert len(pycsw_cite_demo_query['features']) == 1


@pytest.mark.online
@pytest.mark.parametrize("path, expected", [
('collections/foo/1', 'https://demo.pycsw.org/cite/collections/foo/1'),
('collections/foo/https://example.org/11', 'https://demo.pycsw.org/cite/collections/foo/https://example.org/11') # noqa
Expand Down

0 comments on commit 555b785

Please sign in to comment.