QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
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
21
23
24QString QgsAggregateAlgorithm::name() const
25{
26 return QStringLiteral( "aggregate" );
27}
28
29QString QgsAggregateAlgorithm::displayName() const
30{
31 return QObject::tr( "Aggregate" );
32}
33
34QString QgsAggregateAlgorithm::shortHelpString() const
35{
36 return QObject::tr( "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"
37 "It is possible to group all source features together using constant value in group by parameter, example: NULL.\n\n"
38 "It is also possible to group features using multiple fields using Array function, example: Array(\"Field1\", \"Field2\").\n\n"
39 "Geometries (if present) are combined into one multipart geometry for each group.\n\n"
40 "Output attributes are computed depending on each given aggregate definition." );
41}
42
43QString QgsAggregateAlgorithm::shortDescription() const
44{
45 return QObject::tr( "Aggregates features based on a group by expression, combining geometries (if present) into one multipart geometry for each group." );
46}
47
48QStringList QgsAggregateAlgorithm::tags() const
49{
50 return QObject::tr( "attributes,sum,mean,collect,dissolve,statistics" ).split( ',' );
51}
52
53QString QgsAggregateAlgorithm::group() const
54{
55 return QObject::tr( "Vector geometry" );
56}
57
58QString QgsAggregateAlgorithm::groupId() const
59{
60 return QStringLiteral( "vectorgeometry" );
61}
62
63QgsAggregateAlgorithm *QgsAggregateAlgorithm::createInstance() const
64{
65 return new QgsAggregateAlgorithm();
66}
67
68void QgsAggregateAlgorithm::initAlgorithm( const QVariantMap & )
69{
70 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Vector ) ) );
71 addParameter( new QgsProcessingParameterExpression( QStringLiteral( "GROUP_BY" ), QObject::tr( "Group by expression (NULL to group all features)" ), QStringLiteral( "NULL" ), QStringLiteral( "INPUT" ) ) );
72 addParameter( new QgsProcessingParameterAggregate( QStringLiteral( "AGGREGATES" ), QObject::tr( "Aggregates" ), QStringLiteral( "INPUT" ) ) );
73 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Aggregated" ) ) );
74}
75
76bool QgsAggregateAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
77{
78 mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
79 if ( !mSource )
80 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
81
82 mGroupBy = parameterAsExpression( parameters, QStringLiteral( "GROUP_BY" ), context );
83
84 mDa.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
85 mDa.setEllipsoid( context.ellipsoid() );
86
87 mGroupByExpression = createExpression( mGroupBy, context );
88 mGeometryExpression = createExpression( QStringLiteral( "collect($geometry, %1)" ).arg( mGroupBy ), context );
89
90 const QVariantList aggregates = parameters.value( QStringLiteral( "AGGREGATES" ) ).toList();
91 int currentAttributeIndex = 0;
92 for ( const QVariant &aggregate : aggregates )
93 {
94 const QVariantMap aggregateDef = aggregate.toMap();
95
96 const QString name = aggregateDef.value( QStringLiteral( "name" ) ).toString();
97 if ( name.isEmpty() )
98 throw QgsProcessingException( QObject::tr( "Field name cannot be empty" ) );
99
100 const QMetaType::Type type = static_cast<QMetaType::Type>( aggregateDef.value( QStringLiteral( "type" ) ).toInt() );
101 const QString typeName = aggregateDef.value( QStringLiteral( "type_name" ) ).toString();
102 const QMetaType::Type subType = static_cast<QMetaType::Type>( aggregateDef.value( QStringLiteral( "sub_type" ) ).toInt() );
103
104 const int length = aggregateDef.value( QStringLiteral( "length" ), 0 ).toInt();
105 const int precision = aggregateDef.value( QStringLiteral( "precision" ), 0 ).toInt();
106
107 mFields.append( QgsField( name, type, typeName, length, precision, QString(), subType ) );
108
109
110 const QString aggregateType = aggregateDef.value( QStringLiteral( "aggregate" ) ).toString();
111 const QString source = aggregateDef.value( QStringLiteral( "input" ) ).toString();
112 const QString delimiter = aggregateDef.value( QStringLiteral( "delimiter" ) ).toString();
113
114 QString expression;
115 if ( aggregateType == QLatin1String( "first_value" ) )
116 {
117 expression = source;
118 }
119 else if ( aggregateType == QLatin1String( "last_value" ) )
120 {
121 expression = source;
122 mAttributesRequireLastFeature << currentAttributeIndex;
123 }
124 else if ( aggregateType == QLatin1String( "concatenate" ) || aggregateType == QLatin1String( "concatenate_unique" ) )
125 {
126 expression = QStringLiteral( "%1(%2, %3, %4, %5)" ).arg( aggregateType, source, mGroupBy, QStringLiteral( "TRUE" ), QgsExpression::quotedString( delimiter ) );
127 }
128 else
129 {
130 expression = QStringLiteral( "%1(%2, %3)" ).arg( aggregateType, source, mGroupBy );
131 }
132 mExpressions.append( createExpression( expression, context ) );
133 currentAttributeIndex++;
134 }
135
136 return true;
137}
138
139QVariantMap QgsAggregateAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
140{
141 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, mSource.get() );
142 mGroupByExpression.prepare( &expressionContext );
143
144 // Group features in memory layers
145 const long long count = mSource->featureCount();
146 double progressStep = count > 0 ? 50.0 / count : 1;
147 long long current = 0;
148
149 QHash<QVariantList, Group> groups;
150 QVector<QVariantList> keys; // We need deterministic order for the tests
151 QgsFeature feature;
152
153 std::vector<std::unique_ptr<QgsFeatureSink>> groupSinks;
154
155 QgsFeatureIterator it = mSource->getFeatures( QgsFeatureRequest() );
156 while ( it.nextFeature( feature ) )
157 {
158 expressionContext.setFeature( feature );
159 const QVariant groupByValue = mGroupByExpression.evaluate( &expressionContext );
160 if ( mGroupByExpression.hasEvalError() )
161 {
162 throw QgsProcessingException( QObject::tr( "Evaluation error in group by expression \"%1\": %2" ).arg( mGroupByExpression.expression(), mGroupByExpression.evalErrorString() ) );
163 }
164
165 // upgrade group by value to a list, so that we get correct behavior with the QHash
166 const QVariantList key = groupByValue.userType() == QMetaType::Type::QVariantList ? groupByValue.toList() : ( QVariantList() << groupByValue );
167
168 const auto groupIt = groups.find( key );
169 if ( groupIt == groups.end() )
170 {
171 QString id = QStringLiteral( "memory:" );
172 std::unique_ptr<QgsFeatureSink> sink( QgsProcessingUtils::createFeatureSink( id, context, mSource->fields(), mSource->wkbType(), mSource->sourceCrs() ) );
173
174 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
175 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QString() ) );
176
178
179 Group group;
180 group.sink = sink.get();
181 //store ownership of sink in groupSinks, so that these get deleted automatically if an exception is raised later..
182 groupSinks.emplace_back( std::move( sink ) );
183 group.layer = layer;
184 group.firstFeature = feature;
185 group.lastFeature = feature;
186 groups[key] = group;
187 keys.append( key );
188 }
189 else
190 {
191 if ( !groupIt->sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
192 throw QgsProcessingException( writeFeatureError( groupIt->sink, parameters, QString() ) );
193 groupIt->lastFeature = feature;
194 }
195
196 current++;
197 feedback->setProgress( current * progressStep );
198 if ( feedback->isCanceled() )
199 break;
200 }
201
202 // early cleanup
203 groupSinks.clear();
204
205 QString destId;
206 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, destId, mFields, QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) );
207 if ( !sink )
208 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
209
210 // Calculate aggregates on memory layers
211 if ( !keys.empty() )
212 progressStep = 50.0 / keys.size();
213
214 current = 0;
215 for ( const QVariantList &key : keys )
216 {
217 const Group &group = groups[key];
218
219 QgsExpressionContext exprContext = createExpressionContext( parameters, context );
220 exprContext.appendScope( QgsExpressionContextUtils::layerScope( group.layer ) );
221 exprContext.setFeature( group.firstFeature );
222
223 QgsGeometry geometry = mGeometryExpression.evaluate( &exprContext ).value<QgsGeometry>();
224 if ( mGeometryExpression.hasEvalError() )
225 {
226 throw QgsProcessingException( QObject::tr( "Evaluation error in geometry expression \"%1\": %2" ).arg( mGeometryExpression.expression(), mGeometryExpression.evalErrorString() ) );
227 }
228
229 if ( !geometry.isNull() && !geometry.isEmpty() )
230 {
231 geometry = QgsGeometry::unaryUnion( geometry.asGeometryCollection() );
232 if ( geometry.isEmpty() )
233 {
234 QStringList keyString;
235 for ( const QVariant &v : key )
236 keyString << v.toString();
237
238 throw QgsProcessingException( QObject::tr( "Impossible to combine geometries for %1 = %2" ).arg( mGroupBy, keyString.join( ',' ) ) );
239 }
240 }
241
242 QgsAttributes attributes;
243 attributes.reserve( mExpressions.size() );
244 int currentAttributeIndex = 0;
245 for ( auto it = mExpressions.begin(); it != mExpressions.end(); ++it )
246 {
247 exprContext.setFeature( mAttributesRequireLastFeature.contains( currentAttributeIndex ) ? group.lastFeature : group.firstFeature );
248 if ( it->isValid() )
249 {
250 const QVariant value = it->evaluate( &exprContext );
251 if ( it->hasEvalError() )
252 {
253 throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" ).arg( it->expression(), it->evalErrorString() ) );
254 }
255 attributes.append( value );
256 }
257 else
258 {
259 attributes.append( QVariant() );
260 }
261 currentAttributeIndex++;
262 }
263
264 // Write output feature
265 QgsFeature outFeat;
266 outFeat.setGeometry( geometry );
267 outFeat.setAttributes( attributes );
268 if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) )
269 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
270
271 current++;
272 feedback->setProgress( 50 + current * progressStep );
273 if ( feedback->isCanceled() )
274 break;
275 }
276
277 sink->finalize();
278
279 QVariantMap results;
280 results.insert( QStringLiteral( "OUTPUT" ), destId );
281 return results;
282}
283
284bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
285{
286 Q_UNUSED( layer )
287 return false;
288}
289
290QgsExpression QgsAggregateAlgorithm::createExpression( const QString &expressionString, QgsProcessingContext &context ) const
291{
292 QgsExpression expr( expressionString );
293 expr.setGeomCalculator( &mDa );
294 expr.setDistanceUnits( context.distanceUnit() );
295 expr.setAreaUnits( context.areaUnit() );
296 if ( expr.hasParserError() )
297 {
299 QObject::tr( "Parser error in expression \"%1\": %2" ).arg( expressionString, expr.parserErrorString() )
300 );
301 }
302 return expr;
303}
304
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
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:58
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:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
A geometry is the spatial representation of a feature.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as 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...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Base class for all map layer types.
Definition qgsmaplayer.h:77
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.
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.
const QString & typeName
int precision