QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  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  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  sink->addFeature( feature, QgsFeatureSink::FastInsert );
177 
178  QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( id, context );
179 
180  Group group;
181  group.sink = sink.get();
182  //store ownership of sink in groupSinks, so that these get deleted automatically if an exception is raised later..
183  groupSinks.emplace_back( std::move( sink ) );
184  group.layer = layer;
185  group.firstFeature = feature;
186  group.lastFeature = feature;
187  groups[key] = group;
188  keys.append( key );
189  }
190  else
191  {
192  groupIt->sink->addFeature( feature, QgsFeatureSink::FastInsert );
193  groupIt->lastFeature = feature;
194  }
195 
196  current++;
197  feedback->setProgress( current * progressStep );
198  if ( feedback->isCanceled() )
199  break;
200  }
201 
202  // early cleanup
203  groupSinks.clear();
204 
205  QString destId;
206  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, destId, mFields, QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) );
207  if ( !sink )
208  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
209 
210  // Calculate aggregates on memory layers
211  if ( !keys.empty() )
212  progressStep = 50.0 / keys.size();
213 
214  current = 0;
215  for ( const QVariantList &key : keys )
216  {
217  Group &group = groups[ key ];
218 
219  QgsExpressionContext exprContext = createExpressionContext( parameters, context );
220  exprContext.appendScope( QgsExpressionContextUtils::layerScope( group.layer ) );
221  exprContext.setFeature( group.firstFeature );
222 
223  QgsGeometry geometry = mGeometryExpression.evaluate( &exprContext ).value< QgsGeometry >();
224  if ( mGeometryExpression.hasEvalError() )
225  {
226  throw QgsProcessingException( QObject::tr( "Evaluation error in geometry expression \"%1\": %2" ).arg( mGeometryExpression.expression(),
227  mGeometryExpression.evalErrorString() ) );
228  }
229 
230  if ( !geometry.isNull() && !geometry.isEmpty() )
231  {
232  geometry = QgsGeometry::unaryUnion( geometry.asGeometryCollection() );
233  if ( geometry.isEmpty() )
234  {
235  QStringList keyString;
236  for ( const QVariant &v : key )
237  keyString << v.toString();
238 
239  throw QgsProcessingException( QObject::tr( "Impossible to combine geometries for %1 = %2" ).arg( mGroupBy, keyString.join( ',' ) ) );
240  }
241  }
242 
243  QgsAttributes attributes;
244  attributes.reserve( mExpressions.size() );
245  int currentAttributeIndex = 0;
246  for ( auto it = mExpressions.begin(); it != mExpressions.end(); ++it )
247  {
248  exprContext.setFeature( mAttributesRequireLastFeature.contains( currentAttributeIndex ) ? group.lastFeature : group.firstFeature );
249  if ( it->isValid() )
250  {
251  const QVariant value = it->evaluate( &exprContext );
252  if ( it->hasEvalError() )
253  {
254  throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" ).arg( it->expression(), it->evalErrorString() ) );
255  }
256  attributes.append( value );
257  }
258  else
259  {
260  attributes.append( QVariant() );
261  }
262  currentAttributeIndex++;
263  }
264 
265  // Write output feature
266  QgsFeature outFeat;
267  outFeat.setGeometry( geometry );
268  outFeat.setAttributes( attributes );
269  sink->addFeature( outFeat, QgsFeatureSink::FastInsert );
270 
271  current++;
272  feedback->setProgress( 50 + current * progressStep );
273  if ( feedback->isCanceled() )
274  break;
275  }
276 
277  QVariantMap results;
278  results.insert( QStringLiteral( "OUTPUT" ), destId );
279  return results;
280 }
281 
282 bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
283 {
284  Q_UNUSED( layer )
285  return false;
286 }
287 
288 QgsExpression QgsAggregateAlgorithm::createExpression( const QString &expressionString, QgsProcessingContext &context ) const
289 {
290  QgsExpression expr( expressionString );
291  expr.setGeomCalculator( &mDa );
292  expr.setDistanceUnits( context.distanceUnit() );
293  expr.setAreaUnits( context.areaUnit() );
294  if ( expr.hasParserError() )
295  {
297  QObject::tr( "Parser error in expression \"%1\": %2" ).arg( expressionString, expr.parserErrorString() ) );
298  }
299  return expr;
300 }
301 
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").
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:135
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:145
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:124
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:126
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:70
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:302
int precision