QGIS API Documentation  3.6.0-Noosa (5873452)
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 = qgis::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  int joinMethod = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
107  bool discardNonMatching = parameterAsBool( 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  QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
118 
119  QString field1Name = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
120  QString field2Name = parameterAsString( parameters, QStringLiteral( "FIELD_2" ), context );
121  const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
122 
123  int joinField1Index = input->fields().lookupField( field1Name );
124  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  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[ i ].setName( prefix + outFields2[ i ].name() );
158  }
159  }
160 
161  QgsAttributeList fields2Fetch = fields2Indices;
162  fields2Fetch << joinField2Index;
163 
164  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  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  sink->addFeature( feat, QgsFeatureSink::FastInsert );
240  }
241  }
242  }
243  else
244  {
245  // no matching for input feature
246  if ( sink && !discardNonMatching )
247  {
248  sink->addFeature( feat, QgsFeatureSink::FastInsert );
249  }
250  if ( sinkNonMatching1 )
251  {
252  sinkNonMatching1->addFeature( feat );
253  }
254  unjoinedCount++;
255  }
256  }
257 
258  feedback->pushInfo( QObject::tr( "%1 feature(s) from input layer were successfully matched" ).arg( joinedCount ) );
259  if ( unjoinedCount > 0 )
260  feedback->reportError( QObject::tr( "%1 feature(s) from input layer could not be matched" ).arg( unjoinedCount ) );
261 
262  QVariantMap outputs;
263  if ( sink )
264  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
265  outputs.insert( QStringLiteral( "JOINED_COUNT" ), joinedCount );
266  outputs.insert( QStringLiteral( "UNJOINABLE_COUNT" ), unjoinedCount );
267  if ( sinkNonMatching1 )
268  outputs.insert( QStringLiteral( "NON_MATCHING" ), destNonMatching1 );
269  return outputs;
270 }
271 
272 
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
A boolean parameter for processing algorithms.
Wrapper for iterator of features from vector data provider or vector layer.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for providing feedback from a processing algorithm.
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
A vector layer or feature source field parameter for processing algorithms.
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:42
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
A numeric output for processing algorithms.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
A feature sink output for processing algorithms.
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
An enum based parameter for processing algorithms, allowing for selection from predefined values...
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB)
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
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
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
An input feature source (such as vector layers) parameter for processing algorithms.
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsattributes.h:57
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
Contains information about the context in which a processing algorithm is executed.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A string parameter for processing algorithms.
Any vector layer with geometry.
Definition: qgsprocessing.h:47
QgsAttributes attributes
Definition: qgsfeature.h:65
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.