QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
18 #include "qgsalgorithmaggregate.h"
21 
23 
24 QString QgsAggregateAlgorithm::name() const
25 {
26  return QStringLiteral( "aggregate" );
27 }
28 
29 QString QgsAggregateAlgorithm::displayName() const
30 {
31  return QObject::tr( "Aggregate" );
32 }
33 
34 QString 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 
43 QStringList QgsAggregateAlgorithm::tags() const
44 {
45  return QObject::tr( "attributes,sum,mean,collect,dissolve,statistics" ).split( ',' );
46 }
47 
48 QString QgsAggregateAlgorithm::group() const
49 {
50  return QObject::tr( "Vector geometry" );
51 }
52 
53 QString QgsAggregateAlgorithm::groupId() const
54 {
55  return QStringLiteral( "vectorgeometry" );
56 }
57 
58 QgsAggregateAlgorithm *QgsAggregateAlgorithm::createInstance() const
59 {
60  return new QgsAggregateAlgorithm();
61 }
62 
63 void QgsAggregateAlgorithm::initAlgorithm( const QVariantMap & )
64 {
65  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << QgsProcessing::TypeVector ) );
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 
71 bool 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 QVariant::Type type = static_cast< QVariant::Type >( aggregateDef.value( QStringLiteral( "type" ) ).toInt() );
96  const QString typeName = aggregateDef.value( QStringLiteral( "type_name" ) ).toString();
97  const QVariant::Type subType = static_cast< QVariant::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 
138 QVariantMap 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.type() == QVariant::List ? 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 
181  QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( id, context );
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 
287 bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
288 {
289  Q_UNUSED( layer )
290  return false;
291 }
292 
293 QgsExpression 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 
A vector of attributes.
Definition: qgsattributes.h:58
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)
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:56
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:153
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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:51
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
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:73
Contains information about the context in which a processing algorithm is executed.
QgsUnitTypes::AreaUnit areaUnit() const
Returns the area unit to use for area calculations.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QgsUnitTypes::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.
Definition: qgsexception.h:83
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, QgsWkbTypes::Type 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)
Interprets a string as a map layer within the supplied context.
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
static Type multiType(Type type) SIP_HOLDGIL
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:304
const QString & typeName
int precision