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