Skip to content

Commit 79586f1

Browse files
init feature
1 parent 5d2d8d8 commit 79586f1

File tree

7 files changed

+133
-2
lines changed

7 files changed

+133
-2
lines changed

pygeoapi/api/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,17 @@ def describe_collections(api: API, request: APIRequest,
12821282
}
12831283
}
12841284

1285+
filter_dims = p.get_dims()
1286+
if filter_dims:
1287+
collection['filter_dims'] = {}
1288+
for key, value in filter_dims.items():
1289+
collection['filter_dims'][key] = {
1290+
'id': key,
1291+
'type': 'Dimension',
1292+
'name': value['title'],
1293+
'values': value['values']
1294+
}
1295+
12851296
for qt in p.get_query_types():
12861297
data_query = {
12871298
'link': {
@@ -1499,6 +1510,33 @@ def validate_bbox(value=None) -> list:
14991510

15001511
return bbox
15011512

1513+
def validate_filter_dims(query_string):
1514+
if not isinstance(query_string, str):
1515+
msg = 'dimension query must be string'
1516+
LOGGER.debug(msg)
1517+
raise ValueError(msg)
1518+
checked = {}
1519+
for pair in query_string.split(','):
1520+
if ':' not in pair:
1521+
msg = """filter dimension and value must be separated by a colon ':' """
1522+
LOGGER.debug(msg)
1523+
raise ValueError(msg)
1524+
1525+
key, value = map(str.strip, pair.split(':', 1))
1526+
if not key or not value:
1527+
msg = f"""Empty key or value in pair: '{pair}'"""
1528+
LOGGER.debug(msg)
1529+
raise ValueError(msg)
1530+
1531+
if key in checked:
1532+
msg = f"""Duplicate key found: '{key}'"""
1533+
LOGGER.debug(msg)
1534+
raise ValueError(msg)
1535+
1536+
checked[key] = value
1537+
1538+
return checked
1539+
15021540

15031541
def validate_datetime(resource_def, datetime_=None) -> str:
15041542
"""

pygeoapi/api/environmental_data_retrieval.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
)
5858

5959
from . import (APIRequest, API, F_COVERAGEJSON, F_HTML, F_JSON, F_JSONLD,
60-
validate_datetime, validate_bbox)
60+
validate_datetime, validate_bbox, validate_filter_dims)
6161

6262
LOGGER = logging.getLogger(__name__)
6363

@@ -298,6 +298,11 @@ def get_collection_edr_query(api: API, request: APIRequest,
298298
if isinstance(parameternames, str):
299299
parameternames = parameternames.split(',')
300300

301+
LOGGER.debug('Processing dims parameter')
302+
dims = request.params.get('dims')
303+
if dims:
304+
dims = validate_filter_dims(dims)
305+
301306
bbox = None
302307
if query_type in ['cube', 'locations']:
303308
LOGGER.debug('Processing cube bbox')
@@ -364,6 +369,7 @@ def get_collection_edr_query(api: API, request: APIRequest,
364369
format_=request.format,
365370
datetime_=datetime_,
366371
select_properties=parameternames,
372+
dims=dims,
367373
wkt=wkt,
368374
z=z,
369375
bbox=bbox,

pygeoapi/openapi.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,21 @@ def get_oas_30_parameters(cfg: dict, locale_: str):
609609
'type': 'string'
610610
}
611611
},
612+
'dims':{
613+
'name': 'dims',
614+
'in': 'query',
615+
'description': 'Allows to select dims from multi dimensional EDR',
616+
'required': False,
617+
'style': 'form',
618+
'explode': False,
619+
'schema': {
620+
'type': 'string',
621+
'items': {
622+
'type': 'any'
623+
},
624+
'format': 'dim1:value1,dim2:value2'
625+
}
626+
},
612627
'bbox': {
613628
'name': 'bbox',
614629
'in': 'query',

pygeoapi/provider/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def __init__(self, provider_def):
7474
self.properties = provider_def.get('properties', [])
7575
self.file_types = provider_def.get('file_types', [])
7676
self._fields = {}
77+
self._dims = {}
7778
self.filename = None
7879

7980
# for coverage providers

pygeoapi/provider/base_edr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def query(self, **kwargs):
9494
:param wkt: `shapely.geometry` WKT geometry
9595
:param datetime_: temporal (datestamp or extent)
9696
:param select_properties: list of parameters
97+
:param dims: dims to select data from
9798
:param z: vertical level(s)
9899
:param format_: data format of output
99100
:param bbox: bbox geometry (for cube queries)

pygeoapi/provider/xarray_.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def __init__(self, provider_def):
100100
self.axes = self._coverage_properties['axes']
101101

102102
self.get_fields()
103+
self.get_dims()
103104
except Exception as err:
104105
LOGGER.warning(err)
105106
raise ProviderConnectionError(err)
@@ -123,6 +124,30 @@ def get_fields(self):
123124

124125
return self._fields
125126

127+
def get_dims(self):
128+
fields = [self.time_field, self.x_field, self.y_field]
129+
if not self._dims:
130+
for key, value in self._data.coords.items():
131+
if key not in fields:
132+
LOGGER.debug('Adding filterable dim')
133+
dtype = value.dtype
134+
if dtype.name.startswith('float'):
135+
dtype = 'float'
136+
elif dtype.name.startswith('int'):
137+
dtype = 'int'
138+
else:
139+
dtype = 'str'
140+
LOGGER.debug(f"""key: {key} with type: {type(value.values.tolist()[0])}""")
141+
self._dims[key] = {
142+
'type': type(value.values.tolist()[0]),
143+
'title': value.attrs.get('long_name'),
144+
'x-ogc-unit': value.attrs.get('units'),
145+
'values': value.values.tolist()
146+
}
147+
148+
return self._dims
149+
150+
126151
def query(self, properties=[], subsets={}, bbox=[], bbox_crs=4326,
127152
datetime_=None, format_='json', **kwargs):
128153
"""

pygeoapi/provider/xarray_edr.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
import numpy as np
3333

34-
from pygeoapi.provider.base import ProviderNoDataError, ProviderQueryError
34+
from pygeoapi.provider.base import ProviderNoDataError, ProviderQueryError, ProviderInvalidQueryError
3535
from pygeoapi.provider.base_edr import BaseEDRProvider
3636
from pygeoapi.provider.xarray_ import (
3737
_to_datetime_string,
@@ -66,12 +66,14 @@ def position(self, **kwargs):
6666
:param wkt: `shapely.geometry` WKT geometry
6767
:param datetime_: temporal (datestamp or extent)
6868
:param select_properties: list of parameters
69+
:param dims: dict of dimensions to filter
6970
:param z: vertical level(s)
7071
:param format_: data format of output
7172
7273
:returns: coverage data as dict of CoverageJSON or native format
7374
"""
7475

76+
7577
query_params = {}
7678

7779
LOGGER.debug(f'Query parameters: {kwargs}')
@@ -96,6 +98,8 @@ def position(self, **kwargs):
9698
LOGGER.debug('Processing parameter-name')
9799
select_properties = kwargs.get('select_properties')
98100

101+
dims = kwargs.get('dims')
102+
99103
# example of fetching instance passed
100104
# TODO: apply accordingly
101105
instance = kwargs.get('instance')
@@ -114,6 +118,25 @@ def position(self, **kwargs):
114118
else:
115119
data = self._data
116120

121+
if dims:
122+
string_query = {}
123+
if isinstance(dims, dict):
124+
for coord, level in dims.items():
125+
if coord in self._dims:
126+
if self._dims[coord]['type'](level) in self._dims[coord]['values']:
127+
if self._dims[coord]['type'] == str:
128+
string_query[coord] = self._dims[coord]['type'](level)
129+
else:
130+
query_params[coord] = self._dims[coord]['type'](level)
131+
else:
132+
raise ProviderInvalidQueryError(user_msg = f"""Invalid Value '{level}' for Dimension Parameter '{coord}'. Valid Values are '{self._dims[coord]['values']}'""")
133+
134+
data = data.sel(string_query)
135+
else:
136+
raise ProviderInvalidQueryError(user_msg = f"""Invalid Dimension Parameter '{coord}'""")
137+
138+
LOGGER.debug(query_params)
139+
117140
if self.time_field in query_params:
118141
remaining_query = {
119142
key: val for key, val in query_params.items()
@@ -150,6 +173,7 @@ def position(self, **kwargs):
150173
bbox = wkt.bounds
151174
out_meta = {
152175
'bbox': [bbox[0], bbox[1], bbox[2], bbox[3]],
176+
'dims': dims,
153177
"time": time,
154178
"driver": "xarray",
155179
"height": height,
@@ -203,6 +227,26 @@ def cube(self, **kwargs):
203227
if datetime_ is not None:
204228
query_params[self.time_field] = self._make_datetime(datetime_)
205229

230+
dims = kwargs.get('dims')
231+
232+
if dims:
233+
string_query = {}
234+
if isinstance(dims, dict):
235+
for coord, level in dims.items():
236+
if coord in self._dims:
237+
if self._dims[coord]['type'](level) in self._dims[coord]['values']:
238+
if self._dims[coord]['type'] == str:
239+
string_query[coord] = self._dims[coord]['type'](level)
240+
else:
241+
query_params[coord] = self._dims[coord]['type'](level)
242+
else:
243+
raise ProviderInvalidQueryError(
244+
user_msg=f"""Invalid Value '{level}' for Dimension Parameter '{coord}'. Valid Values are '{self._dims[coord]['values']}'""")
245+
246+
data = data.sel(string_query)
247+
else:
248+
raise ProviderInvalidQueryError(user_msg=f"""Invalid Dimension Parameter '{coord}'""")
249+
206250
LOGGER.debug(f'query parameters: {query_params}')
207251
try:
208252
if select_properties:
@@ -226,6 +270,7 @@ def cube(self, **kwargs):
226270
data.coords[self.x_field].values[-1],
227271
data.coords[self.y_field].values[-1]
228272
],
273+
'dims': dims,
229274
"time": time,
230275
"driver": "xarray",
231276
"height": height,

0 commit comments

Comments
 (0)