LCOV - code coverage report
Current view: top level - analysis/processing - qgsalgorithmaggregate.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 161 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

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

Generated by: LCOV version 1.14