QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsalgorithmjoinbyattribute.cpp
Go to the documentation of this file.
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 
19 #include "qgsprocessingoutputs.h"
20 
22 
23 QString QgsJoinByAttributeAlgorithm::name() const
24 {
25  return QStringLiteral( "joinattributestable" );
26 }
27 
28 QString QgsJoinByAttributeAlgorithm::displayName() const
29 {
30  return QObject::tr( "Join attributes by field value" );
31 }
32 
33 QStringList QgsJoinByAttributeAlgorithm::tags() const
34 {
35  return QObject::tr( "join,connect,attributes,values,fields,tables" ).split( ',' );
36 }
37 
38 QString QgsJoinByAttributeAlgorithm::group() const
39 {
40  return QObject::tr( "Vector general" );
41 }
42 
43 QString QgsJoinByAttributeAlgorithm::groupId() const
44 {
45  return QStringLiteral( "vectorgeneral" );
46 }
47 
48 void QgsJoinByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
49 {
50  QStringList methods;
51  methods << QObject::tr( "Create separate feature for each matching feature (one-to-many)" )
52  << QObject::tr( "Take attributes of the first matching feature only (one-to-one)" );
53 
54  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
55  QObject::tr( "Input layer" ), QList< int>() << QgsProcessing::TypeVector ) );
56  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ),
57  QObject::tr( "Table field" ), QVariant(), QStringLiteral( "INPUT" ) ) );
58 
59  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_2" ),
60  QObject::tr( "Input layer 2" ), QList< int>() << QgsProcessing::TypeVector ) );
61  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD_2" ),
62  QObject::tr( "Table field 2" ), QVariant(), QStringLiteral( "INPUT_2" ) ) );
63 
64  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS_TO_COPY" ),
65  QObject::tr( "Layer 2 fields to copy (leave empty to copy all fields)" ),
66  QVariant(), QStringLiteral( "INPUT_2" ), QgsProcessingParameterField::Any,
67  true, true ) );
68 
69  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
70  QObject::tr( "Join type" ),
71  methods, false, 1 ) );
72  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ),
73  QObject::tr( "Discard records which could not be joined" ),
74  false ) );
75 
76  addParameter( new QgsProcessingParameterString( QStringLiteral( "PREFIX" ),
77  QObject::tr( "Joined field prefix" ), QVariant(), false, true ) );
78 
79  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
80 
81  std::unique_ptr< QgsProcessingParameterFeatureSink > nonMatchingSink = std::make_unique< QgsProcessingParameterFeatureSink >(
82  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  addParameter( nonMatchingSink.release() );
86 
87  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "JOINED_COUNT" ), QObject::tr( "Number of joined features from input table" ) ) );
88  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "UNJOINABLE_COUNT" ), QObject::tr( "Number of unjoinable features from input table" ) ) );
89 }
90 
91 QString QgsJoinByAttributeAlgorithm::shortHelpString() const
92 {
93  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 QgsJoinByAttributeAlgorithm *QgsJoinByAttributeAlgorithm::createInstance() const
100 {
101  return new QgsJoinByAttributeAlgorithm();
102 }
103 
104 QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
105 {
106  const int joinMethod = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
107  const bool discardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
108 
109  std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
110  if ( !input )
111  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
112 
113  std::unique_ptr< QgsProcessingFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
114  if ( !input2 )
115  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_2" ) ) );
116 
117  const QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
118 
119  const QString field1Name = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
120  const QString field2Name = parameterAsString( parameters, QStringLiteral( "FIELD_2" ), context );
121  const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
122 
123  const int joinField1Index = input->fields().lookupField( field1Name );
124  const int joinField2Index = input2->fields().lookupField( field2Name );
125  if ( joinField1Index < 0 || joinField2Index < 0 )
126  throw QgsProcessingException( QObject::tr( "Invalid join fields" ) );
127 
128  QgsFields outFields2;
129  QgsAttributeList fields2Indices;
130  if ( fieldsToCopy.empty() )
131  {
132  outFields2 = input2->fields();
133  fields2Indices.reserve( outFields2.count() );
134  for ( int i = 0; i < outFields2.count(); ++i )
135  {
136  fields2Indices << i;
137  }
138  }
139  else
140  {
141  fields2Indices.reserve( fieldsToCopy.count() );
142  for ( const QString &field : fieldsToCopy )
143  {
144  const int index = input2->fields().lookupField( field );
145  if ( index >= 0 )
146  {
147  fields2Indices << index;
148  outFields2.append( input2->fields().at( index ) );
149  }
150  }
151  }
152 
153  if ( !prefix.isEmpty() )
154  {
155  for ( int i = 0; i < outFields2.count(); ++i )
156  {
157  outFields2.rename( i, prefix + outFields2[ i ].name() );
158  }
159  }
160 
161  QgsAttributeList fields2Fetch = fields2Indices;
162  fields2Fetch << joinField2Index;
163 
164  const QgsFields outFields = QgsProcessingUtils::combineFields( input->fields(), outFields2 );
165 
166  QString dest;
167  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
168  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
169  if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
170  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
171 
172  QString destNonMatching1;
173  std::unique_ptr< QgsFeatureSink > sinkNonMatching1( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, destNonMatching1, input->fields(),
174  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
175  if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !sinkNonMatching1 )
176  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
177 
178  // cache attributes of input2
179  QMultiHash< QVariant, QgsAttributes > input2AttributeCache;
180  QgsFeatureIterator features = input2->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( fields2Fetch ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
181  double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
182  int i = 0;
183  QgsFeature feat;
184  while ( features.nextFeature( feat ) )
185  {
186  i++;
187  if ( feedback->isCanceled() )
188  {
189  break;
190  }
191 
192  feedback->setProgress( i * step );
193 
194  if ( joinMethod == 1 && input2AttributeCache.contains( feat.attribute( joinField2Index ) ) )
195  continue;
196 
197  // only keep selected attributes
198  QgsAttributes attributes;
199  for ( int j = 0; j < feat.attributes().count(); ++j )
200  {
201  if ( ! fields2Indices.contains( j ) )
202  continue;
203  attributes << feat.attribute( j );
204  }
205 
206  input2AttributeCache.insert( feat.attribute( joinField2Index ), attributes );
207  }
208 
209  // Create output vector layer with additional attribute
210  step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
212  i = 0;
213  long long joinedCount = 0;
214  long long unjoinedCount = 0;
215  while ( features.nextFeature( feat ) )
216  {
217  i++;
218  if ( feedback->isCanceled() )
219  {
220  break;
221  }
222 
223  feedback->setProgress( 50 + i * step );
224 
225  if ( input2AttributeCache.count( feat.attribute( joinField1Index ) ) > 0 )
226  {
227  joinedCount++;
228  if ( sink )
229  {
230  const QgsAttributes attrs = feat.attributes();
231 
232  QList< QgsAttributes > attributes = input2AttributeCache.values( feat.attribute( joinField1Index ) );
233  QList< QgsAttributes >::iterator attrsIt = attributes.begin();
234  for ( ; attrsIt != attributes.end(); ++attrsIt )
235  {
236  QgsAttributes newAttrs = attrs;
237  newAttrs.append( *attrsIt );
238  feat.setAttributes( newAttrs );
239  if ( !sink->addFeature( feat, QgsFeatureSink::FastInsert ) )
240  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
241  }
242  }
243  }
244  else
245  {
246  // no matching for input feature
247  if ( sink && !discardNonMatching )
248  {
249  if ( !sink->addFeature( feat, QgsFeatureSink::FastInsert ) )
250  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
251  }
252  if ( sinkNonMatching1 )
253  {
254  if ( !sinkNonMatching1->addFeature( feat, QgsFeatureSink::FastInsert ) )
255  throw QgsProcessingException( writeFeatureError( sinkNonMatching1.get(), parameters, QStringLiteral( "NON_MATCHING" ) ) );
256  }
257  unjoinedCount++;
258  }
259  }
260 
261  feedback->pushInfo( QObject::tr( "%1 feature(s) from input layer were successfully matched" ).arg( joinedCount ) );
262  if ( unjoinedCount > 0 )
263  feedback->reportError( QObject::tr( "%1 feature(s) from input layer could not be matched" ).arg( unjoinedCount ) );
264 
265  QVariantMap outputs;
266  if ( sink )
267  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
268  outputs.insert( QStringLiteral( "JOINED_COUNT" ), joinedCount );
269  outputs.insert( QStringLiteral( "UNJOINABLE_COUNT" ), unjoinedCount );
270  if ( sinkNonMatching1 )
271  outputs.insert( QStringLiteral( "NON_MATCHING" ), destNonMatching1 );
272  return outputs;
273 }
274 
275 
A vector of attributes.
Definition: qgsattributes.h:58
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:153
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
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
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
bool rename(int fieldIdx, const QString &name)
Renames a name of field.
Definition: qgsfields.cpp:72
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
@ FlagSkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A numeric output for processing algorithms.
A boolean parameter for processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
A string parameter for processing algorithms.
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix=QString())
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:48
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463