diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 62abb06..beb0b84 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -351,15 +351,46 @@ class API(object): if k not in reserved_fieldnames and k in p.fields.keys(): properties.append((k, v)) + LOGGER.debug('processing sort parameter') + val = args.get('sortby') + + if val is not None: + sortby = [] + sorts = val.split(',') + for s in sorts: + if ':' in s: + prop, order = s.split(':') + if order not in ['A', 'D']: + exception = { + 'code': 'InvalidParameterValue', + 'description': 'sort order should be A or D' + } + LOGGER.error(exception) + return headers_, 400, json.dumps(exception) + sortby.append({'property': prop, 'order': order}) + else: + sortby.append({'property': s, 'order': 'A'}) + for s in sortby: + if s['property'] not in p.fields.keys(): + exception = { + 'code': 'InvalidParameterValue', + 'description': 'bad sort property' + } + LOGGER.error(exception) + return headers_, 400, json.dumps(exception) + else: + sortby = [] + LOGGER.debug('Querying provider') LOGGER.debug('startindex: {}'.format(startindex)) LOGGER.debug('limit: {}'.format(limit)) LOGGER.debug('resulttype: {}'.format(resulttype)) + LOGGER.debug('sortby: {}'.format(sortby)) try: content = p.query(startindex=int(startindex), limit=int(limit), resulttype=resulttype, bbox=bbox, time=time, - properties=properties) + properties=properties, sortby=sortby) except ProviderConnectionError: exception = { 'code': 'NoApplicableCode', diff --git a/pygeoapi/provider/csv_.py b/pygeoapi/provider/csv_.py index 584c17e..4ebca60 100644 --- a/pygeoapi/provider/csv_.py +++ b/pygeoapi/provider/csv_.py @@ -109,7 +109,7 @@ class CSVProvider(BaseProvider): return feature_collection def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ CSV query @@ -119,6 +119,7 @@ class CSVProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: dict of GeoJSON FeatureCollection """ diff --git a/pygeoapi/provider/elasticsearch_.py b/pygeoapi/provider/elasticsearch_.py index 75bc863..1a4d7bc 100644 --- a/pygeoapi/provider/elasticsearch_.py +++ b/pygeoapi/provider/elasticsearch_.py @@ -98,7 +98,7 @@ class ElasticsearchProvider(BaseProvider): return fields_ def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ query Elasticsearch index @@ -108,6 +108,7 @@ class ElasticsearchProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: dict of 0..n GeoJSON features """ @@ -181,6 +182,31 @@ class ElasticsearchProvider(BaseProvider): } query['query']['bool']['filter'].append(pf) + if sortby: + LOGGER.debug('processing sortby') + query['sort'] = [] + for sort in sortby: + LOGGER.debug('processing sort object: {}'.format(sort)) + + sp = sort['property'] + + if self.fields[sp]['type'] == 'string': + LOGGER.debug('setting ES .raw on property') + sort_property = 'properties.{}.raw'.format(sp) + else: + sort_property = 'properties.{}'.format(sp) + + sort_order = 'asc' + if sort['order'] == 'D': + sort_order = 'desc' + + sort_ = { + sort_property: { + 'order': sort_order + } + } + query['sort'].append(sort_) + try: LOGGER.debug('querying Elasticsearch') if startindex + limit > 10000: diff --git a/pygeoapi/provider/geojson.py b/pygeoapi/provider/geojson.py index 61cacb2..94b07f9 100644 --- a/pygeoapi/provider/geojson.py +++ b/pygeoapi/provider/geojson.py @@ -90,7 +90,7 @@ class GeoJSONProvider(BaseProvider): return data def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ query the provider @@ -100,6 +100,7 @@ class GeoJSONProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: FeatureCollection dict of 0..n GeoJSON features """ diff --git a/pygeoapi/provider/geopackage.py b/pygeoapi/provider/geopackage.py index 9705f51..f314d12 100644 --- a/pygeoapi/provider/geopackage.py +++ b/pygeoapi/provider/geopackage.py @@ -185,7 +185,7 @@ class GeoPackageProvider(BaseProvider): self.cursor.execute("SELECT AutoGPKGStop()") def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ Query Geopackage for all the content. e,g: http://localhost:5000/collections/poi/items? @@ -197,6 +197,7 @@ class GeoPackageProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: GeoJSON FeaturesCollection """ diff --git a/pygeoapi/provider/postgresql.py b/pygeoapi/provider/postgresql.py index 94af57d..75c43a8 100644 --- a/pygeoapi/provider/postgresql.py +++ b/pygeoapi/provider/postgresql.py @@ -156,7 +156,7 @@ class PostgreSQLProvider(BaseProvider): LOGGER.debug('Table:{}'.format(self.table)) def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ Query Postgis for all the content. e,g: http://localhost:5000/collections/hotosm_bdi_waterways/items? @@ -168,6 +168,7 @@ class PostgreSQLProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: GeoJSON FeaturesCollection """ diff --git a/pygeoapi/provider/sqlite.py b/pygeoapi/provider/sqlite.py index 2aa271c..4d6ad03 100644 --- a/pygeoapi/provider/sqlite.py +++ b/pygeoapi/provider/sqlite.py @@ -151,7 +151,7 @@ class SQLiteProvider(BaseProvider): return cursor def query(self, startindex=0, limit=10, resulttype='results', - bbox=[], time=None, properties=[]): + bbox=[], time=None, properties=[], sortby=[]): """ Query Sqlite for all the content. e,g: http://localhost:5000/collections/countries/items? @@ -163,6 +163,7 @@ class SQLiteProvider(BaseProvider): :param bbox: bounding box [minx,miny,maxx,maxy] :param time: temporal (datestamp or extent) :param properties: list of tuples (name, value) + :param sortby: list of dicts (property, order) :returns: GeoJSON FeaturesCollection """ diff --git a/tests/load_es_data.py b/tests/load_es_data.py index e438235..f859384 100644 --- a/tests/load_es_data.py +++ b/tests/load_es_data.py @@ -52,6 +52,18 @@ settings = { 'properties': { 'geometry': { 'type': 'geo_shape' + }, + 'properties': { + 'properties': { + 'nameascii': { + 'type': 'text', + 'fields': { + 'raw': { + 'type': 'keyword' + } + } + } + } } } } diff --git a/tests/test_elasticsearch__provider.py b/tests/test_elasticsearch__provider.py index b6c9c2a..0dedbfd 100644 --- a/tests/test_elasticsearch__provider.py +++ b/tests/test_elasticsearch__provider.py @@ -63,6 +63,18 @@ def test_query(config): assert len(results['features']) == 1 assert results['features'][0]['ID'] == 1559804 + results = p.query(sortby=[{'property': 'nameascii', 'order': 'A'}]) + assert results['features'][0]['properties']['nameascii'] == 'Abidjan' + + results = p.query(sortby=[{'property': 'nameascii', 'order': 'D'}]) + assert results['features'][0]['properties']['nameascii'] == 'Zagreb' + + results = p.query(sortby=[{'property': 'scalerank', 'order': 'A'}]) + assert results['features'][0]['properties']['scalerank'] == 0 + + results = p.query(sortby=[{'property': 'scalerank', 'order': 'D'}]) + assert results['features'][0]['properties']['scalerank'] == 8 + def test_get(config): p = ElasticsearchProvider(config)