LCOV - code coverage report
Current view: top level - analysis/processing - qgsalgorithmjoinbyattribute.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 159 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgsalgorithmjoinbyattribute.cpp
       3                 :            :                          ---------------------
       4                 :            :     begin                : April 2017
       5                 :            :     copyright            : (C) 2017 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 "qgsalgorithmjoinbyattribute.h"
      19                 :            : #include "qgsprocessingoutputs.h"
      20                 :            : 
      21                 :            : ///@cond PRIVATE
      22                 :            : 
      23                 :          0 : QString QgsJoinByAttributeAlgorithm::name() const
      24                 :            : {
      25                 :          0 :   return QStringLiteral( "joinattributestable" );
      26                 :            : }
      27                 :            : 
      28                 :          0 : QString QgsJoinByAttributeAlgorithm::displayName() const
      29                 :            : {
      30                 :          0 :   return QObject::tr( "Join attributes by field value" );
      31                 :            : }
      32                 :            : 
      33                 :          0 : QStringList QgsJoinByAttributeAlgorithm::tags() const
      34                 :            : {
      35                 :          0 :   return QObject::tr( "join,connect,attributes,values,fields,tables" ).split( ',' );
      36                 :          0 : }
      37                 :            : 
      38                 :          0 : QString QgsJoinByAttributeAlgorithm::group() const
      39                 :            : {
      40                 :          0 :   return QObject::tr( "Vector general" );
      41                 :            : }
      42                 :            : 
      43                 :          0 : QString QgsJoinByAttributeAlgorithm::groupId() const
      44                 :            : {
      45                 :          0 :   return QStringLiteral( "vectorgeneral" );
      46                 :            : }
      47                 :            : 
      48                 :          0 : void QgsJoinByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
      49                 :            : {
      50                 :          0 :   QStringList methods;
      51                 :          0 :   methods << QObject::tr( "Create separate feature for each matching feature (one-to-many)" )
      52                 :          0 :           << QObject::tr( "Take attributes of the first matching feature only (one-to-one)" );
      53                 :            : 
      54                 :          0 :   addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
      55                 :          0 :                 QObject::tr( "Input layer" ), QList< int>() << QgsProcessing::TypeVector ) );
      56                 :          0 :   addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ),
      57                 :          0 :                 QObject::tr( "Table field" ), QVariant(), QStringLiteral( "INPUT" ) ) );
      58                 :            : 
      59                 :          0 :   addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_2" ),
      60                 :          0 :                 QObject::tr( "Input layer 2" ), QList< int>() << QgsProcessing::TypeVector ) );
      61                 :          0 :   addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD_2" ),
      62                 :          0 :                 QObject::tr( "Table field 2" ), QVariant(), QStringLiteral( "INPUT_2" ) ) );
      63                 :            : 
      64                 :          0 :   addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS_TO_COPY" ),
      65                 :          0 :                 QObject::tr( "Layer 2 fields to copy (leave empty to copy all fields)" ),
      66                 :          0 :                 QVariant(), QStringLiteral( "INPUT_2" ), QgsProcessingParameterField::Any,
      67                 :            :                 true, true ) );
      68                 :            : 
      69                 :          0 :   addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
      70                 :          0 :                 QObject::tr( "Join type" ),
      71                 :          0 :                 methods, false, 1 ) );
      72                 :          0 :   addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ),
      73                 :          0 :                 QObject::tr( "Discard records which could not be joined" ),
      74                 :          0 :                 false ) );
      75                 :            : 
      76                 :          0 :   addParameter( new QgsProcessingParameterString( QStringLiteral( "PREFIX" ),
      77                 :          0 :                 QObject::tr( "Joined field prefix" ), QVariant(), false, true ) );
      78                 :            : 
      79                 :          0 :   addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
      80                 :            : 
      81                 :          0 :   std::unique_ptr< QgsProcessingParameterFeatureSink > nonMatchingSink = std::make_unique< QgsProcessingParameterFeatureSink >(
      82                 :          0 :         QStringLiteral( "NON_MATCHING" ), QObject::tr( "Unjoinable features from first layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, false );
      83                 :            :   // TODO GUI doesn't support advanced outputs yet
      84                 :            :   //nonMatchingSink->setFlags(nonMatchingSink->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
      85                 :          0 :   addParameter( nonMatchingSink.release() );
      86                 :            : 
      87                 :          0 :   addOutput( new QgsProcessingOutputNumber( QStringLiteral( "JOINED_COUNT" ), QObject::tr( "Number of joined features from input table" ) ) );
      88                 :          0 :   addOutput( new QgsProcessingOutputNumber( QStringLiteral( "UNJOINABLE_COUNT" ), QObject::tr( "Number of unjoinable features from input table" ) ) );
      89                 :          0 : }
      90                 :            : 
      91                 :          0 : QString QgsJoinByAttributeAlgorithm::shortHelpString() const
      92                 :            : {
      93                 :          0 :   return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the "
      94                 :            :                       "input one, with additional attributes in its attribute table.\n\n"
      95                 :            :                       "The additional attributes and their values are taken from a second vector layer. An attribute is selected "
      96                 :            :                       "in each of them to define the join criteria." );
      97                 :            : }
      98                 :            : 
      99                 :          0 : QgsJoinByAttributeAlgorithm *QgsJoinByAttributeAlgorithm::createInstance() const
     100                 :            : {
     101                 :          0 :   return new QgsJoinByAttributeAlgorithm();
     102                 :            : }
     103                 :            : 
     104                 :          0 : QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
     105                 :            : {
     106                 :          0 :   int joinMethod = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
     107                 :          0 :   bool discardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
     108                 :            : 
     109                 :          0 :   std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
     110                 :          0 :   if ( !input )
     111                 :          0 :     throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
     112                 :            : 
     113                 :          0 :   std::unique_ptr< QgsProcessingFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
     114                 :          0 :   if ( !input2 )
     115                 :          0 :     throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_2" ) ) );
     116                 :            : 
     117                 :          0 :   QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
     118                 :            : 
     119                 :          0 :   QString field1Name = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
     120                 :          0 :   QString field2Name = parameterAsString( parameters, QStringLiteral( "FIELD_2" ), context );
     121                 :          0 :   const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
     122                 :            : 
     123                 :          0 :   int joinField1Index = input->fields().lookupField( field1Name );
     124                 :          0 :   int joinField2Index = input2->fields().lookupField( field2Name );
     125                 :          0 :   if ( joinField1Index < 0 || joinField2Index < 0 )
     126                 :          0 :     throw QgsProcessingException( QObject::tr( "Invalid join fields" ) );
     127                 :            : 
     128                 :          0 :   QgsFields outFields2;
     129                 :          0 :   QgsAttributeList fields2Indices;
     130                 :          0 :   if ( fieldsToCopy.empty() )
     131                 :            :   {
     132                 :          0 :     outFields2 = input2->fields();
     133                 :          0 :     fields2Indices.reserve( outFields2.count() );
     134                 :          0 :     for ( int i = 0; i < outFields2.count(); ++i )
     135                 :            :     {
     136                 :          0 :       fields2Indices << i;
     137                 :          0 :     }
     138                 :          0 :   }
     139                 :            :   else
     140                 :            :   {
     141                 :          0 :     fields2Indices.reserve( fieldsToCopy.count() );
     142                 :          0 :     for ( const QString &field : fieldsToCopy )
     143                 :            :     {
     144                 :          0 :       int index = input2->fields().lookupField( field );
     145                 :          0 :       if ( index >= 0 )
     146                 :            :       {
     147                 :          0 :         fields2Indices << index;
     148                 :          0 :         outFields2.append( input2->fields().at( index ) );
     149                 :          0 :       }
     150                 :            :     }
     151                 :            :   }
     152                 :            : 
     153                 :          0 :   if ( !prefix.isEmpty() )
     154                 :            :   {
     155                 :          0 :     for ( int i = 0; i < outFields2.count(); ++i )
     156                 :            :     {
     157                 :          0 :       outFields2.rename( i, prefix + outFields2[ i ].name() );
     158                 :          0 :     }
     159                 :          0 :   }
     160                 :            : 
     161                 :          0 :   QgsAttributeList fields2Fetch = fields2Indices;
     162                 :          0 :   fields2Fetch << joinField2Index;
     163                 :            : 
     164                 :          0 :   QgsFields outFields = QgsProcessingUtils::combineFields( input->fields(), outFields2 );
     165                 :            : 
     166                 :          0 :   QString dest;
     167                 :          0 :   std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
     168                 :          0 :                                           input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
     169                 :          0 :   if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
     170                 :          0 :     throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
     171                 :            : 
     172                 :          0 :   QString destNonMatching1;
     173                 :          0 :   std::unique_ptr< QgsFeatureSink > sinkNonMatching1( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, destNonMatching1, input->fields(),
     174                 :          0 :       input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
     175                 :          0 :   if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !sinkNonMatching1 )
     176                 :          0 :     throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
     177                 :            : 
     178                 :            :   // cache attributes of input2
     179                 :          0 :   QMultiHash< QVariant, QgsAttributes > input2AttributeCache;
     180                 :          0 :   QgsFeatureIterator features = input2->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( fields2Fetch ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
     181                 :          0 :   double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
     182                 :          0 :   int i = 0;
     183                 :          0 :   QgsFeature feat;
     184                 :          0 :   while ( features.nextFeature( feat ) )
     185                 :            :   {
     186                 :          0 :     i++;
     187                 :          0 :     if ( feedback->isCanceled() )
     188                 :            :     {
     189                 :          0 :       break;
     190                 :            :     }
     191                 :            : 
     192                 :          0 :     feedback->setProgress( i * step );
     193                 :            : 
     194                 :          0 :     if ( joinMethod == 1 && input2AttributeCache.contains( feat.attribute( joinField2Index ) ) )
     195                 :          0 :       continue;
     196                 :            : 
     197                 :            :     // only keep selected attributes
     198                 :          0 :     QgsAttributes attributes;
     199                 :          0 :     for ( int j = 0; j < feat.attributes().count(); ++j )
     200                 :            :     {
     201                 :          0 :       if ( ! fields2Indices.contains( j ) )
     202                 :          0 :         continue;
     203                 :          0 :       attributes << feat.attribute( j );
     204                 :          0 :     }
     205                 :            : 
     206                 :          0 :     input2AttributeCache.insert( feat.attribute( joinField2Index ), attributes );
     207                 :          0 :   }
     208                 :            : 
     209                 :            :   // Create output vector layer with additional attribute
     210                 :          0 :   step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
     211                 :          0 :   features = input->getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
     212                 :          0 :   i = 0;
     213                 :          0 :   long long joinedCount = 0;
     214                 :          0 :   long long unjoinedCount = 0;
     215                 :          0 :   while ( features.nextFeature( feat ) )
     216                 :            :   {
     217                 :          0 :     i++;
     218                 :          0 :     if ( feedback->isCanceled() )
     219                 :            :     {
     220                 :          0 :       break;
     221                 :            :     }
     222                 :            : 
     223                 :          0 :     feedback->setProgress( 50 + i * step );
     224                 :            : 
     225                 :          0 :     if ( input2AttributeCache.count( feat.attribute( joinField1Index ) ) > 0 )
     226                 :            :     {
     227                 :          0 :       joinedCount++;
     228                 :          0 :       if ( sink )
     229                 :            :       {
     230                 :          0 :         QgsAttributes attrs = feat.attributes();
     231                 :            : 
     232                 :          0 :         QList< QgsAttributes > attributes = input2AttributeCache.values( feat.attribute( joinField1Index ) );
     233                 :          0 :         QList< QgsAttributes >::iterator attrsIt = attributes.begin();
     234                 :          0 :         for ( ; attrsIt != attributes.end(); ++attrsIt )
     235                 :            :         {
     236                 :          0 :           QgsAttributes newAttrs = attrs;
     237                 :          0 :           newAttrs.append( *attrsIt );
     238                 :          0 :           feat.setAttributes( newAttrs );
     239                 :          0 :           sink->addFeature( feat, QgsFeatureSink::FastInsert );
     240                 :          0 :         }
     241                 :          0 :       }
     242                 :          0 :     }
     243                 :            :     else
     244                 :            :     {
     245                 :            :       // no matching for input feature
     246                 :          0 :       if ( sink && !discardNonMatching )
     247                 :            :       {
     248                 :          0 :         sink->addFeature( feat, QgsFeatureSink::FastInsert );
     249                 :          0 :       }
     250                 :          0 :       if ( sinkNonMatching1 )
     251                 :            :       {
     252                 :          0 :         sinkNonMatching1->addFeature( feat, QgsFeatureSink::FastInsert );
     253                 :          0 :       }
     254                 :          0 :       unjoinedCount++;
     255                 :            :     }
     256                 :            :   }
     257                 :            : 
     258                 :          0 :   feedback->pushInfo( QObject::tr( "%1 feature(s) from input layer were successfully matched" ).arg( joinedCount ) );
     259                 :          0 :   if ( unjoinedCount > 0 )
     260                 :          0 :     feedback->reportError( QObject::tr( "%1 feature(s) from input layer could not be matched" ).arg( unjoinedCount ) );
     261                 :            : 
     262                 :          0 :   QVariantMap outputs;
     263                 :          0 :   if ( sink )
     264                 :          0 :     outputs.insert( QStringLiteral( "OUTPUT" ), dest );
     265                 :          0 :   outputs.insert( QStringLiteral( "JOINED_COUNT" ), joinedCount );
     266                 :          0 :   outputs.insert( QStringLiteral( "UNJOINABLE_COUNT" ), unjoinedCount );
     267                 :          0 :   if ( sinkNonMatching1 )
     268                 :          0 :     outputs.insert( QStringLiteral( "NON_MATCHING" ), destNonMatching1 );
     269                 :          0 :   return outputs;
     270                 :          0 : }
     271                 :            : 
     272                 :            : 
     273                 :            : ///@endcond

Generated by: LCOV version 1.14