Skip to content

Commit 6b23e1f

Browse files
authored
Merge pull request #5600 from Gustry/sortby
add order by expression algorithm
2 parents b8b8c1a + 133051a commit 6b23e1f

File tree

8 files changed

+294
-1
lines changed

8 files changed

+294
-1
lines changed

python/plugins/processing/tests/README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ How To
2525
To add a new test please follow these steps:
2626
2727
1. **Run the algorithm** you want to test in QGIS from the processing toolbox. If the
28-
result is a vector layer prefer GML as output for its support of mixed
28+
result is a vector layer prefer GML, with its XSD, as output for its support of mixed
2929
geometry types and good readability. Redirect output to
3030
`python/plugins/processing/tests/testdata/expected`. For input layers prefer to use what's already there in the folder `testdata`. If you need extra data, put it into `testdata/custom`.
3131

@@ -131,6 +131,8 @@ It couldn't be more trivial
131131
type: vector
132132
```
133133

134+
Add the expected GML and XSD in the folder.
135+
134136
#### Vector with tolerance
135137

136138
Sometimes different platforms create slightly different results which are
@@ -191,3 +193,10 @@ OUTPUT:
191193
- 'Geometry: Line String'
192194
- 'Feature Count: 6'
193195
```
196+
197+
Running tests locally
198+
------------------
199+
```bash
200+
ctest -V -R ProcessingQgisAlgorithmsTest
201+
```
202+
or one of the following value listed in the [CMakelists.txt](https://github.com/qgis/QGIS/blob/master/python/plugins/processing/tests/CMakeLists.txt)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ order_by_expression.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:order_by_expression fid="polys.1">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
<ogr:name>Aaaaa</ogr:name>
18+
<ogr:intval>-33</ogr:intval>
19+
<ogr:floatval>0</ogr:floatval>
20+
</ogr:order_by_expression>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:order_by_expression fid="polys.3">
24+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
25+
<ogr:name>ASDF</ogr:name>
26+
<ogr:intval>0</ogr:intval>
27+
<ogr:floatval xsi:nil="true"/>
28+
</ogr:order_by_expression>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:order_by_expression fid="polys.5">
32+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 6,1 6,-3 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
33+
<ogr:name>elim</ogr:name>
34+
<ogr:intval>2</ogr:intval>
35+
<ogr:floatval>3.33</ogr:floatval>
36+
</ogr:order_by_expression>
37+
</gml:featureMember>
38+
<gml:featureMember>
39+
<ogr:order_by_expression fid="polys.0">
40+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
41+
<ogr:name>aaaaa</ogr:name>
42+
<ogr:intval>33</ogr:intval>
43+
<ogr:floatval>44.123456</ogr:floatval>
44+
</ogr:order_by_expression>
45+
</gml:featureMember>
46+
<gml:featureMember>
47+
<ogr:order_by_expression fid="polys.4">
48+
<ogr:name xsi:nil="true"/>
49+
<ogr:intval>120</ogr:intval>
50+
<ogr:floatval>-100291.43213</ogr:floatval>
51+
</ogr:order_by_expression>
52+
</gml:featureMember>
53+
<gml:featureMember>
54+
<ogr:order_by_expression fid="polys.2">
55+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,5 2,6 3,6 3,5 2,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
56+
<ogr:name>bbaaa</ogr:name>
57+
<ogr:intval xsi:nil="true"/>
58+
<ogr:floatval>0.123</ogr:floatval>
59+
</ogr:order_by_expression>
60+
</gml:featureMember>
61+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="order_by_expression" type="ogr:order_by_expression_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="order_by_expression_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="name" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="5"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="intval" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:integer">
29+
<xs:totalDigits value="10"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
<xs:element name="floatval" nillable="true" minOccurs="0" maxOccurs="1">
34+
<xs:simpleType>
35+
<xs:restriction base="xs:decimal">
36+
</xs:restriction>
37+
</xs:simpleType>
38+
</xs:element>
39+
</xs:sequence>
40+
</xs:extension>
41+
</xs:complexContent>
42+
</xs:complexType>
43+
</xs:schema>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -3411,6 +3411,24 @@ tests:
34113411
name: expected/add_field.gml
34123412
type: vector
34133413

3414+
- algorithm: native:orderbyexpression
3415+
name: Order by expression
3416+
params:
3417+
ASCENDING: true
3418+
EXPRESSION: intval
3419+
INPUT:
3420+
name: polys.gml
3421+
type: vector
3422+
NULLS_FIRST: false
3423+
results:
3424+
OUTPUT:
3425+
name: expected/order_by_expression.gml
3426+
type: vector
3427+
compare:
3428+
fields:
3429+
__all__:
3430+
precision: 4
3431+
34143432
- algorithm: qgis:randompointsinextent
34153433
name: Random point in extent, don't check result
34163434
params:

src/analysis/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ SET(QGIS_ANALYSIS_SRCS
4646
processing/qgsalgorithmmergelines.cpp
4747
processing/qgsalgorithmminimumenclosingcircle.cpp
4848
processing/qgsalgorithmmultiparttosinglepart.cpp
49+
processing/qgsalgorithmorderbyexpression.cpp
4950
processing/qgsalgorithmorientedminimumboundingbox.cpp
5051
processing/qgsalgorithmpackage.cpp
5152
processing/qgsalgorithmpromotetomultipart.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/***************************************************************************
2+
qgsalgorithmorderbyexpression.h
3+
---------------------
4+
begin : November 2017
5+
copyright : (C) 2017 by Etienne Trimaille
6+
email : etienne dot trimaille at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
19+
#include "qgsalgorithmorderbyexpression.h"
20+
#include "qgsfeaturerequest.h"
21+
22+
23+
///@cond PRIVATE
24+
25+
QString QgsOrderByExpressionAlgorithm::name() const
26+
{
27+
return QStringLiteral( "orderbyexpression" );
28+
}
29+
30+
QString QgsOrderByExpressionAlgorithm::displayName() const
31+
{
32+
return QObject::tr( "Order by expression" );
33+
}
34+
35+
QStringList QgsOrderByExpressionAlgorithm::tags() const
36+
{
37+
return QObject::tr( "orderby,sort,expression,field" ).split( ',' );
38+
}
39+
40+
QString QgsOrderByExpressionAlgorithm::group() const
41+
{
42+
return QObject::tr( "Vector general" );
43+
}
44+
45+
void QgsOrderByExpressionAlgorithm::initAlgorithm( const QVariantMap & )
46+
{
47+
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
48+
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ) ) );
49+
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "ASCENDING" ), QObject::tr( "Ascending" ), true ) );
50+
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NULLS_FIRST" ), QObject::tr( "Nulls first" ), false ) );
51+
52+
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Output layer" ) ) );
53+
}
54+
55+
QString QgsOrderByExpressionAlgorithm::shortHelpString() const
56+
{
57+
return QObject::tr( "This algorithm sorts a vector layer according to an expression. Be careful, it might not work as expected with some providers, the order might not be kept every time." );
58+
}
59+
60+
QgsOrderByExpressionAlgorithm *QgsOrderByExpressionAlgorithm::createInstance() const
61+
{
62+
return new QgsOrderByExpressionAlgorithm();
63+
}
64+
65+
QVariantMap QgsOrderByExpressionAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
66+
{
67+
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
68+
if ( !source )
69+
return QVariantMap();
70+
71+
QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context );
72+
73+
bool ascending = parameterAsBool( parameters, QStringLiteral( "ASCENDING" ), context );
74+
bool nullsFirst = parameterAsBool( parameters, QStringLiteral( "NULLS_FIRST" ), context );
75+
76+
QString sinkId;
77+
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, sinkId, source->fields(), source->wkbType(), source->sourceCrs() ) );
78+
if ( !sink )
79+
return QVariantMap();
80+
81+
long count = source->featureCount();
82+
double step = count > 0 ? 100.0 / count : 1;
83+
int current = 0;
84+
85+
QgsFeatureRequest request;
86+
request.addOrderBy( expressionString, ascending, nullsFirst );
87+
88+
QgsFeature inFeature;
89+
QgsFeatureIterator features = source->getFeatures( request );
90+
while ( features.nextFeature( inFeature ) )
91+
{
92+
if ( feedback->isCanceled() )
93+
{
94+
break;
95+
}
96+
sink->addFeature( inFeature );
97+
feedback->setProgress( current * step );
98+
current++;
99+
}
100+
101+
QVariantMap outputs;
102+
outputs.insert( QStringLiteral( "OUTPUT" ), sinkId );
103+
return outputs;
104+
}
105+
106+
///@endcond
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/***************************************************************************
2+
qgsalgorithmorderbyexpression.h
3+
---------------------
4+
begin : November 2017
5+
copyright : (C) 2017 by Etienne Trimaille
6+
email : etienne dot trimaille at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
19+
20+
#ifndef QGSALGORITHMORDERBYEXPRESSION_H
21+
#define QGSALGORITHMORDERBYEXPRESSION_H
22+
23+
#define SIP_NO_FILE
24+
25+
#include "qgis.h"
26+
#include "qgsprocessingalgorithm.h"
27+
28+
///@cond PRIVATE
29+
30+
/**
31+
* Native order by expression algorithm.
32+
*/
33+
class QgsOrderByExpressionAlgorithm : public QgsProcessingAlgorithm
34+
{
35+
public:
36+
QgsOrderByExpressionAlgorithm() = default;
37+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
38+
QString name() const override;
39+
QString displayName() const override;
40+
virtual QStringList tags() const override;
41+
QString group() const override;
42+
QString shortHelpString() const override;
43+
QgsOrderByExpressionAlgorithm *createInstance() const override SIP_FACTORY;
44+
45+
protected:
46+
47+
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
48+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
49+
};
50+
51+
///@endcond PRIVATE
52+
53+
#endif // QGSALGORITHMORDERBYEXPRESSION_H

src/analysis/processing/qgsnativealgorithms.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "qgsalgorithmmergelines.h"
4444
#include "qgsalgorithmminimumenclosingcircle.h"
4545
#include "qgsalgorithmmultiparttosinglepart.h"
46+
#include "qgsalgorithmorderbyexpression.h"
4647
#include "qgsalgorithmorientedminimumboundingbox.h"
4748
#include "qgsalgorithmpackage.h"
4849
#include "qgsalgorithmpromotetomultipart.h"
@@ -122,6 +123,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
122123
addAlgorithm( new QgsMergeLinesAlgorithm() );
123124
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
124125
addAlgorithm( new QgsMultipartToSinglepartAlgorithm() );
126+
addAlgorithm( new QgsOrderByExpressionAlgorithm() );
125127
addAlgorithm( new QgsOrientedMinimumBoundingBoxAlgorithm() );
126128
addAlgorithm( new QgsPackageAlgorithm() );
127129
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );

0 commit comments

Comments
 (0)