QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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" ).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.project()->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 
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( "concatenate" ) || aggregateType == QLatin1String( "concatenate_unique" ) )
113  {
114  expression = QStringLiteral( "%1(%2, %3, %4, \'%5\')" ).arg( aggregateType,
115  source,
116  mGroupBy,
117  QStringLiteral( "TRUE" ),
118  delimiter );
119  }
120  else
121  {
122  expression = QStringLiteral( "%1(%2, %3)" ).arg( aggregateType, source, mGroupBy );
123  }
124  mExpressions.append( createExpression( expression, context ) );
125  }
126 
127  return true;
128 }
129 
130 QVariantMap QgsAggregateAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
131 {
132  QgsExpressionContext expressionContext = createExpressionContext( parameters, context, mSource.get() );
133  mGroupByExpression.prepare( &expressionContext );
134 
135  // Group features in memory layers
136  const long long count = mSource->featureCount();
137  double progressStep = count > 0 ? 50.0 / count : 1;
138  long long current = 0;
139 
140  QHash< QVariantList, Group > groups;
141  QVector< QVariantList > keys; // We need deterministic order for the tests
142  QgsFeature feature;
143 
144  std::vector< std::unique_ptr< QgsFeatureSink > > groupSinks;
145 
146  QgsFeatureIterator it = mSource->getFeatures( QgsFeatureRequest() );
147  while ( it.nextFeature( feature ) )
148  {
149  expressionContext.setFeature( feature );
150  const QVariant groupByValue = mGroupByExpression.evaluate( &expressionContext );
151  if ( mGroupByExpression.hasEvalError() )
152  {
153  throw QgsProcessingException( QObject::tr( "Evaluation error in group by expression \"%1\": %2" ).arg( mGroupByExpression.expression(),
154  mGroupByExpression.evalErrorString() ) );
155  }
156 
157  // upgrade group by value to a list, so that we get correct behavior with the QHash
158  const QVariantList key = groupByValue.type() == QVariant::List ? groupByValue.toList() : ( QVariantList() << groupByValue );
159 
160  auto groupIt = groups.constFind( key );
161  if ( groupIt == groups.constEnd() )
162  {
163  QString id = QStringLiteral( "memory:" );
164  std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( id,
165  context,
166  mSource->fields(),
167  mSource->wkbType(),
168  mSource->sourceCrs() ) );
169 
170  sink->addFeature( feature, QgsFeatureSink::FastInsert );
171 
172  QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( id, context );
173 
174  Group group;
175  group.sink = sink.get();
176  //store ownership of sink in groupSinks, so that these get deleted automatically if an exception is raised later..
177  groupSinks.emplace_back( std::move( sink ) );
178  group.layer = layer;
179  group.feature = feature;
180  groups[key] = group;
181  keys.append( key );
182  }
183  else
184  {
185  groupIt->sink->addFeature( feature, QgsFeatureSink::FastInsert );
186  }
187 
188  current++;
189  feedback->setProgress( current * progressStep );
190  if ( feedback->isCanceled() )
191  break;
192  }
193 
194  // early cleanup
195  groupSinks.clear();
196 
197  QString destId;
198  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, destId, mFields, QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) );
199  if ( !sink )
200  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
201 
202  // Calculate aggregates on memory layers
203  if ( !keys.empty() )
204  progressStep = 50.0 / keys.size();
205 
206  current = 0;
207  for ( const QVariantList &key : keys )
208  {
209  Group &group = groups[ key ];
210 
211  QgsExpressionContext exprContext = createExpressionContext( parameters, context );
212  exprContext.appendScope( QgsExpressionContextUtils::layerScope( group.layer ) );
213  exprContext.setFeature( group.feature );
214 
215  QgsGeometry geometry = mGeometryExpression.evaluate( &exprContext ).value< QgsGeometry >();
216  if ( mGeometryExpression.hasEvalError() )
217  {
218  throw QgsProcessingException( QObject::tr( "Evaluation error in geometry expression \"%1\": %2" ).arg( mGeometryExpression.expression(),
219  mGeometryExpression.evalErrorString() ) );
220  }
221 
222  if ( !geometry.isNull() && !geometry.isEmpty() )
223  {
224  geometry = QgsGeometry::unaryUnion( geometry.asGeometryCollection() );
225  if ( geometry.isEmpty() )
226  {
227  QStringList keyString;
228  for ( const QVariant &v : key )
229  keyString << v.toString();
230 
231  throw QgsProcessingException( QObject::tr( "Impossible to combine geometries for %1 = %2" ).arg( mGroupBy, keyString.join( ',' ) ) );
232  }
233  }
234 
235  QgsAttributes attributes;
236  attributes.reserve( mExpressions.size() );
237  for ( auto it = mExpressions.begin(); it != mExpressions.end(); ++it )
238  {
239  if ( it->isValid() )
240  {
241  const QVariant value = it->evaluate( &exprContext );
242  if ( it->hasEvalError() )
243  {
244  throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" ).arg( it->expression(), it->evalErrorString() ) );
245  }
246  attributes.append( value );
247  }
248  else
249  {
250  attributes.append( QVariant() );
251  }
252  }
253 
254  // Write output feature
255  QgsFeature outFeat;
256  outFeat.setGeometry( geometry );
257  outFeat.setAttributes( attributes );
258  sink->addFeature( outFeat, QgsFeatureSink::FastInsert );
259 
260  current++;
261  feedback->setProgress( 50 + current * progressStep );
262  if ( feedback->isCanceled() )
263  break;
264  }
265 
266  QVariantMap results;
267  results.insert( QStringLiteral( "OUTPUT" ), destId );
268  return results;
269 }
270 
271 bool QgsAggregateAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
272 {
273  Q_UNUSED( layer )
274  return false;
275 }
276 
277 QgsExpression QgsAggregateAlgorithm::createExpression( const QString &expressionString, QgsProcessingContext &context ) const
278 {
279  QgsExpression expr( expressionString );
280  expr.setGeomCalculator( &mDa );
281  expr.setDistanceUnits( context.project()->distanceUnits() );
282  expr.setAreaUnits( context.project()->areaUnits() );
283  if ( expr.hasParserError() )
284  {
286  QObject::tr( "Parser error in expression \"%1\": %2" ).arg( expressionString, expr.parserErrorString() ) );
287  }
288  return expr;
289 }
290 
QgsWkbTypes::multiType
static Type multiType(Type type)
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:301
QgsExpressionContext
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Definition: qgsexpressioncontext.h:369
qgsexpressioncontextutils.h
QgsFeedback::setProgress
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:75
QgsProcessingContext::project
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Definition: qgsprocessingcontext.h:99
QgsProcessingFeedback
Definition: qgsprocessingfeedback.h:37
QgsProject::distanceUnits
QgsUnitTypes::DistanceUnit distanceUnits() const
Convenience function to query default distance measurement units for project.
Definition: qgsproject.cpp:2879
QgsExpressionContextUtils::layerScope
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Definition: qgsexpressioncontextutils.cpp:264
QgsProcessingParameterFeatureSource
Definition: qgsprocessingparameters.h:2612
QgsProject::areaUnits
QgsUnitTypes::AreaUnit areaUnits() const
Convenience function to query default area measurement units for project.
Definition: qgsproject.cpp:2897
QgsGeometry::asGeometryCollection
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
Definition: qgsgeometry.cpp:2526
QgsProcessingParameterFeatureSink
Definition: qgsprocessingparameters.h:2773
QgsProcessingParameterAggregate
Definition: qgsprocessingparameteraggregate.h:31
qgsprocessingparameteraggregate.h
QgsFeatureRequest
Definition: qgsfeaturerequest.h:75
precision
int precision
Definition: qgswfsgetfeature.cpp:103
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
QgsFeature::setGeometry
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:137
QgsProcessingContext
Definition: qgsprocessingcontext.h:43
QgsGeometry::isEmpty
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Definition: qgsgeometry.cpp:367
QgsProcessingContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Definition: qgsprocessingcontext.h:135
QgsGeometry::isNull
bool isNull
Definition: qgsgeometry.h:125
QgsExpressionContext::appendScope
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Definition: qgsexpressioncontext.cpp:490
QgsFeedback::isCanceled
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:66
qgsalgorithmaggregate.h
QgsFeatureIterator::nextFeature
bool nextFeature(QgsFeature &f)
Definition: qgsfeatureiterator.h:373
QgsGeometry
Definition: qgsgeometry.h:122
QgsProcessingParameterExpression
Definition: qgsprocessingparameters.h:2294
QgsMapLayer
Definition: qgsmaplayer.h:81
QgsAttributes
Definition: qgsattributes.h:57
QgsProcessingUtils::mapLayerFromString
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.
Definition: qgsprocessingutils.cpp:314
QgsFeature
Definition: qgsfeature.h:55
QgsProcessingUtils::createFeatureSink
static QgsFeatureSink * createFeatureSink(QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions=QVariantMap(), QgsFeatureSink::SinkFlags sinkFlags=QgsFeatureSink::SinkFlags(), QgsRemappingSinkDefinition *remappingDefinition=nullptr)
Creates a feature sink ready for adding features.
Definition: qgsprocessingutils.cpp:690
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:127
QgsExpression
Definition: qgsexpression.h:113
QgsFeatureIterator
Definition: qgsfeatureiterator.h:263
QgsProject::ellipsoid
QString ellipsoid
Definition: qgsproject.h:100
QgsProcessingException
Definition: qgsexception.h:82
QgsFeatureIterator::isValid
bool isValid() const
Will return if this iterator is valid.
Definition: qgsfeatureiterator.cpp:239
QgsGeometry::unaryUnion
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Definition: qgsgeometry.cpp:2785
QgsFeatureSink::FastInsert
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Definition: qgsfeaturesink.h:70
QgsExpressionContext::setFeature
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Definition: qgsexpressioncontext.cpp:521
QgsField
Definition: qgsfield.h:49