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