Skip to content

Commit 555b785

Browse files
authored
fix OGC API coverages subset and datetime not forwarded (#967)
* 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
1 parent 534d309 commit 555b785

File tree

3 files changed

+177
-20
lines changed

3 files changed

+177
-20
lines changed

owslib/ogcapi/coverages.py

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,42 +48,84 @@ def coverage(self, collection_id: str, **kwargs: dict) -> BinaryIO:
4848
4949
@type collection_id: string
5050
@param collection_id: id of collection
51-
@type properties: list
51+
@type properties: tuple | list
5252
@param properties: range subset
53-
@type subset: list of tuples
54-
@param subset: [(name, lower bound, upper bound)]
55-
@type scale_size: list of tuples
56-
@param scale_size: [(axis name, number)]
53+
@type subset: list or dict of tuples/lists
54+
@param subset:
55+
[(name, lower bound, upper bound)]
56+
[[name, lower bound, upper bound]]
57+
{name: (lower bound, upper bound)}
58+
{name: [lower bound, upper bound]}
59+
@type scale_size: list of tuples or dict
60+
@param scale_size: [(axis name, number)] | {axis name: number}
5761
@type scale_factor: int
5862
@param scale_factor: factor by which to scale the resulting coverage
59-
@type scale_axes: list of tuples
60-
@param scale_axes: [(axis name, number)]
63+
@type scale_axes: list of tuples or dict
64+
@param scale_axes: [(axis name, number)] | {axis name: number}
65+
@type datetime: tuple | list | str
66+
@param datetime:
67+
tuple or list of start/end datetimes, or as 'start/end' string
68+
start and end datetimes can be ".." for unbounded value
6169
6270
@returns: coverage data
6371
"""
6472

6573
kwargs_ = {}
6674

67-
if 'properties' in kwargs:
68-
kwargs_['properties'] = ','.join(
69-
[str(x) for x in kwargs['properties']])
75+
if isinstance(kwargs.get('properties'), (tuple, list)):
76+
kwargs_['properties'] = ','.join([
77+
str(x) for x in kwargs['properties']
78+
])
7079

7180
for p in ['scale_axes', 'scale_size']:
7281
if p in kwargs:
7382
p2 = p.replace('_', '-')
83+
if isinstance(kwargs[p], (tuple, list)):
84+
items = kwargs[p]
85+
elif isinstance(kwargs[p], dict):
86+
items = [
87+
(name, value)
88+
for name, value
89+
in kwargs[p].items()
90+
]
91+
else:
92+
continue
7493
kwargs_[p2] = []
75-
for s in kwargs[p2]:
76-
val = f'{s[0]}({s[1]},{s[2]})'
77-
kwargs_[p2].append(val)
78-
79-
if 'subset' in kwargs:
80-
subsets_list = []
81-
for s in kwargs['subset']:
82-
subsets_list.append(f'{s[0]}({s[1]}:{s[2]})')
83-
kwargs['subset'] = ','.join(subsets_list)
94+
kwargs_[p2] = ",".join(
95+
f'{s[0]}({s[1]})'
96+
for s in items
97+
)
8498

8599
if 'scale_factor' in kwargs:
86-
kwargs_['scale-factor'] = int(kwargs['scale_factor'])
100+
scale_f = float(kwargs['scale_factor'])
101+
scale_i = int(scale_f)
102+
if scale_i == scale_f:
103+
kwargs_['scale-factor'] = scale_i
104+
else:
105+
kwargs_['scale-factor'] = scale_f
106+
107+
if 'subset' in kwargs:
108+
subset_items = []
109+
subset_values = kwargs['subset']
110+
if isinstance(subset_values, (tuple, list)):
111+
subset_items = subset_values
112+
elif isinstance(subset_values, dict):
113+
subset_items = [
114+
(name, *values)
115+
for name, values
116+
in subset_values.items()
117+
]
118+
if subset_items:
119+
kwargs_['subset'] = ','.join([
120+
f'{s[0]}({s[1]}:{s[2]})'
121+
for s in subset_items
122+
])
123+
124+
if 'datetime' in kwargs:
125+
if isinstance(kwargs['datetime'], (tuple, list)):
126+
kwargs_['datetime'] = '/'.join(kwargs['datetime'][:2])
127+
else:
128+
kwargs_['datetime'] = str(kwargs['datetime'])
87129

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

tests/test_ogcapi_coverages.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import json
2+
import pytest
3+
4+
from owslib.ogcapi.coverages import Coverages
5+
6+
7+
class MockCoverages(Coverages):
8+
def __init__(self, *args, **kwargs):
9+
kwargs["json_"] = '{}' # avoid init API request
10+
super(MockCoverages, self).__init__(*args, **kwargs)
11+
12+
def _request(self, **kwargs):
13+
json_args = json.dumps(kwargs)
14+
return json_args.encode("utf-8")
15+
16+
17+
@pytest.mark.parametrize(
18+
["kwargs", "expect"],
19+
[
20+
(
21+
{"unknown": "dropped-param"},
22+
{}
23+
),
24+
(
25+
{"properties": ["B04"]},
26+
{"properties": "B04"},
27+
),
28+
(
29+
{"properties": ["B04", "B08"]},
30+
{"properties": "B04,B08"},
31+
),
32+
(
33+
{"scale_axes": [("Lat", 1), ("Lon", 2)]},
34+
{"scale-axes": "Lat(1),Lon(2)"},
35+
),
36+
(
37+
{"scale_axes": (("Lat", 1), ("Lon", 2))},
38+
{"scale-axes": "Lat(1),Lon(2)"},
39+
),
40+
(
41+
{"scale_axes": [["Lat", 1], ["Lon", 2]]},
42+
{"scale-axes": "Lat(1),Lon(2)"},
43+
),
44+
(
45+
{"scale_axes": {"Lat": 1, "Lon": 2}},
46+
{"scale-axes": "Lat(1),Lon(2)"},
47+
),
48+
(
49+
{"scale_size": [("Lat", 100), ("Lon", 200)]},
50+
{"scale-size": "Lat(100),Lon(200)"},
51+
),
52+
(
53+
{"scale_size": (("Lat", 100), ("Lon", 200))},
54+
{"scale-size": "Lat(100),Lon(200)"},
55+
),
56+
(
57+
{"scale_size": [["Lat", 100], ["Lon", 200]]},
58+
{"scale-size": "Lat(100),Lon(200)"},
59+
),
60+
(
61+
{"scale_size": {"Lat": 100, "Lon": 200}},
62+
{"scale-size": "Lat(100),Lon(200)"},
63+
),
64+
(
65+
{"scale_factor": 1.23},
66+
{"scale-factor": 1.23},
67+
),
68+
(
69+
{"scale_factor": 2},
70+
{"scale-factor": 2},
71+
),
72+
(
73+
{"scale_factor": 0.5},
74+
{"scale-factor": 0.5},
75+
),
76+
(
77+
{"subset": {"Lat": [10, 20], "Lon": [30, 40]}},
78+
{"subset": "Lat(10:20),Lon(30:40)"},
79+
),
80+
(
81+
{"subset": {"Lat": (10, 20), "Lon": (30, 40)}},
82+
{"subset": "Lat(10:20),Lon(30:40)"},
83+
),
84+
(
85+
{"subset": [("Lat", 10, 20), ("Lon", 30, 40)]},
86+
{"subset": "Lat(10:20),Lon(30:40)"},
87+
),
88+
(
89+
{"subset": [["Lat", 10, 20], ["Lon", 30, 40]]},
90+
{"subset": "Lat(10:20),Lon(30:40)"},
91+
),
92+
(
93+
{"datetime": ("2025-01-01", "2025-01-02")},
94+
{"datetime": "2025-01-01/2025-01-02"},
95+
),
96+
(
97+
{"datetime": ["2025-01-01", "2025-01-02"]},
98+
{"datetime": "2025-01-01/2025-01-02"},
99+
),
100+
(
101+
{"datetime": "2025-01-01/2025-01-02"},
102+
{"datetime": "2025-01-01/2025-01-02"},
103+
),
104+
]
105+
)
106+
def test_coverages_coverage_kwargs(kwargs, expect):
107+
"""
108+
Validate that additional keywords for coverages are parsed as intended.
109+
"""
110+
cov = MockCoverages("")
111+
result = cov.coverage("test", **kwargs)
112+
args = result.read()
113+
params = json.loads(args)
114+
assert params["kwargs"] == expect

tests/test_ogcapi_records_pycsw.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def test_ogcapi_records_pycsw():
6969
assert len(pycsw_cite_demo_query['features']) == 1
7070

7171

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

0 commit comments

Comments
 (0)