Skip to content

update CoverageJSON compliance #2012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2025
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
2 changes: 2 additions & 0 deletions pygeoapi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,8 @@ def get_collection_schema(api: API, request: Union[APIRequest, Any],

for k, v in p.fields.items():
schema['properties'][k] = v
if v['type'] == 'float':
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a comment on why we are setting the schema different than the value returned from the provider.

schema['properties'][k]['type'] = 'number'
if v.get('format') is None:
schema['properties'][k].pop('format', None)

Expand Down
2 changes: 2 additions & 0 deletions pygeoapi/api/itemtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def get_collection_queryables(api: API, request: Union[APIRequest, Any],
'title': k,
'type': v['type']
}
if v['type'] == 'float':
queryables['properties'][k]['type'] = 'number'
if v.get('format') is not None:
queryables['properties'][k]['format'] = v['format']
if 'values' in v:
Expand Down
10 changes: 7 additions & 3 deletions pygeoapi/provider/rasterio_.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2024 Tom Kralidis
# Copyright (c) 2025 Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
Expand Down Expand Up @@ -79,9 +79,11 @@ def get_fields(self):

dtype2 = dtype
if dtype.startswith('float'):
dtype2 = 'number'
dtype2 = 'float'
elif dtype.startswith('int'):
dtype2 = 'integer'
elif dtype.startswith('str'):
dtype2 = 'string'

self._fields[i2] = {
'title': name,
Expand Down Expand Up @@ -306,7 +308,9 @@ def gen_covjson(self, metadata, data):

parameter = {
'type': 'Parameter',
'description': pm['description'],
'description': {
'en': pm['description']
},
'unit': {
'symbol': pm['unit_label']
},
Expand Down
44 changes: 32 additions & 12 deletions pygeoapi/provider/xarray_.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2020 Gregory Petrochenkov
# Copyright (c) 2022 Tom Kralidis
# Copyright (c) 2025 Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
Expand Down Expand Up @@ -111,9 +111,11 @@ def get_fields(self):
LOGGER.debug('Adding variable')
dtype = value.dtype
if dtype.name.startswith('float'):
dtype = 'number'
dtype = 'float'
elif dtype.name.startswith('int'):
dtype = 'integer'
elif dtype.name.startswith('str'):
dtype = 'string'

self._fields[key] = {
'type': dtype,
Expand Down Expand Up @@ -330,18 +332,35 @@ def gen_covjson(self, metadata, data, fields):
'ranges': {}
}

if (data.coords[self.x_field].size == 1 and
data.coords[self.y_field].size == 1):
LOGGER.debug('Modelling as PointSeries')
cj['domain']['axes']['x'] = {
'values': [float(data.coords[self.x_field].values)]
}
cj['domain']['axes']['y'] = {
'values': [float(data.coords[self.y_field].values)]
}
cj['domain']['domainType'] = 'PointSeries'

if self.time_field is not None:
mint, maxt = metadata['time']
cj['domain']['axes'][self.time_field] = {
'start': mint,
'stop': maxt,
'num': metadata['time_steps'],
cj['domain']['axes']['t'] = {
'values': [str(v) for v in data[self.time_field].values]
Comment on lines +339 to +348
Copy link
Member

Choose a reason for hiding this comment

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

Consider using vectorized type casting

}
cj['domain']['referencing'].append({
'coordinates': ['t'],
'system': {
'type': 'TemporalRS',
'calendar': 'Gregorian'
}
})

for key, value in selected_fields.items():
parameter = {
'type': 'Parameter',
'description': value['title'],
'description': {
'en': value['title']
},
'unit': {
'symbol': value['x-ogc-unit']
},
Expand All @@ -368,12 +387,13 @@ def gen_covjson(self, metadata, data, fields):
'shape': [metadata['height'],
metadata['width']]
}
cj['ranges'][key]['values'] = data[key].values.flatten().tolist() # noqa
cj['ranges'][key]['values'] = [
None if np.isnan(v) else v
for v in data[key].values.flatten()
]

if self.time_field is not None:
cj['ranges'][key]['axisNames'].append(
self._coverage_properties['time_axis_label']
)
cj['ranges'][key]['axisNames'].append('t')
cj['ranges'][key]['shape'].append(metadata['time_steps'])
except IndexError as err:
LOGGER.warning(err)
Expand Down
65 changes: 46 additions & 19 deletions tests/api/test_environmental_data_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Colin Blackburn <colb@bgs.ac.uk>
# Bernhard Mallinger <bernhard.mallinger@eox.at>
#
# Copyright (c) 2024 Tom Kralidis
# Copyright (c) 2025 Tom Kralidis
# Copyright (c) 2022 John A Stevenson and Colin Blackburn
#
# Permission is hereby granted, free of charge, to any person
Expand Down Expand Up @@ -87,12 +87,12 @@ def test_get_collection_edr_query(config, api_):
axes = list(data['domain']['axes'].keys())
axes.sort()
assert len(axes) == 3
assert axes == ['TIME', 'x', 'y']
assert axes == ['t', 'x', 'y']

assert data['domain']['axes']['x']['start'] == 11.0
assert data['domain']['axes']['x']['stop'] == 11.0
assert data['domain']['axes']['y']['start'] == 11.0
assert data['domain']['axes']['y']['stop'] == 11.0
assert isinstance(data['domain']['axes']['x'], dict)
assert isinstance(data['domain']['axes']['x']['values'], list)
assert data['domain']['axes']['x']['values'][0] == 11.0
assert data['domain']['axes']['y']['values'][0] == 11.0

parameters = list(data['parameters'].keys())
parameters.sort()
Expand Down Expand Up @@ -131,11 +131,19 @@ def test_get_collection_edr_query(config, api_):
assert code == HTTPStatus.OK

data = json.loads(response)
time_dict = data['domain']['axes']['TIME']
time_dict = data['domain']['axes']['t']
assert isinstance(time_dict, dict)
assert isinstance(time_dict['values'], list)

assert time_dict['start'] == '2000-02-15T16:29:05.999999999'
assert time_dict['stop'] == '2000-06-16T10:25:30.000000000'
assert time_dict['num'] == 5
t_values = [
'2000-02-15T16:29:05.999999999',
'2000-03-17T02:58:12.000000000',
'2000-04-16T13:27:18.000000000',
'2000-05-16T23:56:24.000000000',
'2000-06-16T10:25:30.000000000'
]

assert sorted(time_dict['values']) == t_values

# unbounded date range - start
req = mock_api_request({
Expand All @@ -147,11 +155,20 @@ def test_get_collection_edr_query(config, api_):
assert code == HTTPStatus.OK

data = json.loads(response)
time_dict = data['domain']['axes']['TIME']
time_dict = data['domain']['axes']['t']
assert isinstance(time_dict, dict)
assert isinstance(time_dict['values'], list)

t_values = [
'2000-01-16T06:00:00.000000000',
'2000-02-15T16:29:05.999999999',
'2000-03-17T02:58:12.000000000',
'2000-04-16T13:27:18.000000000',
'2000-05-16T23:56:24.000000000',
'2000-06-16T10:25:30.000000000'
]

assert time_dict['start'] == '2000-01-16T06:00:00.000000000'
assert time_dict['stop'] == '2000-06-16T10:25:30.000000000'
assert time_dict['num'] == 6
assert sorted(time_dict['values']) == t_values

# unbounded date range - end
req = mock_api_request({
Expand All @@ -163,11 +180,21 @@ def test_get_collection_edr_query(config, api_):
assert code == HTTPStatus.OK

data = json.loads(response)
time_dict = data['domain']['axes']['TIME']

assert time_dict['start'] == '2000-06-16T10:25:30.000000000'
assert time_dict['stop'] == '2000-12-16T01:20:05.999999996'
assert time_dict['num'] == 7
time_dict = data['domain']['axes']['t']
assert isinstance(time_dict, dict)
assert isinstance(time_dict['values'], list)

t_values = [
'2000-06-16T10:25:30.000000000',
'2000-07-16T20:54:36.000000000',
'2000-08-16T07:23:42.000000000',
'2000-09-15T17:52:48.000000000',
'2000-10-16T04:21:54.000000000',
'2000-11-15T14:51:00.000000000',
'2000-12-16T01:20:05.999999996'
]

assert sorted(time_dict['values']) == t_values

# some data
req = mock_api_request({
Expand Down
2 changes: 1 addition & 1 deletion tests/test_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ def test_django_edr_without_instance_id(django_):
# Validate CoverageJSON is returned
response_json = response.json()
assert response_json["type"] == "Coverage"
assert response_json["domain"]["domainType"] == "Grid"
assert response_json["domain"]["domainType"] == "PointSeries"