QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
Loading...
Searching...
No Matches
qgsalgorithmaggregate.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmaggregate.h
3 ---------------------------------
4 begin : June 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson 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
19
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QString QgsAggregateAlgorithm::name() const
30{
31 return u"aggregate"_s;
32}
33
34QString QgsAggregateAlgorithm::displayName() const
35{
36 return QObject::tr( "Aggregate" );
37}
38
39QString QgsAggregateAlgorithm::shortHelpString() const
40{
41 return QObject::tr(
42 "This algorithm takes a vector or table layer and aggregates features based on a group by expression. Features for which group by expression return the same value are grouped together.\n\n"
43 "It is possible to group all source features together using constant value in group by parameter, example: NULL.\n\n"
44 "It is also possible to group features using multiple fields using Array function, example: Array(\"Field1\", \"Field2\").\n\n"
45 "Geometries (if present) are combined into one multipart geometry for each group.\n\n"
46 "Output attributes are computed depending on each given aggregate definition."
47 );
48}
49
50QString QgsAggregateAlgorithm::shortDescription() const
51{
52 return QObject::tr( "Aggregates features based on a group by expression, combining geometries (if present) into one multipart geometry for each group." );
53}
54
55Qgis::ProcessingAlgorithmDocumentationFlags QgsAggregateAlgorithm::documentationFlags() const
56{
58}
59
60QStringList QgsAggregateAlgorithm::tags() const
61{
62 return QObject::tr( "attributes,sum,mean,median,count,collect,dissolve,statistics,merge,combine,concatenate" ).split( ',' );
63}
64
65QString QgsAggregateAlgorithm::group() const
66{
67 return QObject::tr( "Vector geometry" );
68}
69
70QString QgsAggregateAlgorithm::groupId() const
71{
72 return u"vectorgeometry"_s;
73}
74
75QgsAggregateAlgorithm *QgsAggregateAlgorithm::createInstance() const
76{
77 return new QgsAggregateAlgorithm();
78}
79
80void QgsAggregateAlgorithm::initAlgorithm( const QVariantMap & )
81{
82 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Vector ) ) );
83 addParameter( new QgsProcessingParameterExpression( u"GROUP_BY"_s, QObject::tr( "Group by expression (NULL to group all features)" ), u"NULL"_s, u"INPUT"_s ) );
84 addParameter( new QgsProcessingParameterAggregate( u"AGGREGATES"_s, QObject::tr( "Aggregates" ), u"INPUT"_s ) );
85 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Aggregated" ) ) );
86}
87
88bool QgsAggregateAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
89{
90 mSource.reset( parameterAsSource( parameters, u"INPUT"_s, context ) );
91 if ( !mSource )
92 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
93
94 mGroupBy = parameterAsExpression( parameters, u"GROUP_BY"_s, context );
95
96 mDa.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
97 mDa.setEllipsoid( context.ellipsoid() );
98
99 mGroupByExpression = createExpression( mGroupBy, context );
100 mGeometryExpression = createExpression( u"collect($geometry, %1)"_s.arg( mGroupBy ), context );
101
102 const QVariantList aggregates = parameters.value( u"AGGREGATES"_s ).toList();
103 int currentAttributeIndex = 0;
104 for ( const QVariant &aggregate : aggregates )
105 {
106 const QVariantMap aggregateDef = aggregate.toMap();
107
108 const QString name = aggregateDef.value( u"name"_s ).toString();
109 if ( name.isEmpty() )
110 throw QgsProcessingException( QObject::tr( "Field name cannot be empty" ) );
111
112 const QMetaType::Type type = static_cast<QMetaType::Type>( aggregateDef.value( u"type"_s ).toInt() );
113 const QString typeName = aggregateDef.value( u"type_name"_s ).toString();
114 const QMetaType::Type subType = static_cast<QMetaType::Type>( aggregateDef.value( u"sub_type"_s ).toInt() );
115
116 const int length = aggregateDef.value( u"length"_s, 0 ).toInt();
117 const int precision = aggregateDef.value( u"precision"_s, 0 ).toInt();
118
119 mFields.append( QgsField( name, type, typeName, length, precision, QString(), subType ) );
120
121
122 const QString aggregateType = aggregateDef.value( u"aggregate"_s ).toString();
123 const QString source = aggregateDef.value( u"input"_s ).toString();
124 const QString delimiter = aggregateDef.value( u"delimiter"_s ).toString();
125
126 QString expression;
127 if ( aggregateType == "first_value"_L1 )
128 {
129 expression = source;
130 }
131 else if ( aggregateType == "last_value"_L1 )
132 {
133 expression = source;
134 mAttributesRequireLastFeature << currentAttributeIndex;
135 }
136 else if ( aggregateType == "concatenate"_L1 || aggregateType == "concatenate_unique"_L1 )
137 {
138 expression = u"%1(%2, %3, %4, %5)"_s.arg( aggregateType, source, mGroupBy, u"TRUE"_s, QgsExpression::quotedString( delimiter ) );
139 }
140 else
141 {
142 expression = u"%1(%2, %3)"_s.arg( aggregateType, source, mGroupBy );
143 }
144 mExpressions.append( createExpression( expression, context ) );
145 currentAttributeIndex++;
146 }
147
148 return true;
149}
150
151QVariantMap QgsAggregateAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
152{
153 QGS_MARK_ALGORITHM_SOURCE
154
155 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, mSource.get() );
156 mGroupByExpression.prepare( &expressionContext );
157
158 // Group features in memory layers
159 const long long count = mSource->featureCount();
160 double progressStep = count > 0 ? 50.0 / count : 1;
161 long long current = 0;
162
163 QHash<QVariantList, Group> groups;
164 QVector<QVariantList> keys; // We need deterministic order for the tests
165 QgsFeature feature;
166
167 std::vector<std::unique_ptr<QgsFeatureSink>> groupSinks;
168
169 QgsFeatureIterator it = mSource->getFeatures( QgsFeatureRequest() );
170 while ( it.nextFeature( feature ) )
171 {
172 expressionContext.setFeature( feature );
173 const QVariant groupByValue = mGroupByExpression.evaluate( &expressionContext );
174 if ( mGroupByExpression.hasEvalError() )
175 {
176 throw QgsProcessingException( QObject::tr( "Evaluation error in group by expression \"%1\": %2" ).arg( mGroupByExpression.expression(), mGroupByExpression.evalErrorString() ) );
177 }
178
179 // upgrade group by value to a list, so that we get correct behavior with the QHash
180 const QVariantList key = groupByValue.userType() == QMetaType::Type::QVariantList ? groupByValue.toList() : ( QVariantList() << groupByValue );
181
182 const auto groupIt = groups.find( key );
183 if ( groupIt == groups.end() )
184 {
185 QString id = u"memory:"_s;
186 std::unique_ptr<QgsFeatureSink> sink( QgsProcessingUtils::createFeatureSink( id, context, mSource->fields(), mSource->wkbType(), mSource->sourceCrs() ) );
187
188 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
189 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QString() ) );
190
192
193 Group group;
194 group.sink = sink.get();
195 //store ownership of sink in groupSinks, so that these get deleted automatically if an exception is raised later..
196 groupSinks.emplace_back( std::move( sink ) );
197 group.layer = layer;
198 group.firstFeature = feature;
199 group.lastFeature = feature;
200 groups[key] = std::move( group );
201 keys.append( key );
202 }
203 else
204 {
205 if ( !groupIt->sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
206 throw QgsProcessingException( writeFeatureError( groupIt->sink, parameters, QString() ) );
207 groupIt->lastFeature = feature;
208 }
209
210 current++;
211 feedback->setProgress( current * progressStep );
212 if ( feedback->isCanceled() )
213 break;
214 }
215
216 // early cleanup
217 groupSinks.clear();
218
219 QString destId;
220 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, destId, mFields, QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) );
221 if ( !sink )
222 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
223
224 // Calculate aggregates on memory layers
225 if ( !keys.empty() )
226 progressStep = 50.0 / keys.size();
227
228 current = 0;
229 for ( const QVariantList &key : keys )
230 {
231 const Group &group = groups[key];
232
233 QgsExpressionContext exprContext = createExpressionContext( parameters, context );
234 exprContext.appendScope( QgsExpressionContextUtils::layerScope( group.layer ) );
235 exprContext.setFeature( group.firstFeature );
236
237 QgsGeometry geometry = mGeometryExpression.evaluate( &exprContext ).value<QgsGeometry>();
238 if ( mGeometryExpression.hasEvalError() )
239 {
240 throw QgsProcessingException( QObject::tr( "Evaluation error in geometry expression \"%1\": %2" ).arg( mGeometryExpression.expression(), mGeometryExpression.evalErrorString() ) );
241 }
242
243 if ( !geometry.isNull() && !geometry.isEmpty() )
244 {
245 geometry = QgsGeometry::unaryUnion( geometry.asGeometryCollection(), QgsGeometryParameters(), feedback );
246 if ( geometry.isEmpty() )
247 {
248 QStringList keyString;
249 for ( const QVariant &v : key )
250 keyString << v.toString();
251
252 throw QgsProcessingException( QObject::tr( "Impossible to combine geometries for %1 = %2" ).arg( mGroupBy, keyString.join( ',' ) ) );
253 }
254 }
255
256 QgsAttributes attributes;
257 attributes.reserve( mExpressions.size() );
258 int currentAttributeIndex = 0;
259 for ( auto it = mExpressions.begin(); it != mExpressions.end(); ++it )
260 {
261 exprContext.setFeature( mAttributesRequireLastFeature.contains( currentAttributeIndex ) ? group.lastFeature : group.firstFeature );
262 if ( it->isValid() )
263 {
264 const QVariant value = it->evaluate( &exprContext );
265 if ( it->hasEvalError() )
266 {
267 throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" ).arg( it->expression(), it->evalErrorString() ) );
268 }
269 attributes.append( value );
270 }
271 else
272 {
273 attributes.append( QVariant() );
274 }
275 currentAttributeIndex++;
276 }
277
278 // Write output feature
279 QgsFeature outFeat;
280 outFeat.setGeometry( geometry );
281 outFeat.setAttributes( attributes );
282 if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) )
283 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
284 else
285 feedback->featureAddedToSink( u"OUTPUT"_s );
286
287 current++;
288 feedback->setProgress( 50 + current * progressStep );
289 if ( feedback->isCanceled() )
290 break;
291 }
292
293 sink->finalize();
294 feedback->featureSinkFinalized( u"OUTPUT"_s );
295
296 QVariantMap results;
297 results.insert( u"OUTPUT"_s, destId );
298 return results;
299}
300
301bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
302{
303 Q_UNUSED( layer )
304 return false;
305}
306
307QgsExpression QgsAggregateAlgorithm::createExpression( const QString &expressionString, QgsProcessingContext &context ) const
308{
309 QgsExpression expr( expressionString );
310 expr.setGeomCalculator( &mDa );
311 expr.setDistanceUnits( context.distanceUnit() );
312 expr.setAreaUnits( context.areaUnit() );
313 if ( expr.hasParserError() )
314 {
315 throw QgsProcessingException( QObject::tr( "Parser error in expression \"%1\": %2" ).arg( expressionString, expr.parserErrorString() ) );
316 }
317 return expr;
318}
319
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3755
@ RespectsEllipsoid
Algorithm respects the context's ellipsoid settings, and uses ellipsoidal based measurements.
Definition qgis.h:3838
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3847
A vector of attributes.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
static QString quotedString(QString text)
Returns a quoted version of a string (in single quotes).
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
bool isValid() const
Will return if this iterator is valid.
Wraps a request for features to a vector layer (or directly its vector data provider).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Encapsulates parameters under which a geometry operation is performed.
A geometry is the spatial representation of a feature.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr)
Compute the unary union on a list of geometries.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Base class for all map layer types.
Definition qgsmaplayer.h:83
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Qgis::AreaUnit areaUnit() const
Returns the area unit to use for area calculations.
Qgis::DistanceUnit distanceUnit() const
Returns the distance unit to use for distance calculations.
QString ellipsoid() const
Returns the ellipsoid to use for distance and area calculations.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
A parameter for "aggregate" configurations, which consist of a definition of desired output fields,...
An expression parameter for processing algorithms.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
static QgsFeatureSink * createFeatureSink(QString &destination, QgsProcessingContext &context, const QgsFields &fields, Qgis::WkbType geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions=QVariantMap(), const QStringList &datasourceOptions=QStringList(), const QStringList &layerOptions=QStringList(), QgsFeatureSink::SinkFlags sinkFlags=QgsFeatureSink::SinkFlags(), QgsRemappingSinkDefinition *remappingDefinition=nullptr)
Creates a feature sink ready for adding features.
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType, QgsProcessing::LayerOptionsFlags flags=QgsProcessing::LayerOptionsFlags())
Interprets a string as a map layer within the supplied context.
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.