QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
97  const int length = aggregateDef.value( QStringLiteral( "length" ), 0 ).toInt();
98  const int precision = aggregateDef.value( QStringLiteral( "precision" ), 0 ).toInt();
99 
100  mFields.append( QgsField( name, type, QString(), length, precision ) );
101 
102 
103  const QString aggregateType = aggregateDef.value( QStringLiteral( "aggregate" ) ).toString();
104  const QString source = aggregateDef.value( QStringLiteral( "input" ) ).toString();
105  const QString delimiter = aggregateDef.value( QStringLiteral( "delimiter" ) ).toString();
106 
107  QString expression;
108  if ( aggregateType == QLatin1String( "first_value" ) )
109  {
110  expression = source;
111  }
112  else if ( aggregateType == QLatin1String( "last_value" ) )
113  {
114  expression = source;
115  mAttributesRequireLastFeature << currentAttributeIndex;
116  }
117  else if ( aggregateType == QLatin1String( "concatenate" ) || aggregateType == QLatin1String( "concatenate_unique" ) )
118  {
119  expression = QStringLiteral( "%1(%2, %3, %4, %5)" ).arg( aggregateType,
120  source,
121  mGroupBy,
122  QStringLiteral( "TRUE" ),
123  QgsExpression::quotedString( delimiter ) );
124  }
125  else
126  {
127  expression = QStringLiteral( "%1(%2, %3)" ).arg( aggregateType, source, mGroupBy );
128  }
129  mExpressions.append( createExpression( expression, context ) );
130  currentAttributeIndex++;
131  }
132 
133  return true;
134 }
135 
136 QVariantMap QgsAggregateAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
137 {
138  QgsExpressionContext expressionContext = createExpressionContext( parameters, context, mSource.get() );
139  mGroupByExpression.prepare( &expressionContext );
140 
141  // Group features in memory layers
142  const long long count = mSource->featureCount();
143  double progressStep = count > 0 ? 50.0 / count : 1;
144  long long current = 0;
145 
146  QHash< QVariantList, Group > groups;
147  QVector< QVariantList > keys; // We need deterministic order for the tests
148  QgsFeature feature;
149 
150  std::vector< std::unique_ptr< QgsFeatureSink > > groupSinks;
151 
152  QgsFeatureIterator it = mSource->getFeatures( QgsFeatureRequest() );
153  while ( it.nextFeature( feature ) )
154  {
155  expressionContext.setFeature( feature );
156  const QVariant groupByValue = mGroupByExpression.evaluate( &expressionContext );
157  if ( mGroupByExpression.hasEvalError() )
158  {
159  throw QgsProcessingException( QObject::tr( "Evaluation error in group by expression \"%1\": %2" ).arg( mGroupByExpression.expression(),
160  mGroupByExpression.evalErrorString() ) );
161  }
162 
163  // upgrade group by value to a list, so that we get correct behavior with the QHash
164  const QVariantList key = groupByValue.type() == QVariant::List ? groupByValue.toList() : ( QVariantList() << groupByValue );
165 
166  const auto groupIt = groups.find( key );
167  if ( groupIt == groups.end() )
168  {
169  QString id = QStringLiteral( "memory:" );
170  std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( id,
171  context,
172  mSource->fields(),
173  mSource->wkbType(),
174  mSource->sourceCrs() ) );
175 
176  if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
177  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QString() ) );
178 
179  QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( id, context );
180 
181  Group group;
182  group.sink = sink.get();
183  //store ownership of sink in groupSinks, so that these get deleted automatically if an exception is raised later..
184  groupSinks.emplace_back( std::move( sink ) );
185  group.layer = layer;
186  group.firstFeature = feature;
187  group.lastFeature = feature;
188  groups[key] = group;
189  keys.append( key );
190  }
191  else
192  {
193  if ( !groupIt->sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
194  throw QgsProcessingException( writeFeatureError( groupIt->sink, parameters, QString() ) );
195  groupIt->lastFeature = feature;
196  }
197 
198  current++;
199  feedback->setProgress( current * progressStep );
200  if ( feedback->isCanceled() )
201  break;
202  }
203 
204  // early cleanup
205  groupSinks.clear();
206 
207  QString destId;
208  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, destId, mFields, QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) );
209  if ( !sink )
210  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
211 
212  // Calculate aggregates on memory layers
213  if ( !keys.empty() )
214  progressStep = 50.0 / keys.size();
215 
216  current = 0;
217  for ( const QVariantList &key : keys )
218  {
219  const Group &group = groups[ key ];
220 
221  QgsExpressionContext exprContext = createExpressionContext( parameters, context );
222  exprContext.appendScope( QgsExpressionContextUtils::layerScope( group.layer ) );
223  exprContext.setFeature( group.firstFeature );
224 
225  QgsGeometry geometry = mGeometryExpression.evaluate( &exprContext ).value< QgsGeometry >();
226  if ( mGeometryExpression.hasEvalError() )
227  {
228  throw QgsProcessingException( QObject::tr( "Evaluation error in geometry expression \"%1\": %2" ).arg( mGeometryExpression.expression(),
229  mGeometryExpression.evalErrorString() ) );
230  }
231 
232  if ( !geometry.isNull() && !geometry.isEmpty() )
233  {
234  geometry = QgsGeometry::unaryUnion( geometry.asGeometryCollection() );
235  if ( geometry.isEmpty() )
236  {
237  QStringList keyString;
238  for ( const QVariant &v : key )
239  keyString << v.toString();
240 
241  throw QgsProcessingException( QObject::tr( "Impossible to combine geometries for %1 = %2" ).arg( mGroupBy, keyString.join( ',' ) ) );
242  }
243  }
244 
245  QgsAttributes attributes;
246  attributes.reserve( mExpressions.size() );
247  int currentAttributeIndex = 0;
248  for ( auto it = mExpressions.begin(); it != mExpressions.end(); ++it )
249  {
250  exprContext.setFeature( mAttributesRequireLastFeature.contains( currentAttributeIndex ) ? group.lastFeature : group.firstFeature );
251  if ( it->isValid() )
252  {
253  const QVariant value = it->evaluate( &exprContext );
254  if ( it->hasEvalError() )
255  {
256  throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" ).arg( it->expression(), it->evalErrorString() ) );
257  }
258  attributes.append( value );
259  }
260  else
261  {
262  attributes.append( QVariant() );
263  }
264  currentAttributeIndex++;
265  }
266 
267  // Write output feature
268  QgsFeature outFeat;
269  outFeat.setGeometry( geometry );
270  outFeat.setAttributes( attributes );
271  if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) )
272  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
273 
274  current++;
275  feedback->setProgress( 50 + current * progressStep );
276  if ( feedback->isCanceled() )
277  break;
278  }
279 
280  QVariantMap results;
281  results.insert( QStringLiteral( "OUTPUT" ), destId );
282  return results;
283 }
284 
285 bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
286 {
287  Q_UNUSED( layer )
288  return false;
289 }
290 
291 QgsExpression QgsAggregateAlgorithm::createExpression( const QString &expressionString, QgsProcessingContext &context ) const
292 {
293  QgsExpression expr( expressionString );
294  expr.setGeomCalculator( &mDa );
295  expr.setDistanceUnits( context.distanceUnit() );
296  expr.setAreaUnits( context.areaUnit() );
297  if ( expr.hasParserError() )
298  {
300  QObject::tr( "Parser error in expression \"%1\": %2" ).arg( expressionString, expr.parserErrorString() ) );
301  }
302  return expr;
303 }
304 
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
int precision