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