Skip to content

Commit

Permalink
Merge pull request #37429 from elpaso/server-api-sorting
Browse files Browse the repository at this point in the history
[feature] Server WFS3 api sorting
  • Loading branch information
elpaso committed Jun 26, 2020
2 parents b726848 + ed86956 commit e712a33
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 9 deletions.
18 changes: 18 additions & 0 deletions resources/server/api/ogc/schema.json
Expand Up @@ -374,6 +374,24 @@
"type" : "string"
}
},
"sortby" : {
"name" : "sortby",
"in" : "path",
"description" : "Sort results by the specified field name",
"required" : false,
"schema" : {
"type" : "string"
}
},
"sortdesc" : {
"name" : "sortdesc",
"in" : "path",
"description" : "Sort results in descending order, field name must be specified with 'sortby' parameter",
"required" : false,
"schema" : {
"type" : "bool"
}
},
"relations" : {
"name" : "relations",
"in" : "query",
Expand Down
2 changes: 2 additions & 0 deletions src/server/qgsserver.cpp
Expand Up @@ -306,6 +306,8 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
time.start();
}

response.clear();

// Pass the filters to the requestHandler, this is needed for the following reasons:
// Allow server request to call sendResponse plugin hook if enabled
QgsFilterResponseDecorator responseDecorator( sServerInterface->filters(), response );
Expand Down
37 changes: 35 additions & 2 deletions src/server/services/wfs3/qgswfs3handlers.cpp
Expand Up @@ -301,7 +301,7 @@ void QgsWfs3LandingPageHandler::handleRequest( const QgsServerApiContext &contex
} );
data["links"].push_back(
{
{ "href", href( context, "/api.json" )},
{ "href", href( context, "/api" )},
{ "rel", QgsServerOgcApi::relToString( QgsServerOgcApi::Rel::service_desc ) },
{ "type", QgsServerOgcApi::mimeType( QgsServerOgcApi::ContentType::OPENAPI3 ) },
{ "title", "API description" },
Expand Down Expand Up @@ -930,6 +930,20 @@ QList<QgsServerQueryStringParameter> QgsWfs3CollectionsItemsHandler::parameters(
QStringLiteral( "results" ) };
params.push_back( resultType );

// Sortby
QgsServerQueryStringParameter sortBy { QStringLiteral( "sortby" ), false,
QgsServerQueryStringParameter::Type::String,
QStringLiteral( "Sort results by the specified field" )
};
params.push_back( sortBy );

// Sortdesc
QgsServerQueryStringParameter sortDesc { QStringLiteral( "sortdesc" ), false,
QgsServerQueryStringParameter::Type::Boolean,
QStringLiteral( "Sort results in descending order, field name must be specified with 'sortby' parameter" ),
false };
params.push_back( sortDesc );

return params;
}

Expand All @@ -956,7 +970,9 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context
QStringLiteral( "bbox" ),
QStringLiteral( "bbox-crs" ),
QStringLiteral( "crs" ),
QStringLiteral( "datetime" )
QStringLiteral( "datetime" ),
QStringLiteral( "sortby" ),
QStringLiteral( "sortdesc" ),
};

json componentParameters = json::array();
Expand Down Expand Up @@ -1187,12 +1203,29 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c
// Properties (subset attributes)
const QStringList requestedProperties { params.value( QStringLiteral( "properties" ) ).toStringList( ) };

// Sorting
const QString sortBy { params.value( QStringLiteral( "sortby" ) ).toString( ) };
const bool sortDesc { params.value( QStringLiteral( "sortdesc" ) ).toBool( ) };

if ( !sortBy.isEmpty() )
{
if ( ! constPublishedFields.names().contains( QgsServerApiUtils::sanitizedFieldValue( sortBy ) ) )
{
throw QgsServerApiBadRequestException( QStringLiteral( "Invalid sortBy field '%1'" ).arg( QgsServerApiUtils::sanitizedFieldValue( sortBy ) ) );
}
}


// ////////////////////////////////////////////////////////////////////////////////////////////////////
// End of input control: inputs are valid, process the request

QgsFeatureRequest featureRequest = filteredRequest( mapLayer, context, requestedProperties );

if ( ! sortBy.isEmpty() )
{
featureRequest.setOrderBy( { { { sortBy, ! sortDesc } } } );
}

if ( ! filterRect.isNull() )
{
QgsCoordinateTransform ct( bboxCrs, mapLayer->crs(), context.project()->transformContext() );
Expand Down
29 changes: 29 additions & 0 deletions tests/src/python/test_qgsserver_api.py
Expand Up @@ -957,6 +957,35 @@ def test_wfs3_field_filters(self):
self.assertEqual(response.statusCode(), 200)
self.compareApi(request, project, 'test_wfs3_collections_items_layer1_with_short_name_eq_two.json')

def test_wfs3_sorting(self):
"""Test sorting"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
# Check not published
response = QgsBufferServerResponse()
request = QgsBufferServerRequest(
'http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?sortby=does_not_exist')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 400) # Bad request
request = QgsBufferServerRequest(
'http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?sortby=name')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 200)
self.compareApi(
request, project, 'test_wfs3_collections_items_layer1_with_short_name_sort_by_name.json')
request = QgsBufferServerRequest(
'http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?sortby=name&sortdesc=1')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 200)
self.compareApi(
request, project, 'test_wfs3_collections_items_layer1_with_short_name_sort_by_name_desc.json')
request = QgsBufferServerRequest(
'http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?sortby=name&sortdesc=0')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 200)
self.compareApi(
request, project, 'test_wfs3_collections_items_layer1_with_short_name_sort_by_name_asc.json')

def test_wfs3_collection_items_properties(self):
"""Test WFS3 API items"""
project = QgsProject()
Expand Down
45 changes: 43 additions & 2 deletions tests/testdata/qgis_server/api/test_wfs3_api_project.json
Expand Up @@ -132,6 +132,24 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
"type": "string"
},
"style": "form"
},
"sortby": {
"description": "Sort results by the specified field name",
"in": "path",
"name": "sortby",
"required": false,
"schema": {
"type": "string"
}
},
"sortdesc": {
"description": "Sort results in descending order, field name must be specified with 'sortby' parameter",
"in": "path",
"name": "sortdesc",
"required": false,
"schema": {
"type": "bool"
}
}
},
"schemas": {
Expand Down Expand Up @@ -708,6 +726,12 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
{
"$ref": "#/components/parameters/datetime"
},
{
"$ref": "#/components/parameters/sortby"
},
{
"$ref": "#/components/parameters/sortdesc"
},
{
"description": "Retrieve features filtered by: id (Integer)",
"explode": false,
Expand Down Expand Up @@ -1034,6 +1058,12 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
{
"$ref": "#/components/parameters/datetime"
},
{
"$ref": "#/components/parameters/sortby"
},
{
"$ref": "#/components/parameters/sortdesc"
},
{
"description": "Retrieve features filtered by: alias_id (Integer)",
"explode": false,
Expand Down Expand Up @@ -1371,6 +1401,12 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
{
"$ref": "#/components/parameters/datetime"
},
{
"$ref": "#/components/parameters/sortby"
},
{
"$ref": "#/components/parameters/sortdesc"
},
{
"description": "Retrieve features filtered by: id (Integer)",
"explode": false,
Expand Down Expand Up @@ -1708,6 +1744,12 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
{
"$ref": "#/components/parameters/datetime"
},
{
"$ref": "#/components/parameters/sortby"
},
{
"$ref": "#/components/parameters/sortdesc"
},
{
"description": "Retrieve features filtered by: id (Integer)",
"explode": false,
Expand Down Expand Up @@ -2034,6 +2076,5 @@ Content-Type: application/vnd.oai.openapi+json;version=3.0
"description": "Access to data (features).",
"name": "Features"
}
],
"timeStamp": "2019-07-05T12:27:07Z"
]
}
@@ -0,0 +1,72 @@
Content-Type: application/geo+json

{
"features": [
{
"geometry": {
"coordinates": [
8.203496,
44.901483
],
"type": "Point"
},
"id": 0,
"properties": {
"id": 1,
"name": "one",
"utf8nameè": "one èé"
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
8.203459,
44.901395
],
"type": "Point"
},
"id": 2,
"properties": {
"id": 3,
"name": "three",
"utf8nameè": "three èé↓"
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
8.203547,
44.901436
],
"type": "Point"
},
"id": 1,
"properties": {
"id": 2,
"name": "two",
"utf8nameè": "two àò"
},
"type": "Feature"
}
],
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/layer1_with_short_name/items.geojson?sortby=name",
"rel": "self",
"title": "Retrieve the features of the collection as GEOJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/layer1_with_short_name/items.html?sortby=name",
"rel": "alternate",
"title": "Retrieve the features of the collection as HTML",
"type": "text/html"
}
],
"numberMatched": 3,
"numberReturned": 3,
"timeStamp": "2020-06-26T11:47:36Z",
"type": "FeatureCollection"
}
@@ -0,0 +1,72 @@
Content-Type: application/geo+json

{
"features": [
{
"geometry": {
"coordinates": [
8.203496,
44.901483
],
"type": "Point"
},
"id": 0,
"properties": {
"id": 1,
"name": "one",
"utf8nameè": "one èé"
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
8.203459,
44.901395
],
"type": "Point"
},
"id": 2,
"properties": {
"id": 3,
"name": "three",
"utf8nameè": "three èé↓"
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
8.203547,
44.901436
],
"type": "Point"
},
"id": 1,
"properties": {
"id": 2,
"name": "two",
"utf8nameè": "two àò"
},
"type": "Feature"
}
],
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/layer1_with_short_name/items.geojson?sortby=name&sortdesc=0",
"rel": "self",
"title": "Retrieve the features of the collection as GEOJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/layer1_with_short_name/items.html?sortby=name&sortdesc=0",
"rel": "alternate",
"title": "Retrieve the features of the collection as HTML",
"type": "text/html"
}
],
"numberMatched": 3,
"numberReturned": 3,
"timeStamp": "2020-06-26T11:47:36Z",
"type": "FeatureCollection"
}

0 comments on commit e712a33

Please sign in to comment.