QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
21 
22 QString QgsJoinByAttributeAlgorithm::name() const
23 {
24  return QStringLiteral( "joinattributestable" );
25 }
26 
27 QString QgsJoinByAttributeAlgorithm::displayName() const
28 {
29  return QObject::tr( "Join attributes by field value" );
30 }
31 
32 QStringList QgsJoinByAttributeAlgorithm::tags() const
33 {
34  return QObject::tr( "join,connect,attributes,values,fields,tables" ).split( ',' );
35 }
36 
37 QString QgsJoinByAttributeAlgorithm::group() const
38 {
39  return QObject::tr( "Vector general" );
40 }
41 
42 QString QgsJoinByAttributeAlgorithm::groupId() const
43 {
44  return QStringLiteral( "vectorgeneral" );
45 }
46 
47 void QgsJoinByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
48 {
49  QStringList methods;
50  methods << QObject::tr( "Create separate feature for each matching feature (one-to-many)" )
51  << QObject::tr( "Take attributes of the first matching feature only (one-to-one)" );
52 
53  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
54  QObject::tr( "Input layer" ), QList< int>() << QgsProcessing::TypeVector ) );
55  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ),
56  QObject::tr( "Table field" ), QVariant(), QStringLiteral( "INPUT" ) ) );
57 
58  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_2" ),
59  QObject::tr( "Input layer 2" ), QList< int>() << QgsProcessing::TypeVector ) );
60  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD_2" ),
61  QObject::tr( "Table field 2" ), QVariant(), QStringLiteral( "INPUT_2" ) ) );
62 
63  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS_TO_COPY" ),
64  QObject::tr( "Layer 2 fields to copy (leave empty to copy all fields)" ),
65  QVariant(), QStringLiteral( "INPUT_2" ), QgsProcessingParameterField::Any,
66  true, true ) );
67 
68  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
69  QObject::tr( "Join type" ),
70  methods, false, 1 ) );
71  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ),
72  QObject::tr( "Discard records which could not be joined" ),
73  false ) );
74 
75  addParameter( new QgsProcessingParameterString( QStringLiteral( "PREFIX" ),
76  QObject::tr( "Joined field prefix" ), QVariant(), false, true ) );
77 
78  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ) ) );
79 }
80 
81 QString QgsJoinByAttributeAlgorithm::shortHelpString() const
82 {
83  return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the "
84  "input one, with additional attributes in its attribute table.\n\n"
85  "The additional attributes and their values are taken from a second vector layer. An attribute is selected "
86  "in each of them to define the join criteria." );
87 }
88 
89 QgsJoinByAttributeAlgorithm *QgsJoinByAttributeAlgorithm::createInstance() const
90 {
91  return new QgsJoinByAttributeAlgorithm();
92 }
93 
94 QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
95 {
96  int joinMethod = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
97  bool discardNonMatching = parameterAsBool( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
98 
99  std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
100  if ( !input )
101  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
102 
103  std::unique_ptr< QgsProcessingFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
104  if ( !input2 )
105  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_2" ) ) );
106 
107  QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
108 
109  QString field1Name = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
110  QString field2Name = parameterAsString( parameters, QStringLiteral( "FIELD_2" ), context );
111  const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
112 
113  int joinField1Index = input->fields().lookupField( field1Name );
114  int joinField2Index = input2->fields().lookupField( field2Name );
115  if ( joinField1Index < 0 || joinField2Index < 0 )
116  throw QgsProcessingException( QObject::tr( "Invalid join fields" ) );
117 
118  QgsFields outFields2;
119  QgsAttributeList fields2Indices;
120  if ( fieldsToCopy.empty() )
121  {
122  outFields2 = input2->fields();
123  for ( int i = 0; i < outFields2.count(); ++i )
124  {
125  fields2Indices << i;
126  }
127  }
128  else
129  {
130  for ( const QString &field : fieldsToCopy )
131  {
132  int index = input2->fields().lookupField( field );
133  if ( index >= 0 )
134  {
135  fields2Indices << index;
136  outFields2.append( input2->fields().at( index ) );
137  }
138  }
139  }
140 
141  if ( !prefix.isEmpty() )
142  {
143  for ( int i = 0; i < outFields2.count(); ++i )
144  {
145  outFields2[ i ].setName( prefix + outFields2[ i ].name() );
146  }
147  }
148 
149  QgsAttributeList fields2Fetch = fields2Indices;
150  fields2Fetch << joinField2Index;
151 
152  QgsFields outFields = QgsProcessingUtils::combineFields( input->fields(), outFields2 );
153 
154  QString dest;
155  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
156  input->wkbType(), input->sourceCrs() ) );
157  if ( !sink )
158  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
159 
160 
161  // cache attributes of input2
162  QMultiHash< QVariant, QgsAttributes > input2AttributeCache;
163  QgsFeatureIterator features = input2->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( fields2Fetch ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
164  double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
165  int i = 0;
166  QgsFeature feat;
167  while ( features.nextFeature( feat ) )
168  {
169  i++;
170  if ( feedback->isCanceled() )
171  {
172  break;
173  }
174 
175  feedback->setProgress( i * step );
176 
177  if ( joinMethod == 1 && input2AttributeCache.contains( feat.attribute( joinField2Index ) ) )
178  continue;
179 
180  // only keep selected attributes
181  QgsAttributes attributes;
182  for ( int j = 0; j < feat.attributes().count(); ++j )
183  {
184  if ( ! fields2Indices.contains( j ) )
185  continue;
186  attributes << feat.attribute( j );
187  }
188 
189  input2AttributeCache.insert( feat.attribute( joinField2Index ), attributes );
190  }
191 
192  // Create output vector layer with additional attribute
193  step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
195  i = 0;
196  while ( features.nextFeature( feat ) )
197  {
198  i++;
199  if ( feedback->isCanceled() )
200  {
201  break;
202  }
203 
204  feedback->setProgress( 50 + i * step );
205 
206  if ( input2AttributeCache.count( feat.attribute( joinField1Index ) ) > 0 )
207  {
208  QgsAttributes attrs = feat.attributes();
209 
210  QList< QgsAttributes > attributes = input2AttributeCache.values( feat.attribute( joinField1Index ) );
211  QList< QgsAttributes >::iterator attrsIt = attributes.begin();
212  for ( ; attrsIt != attributes.end(); ++attrsIt )
213  {
214  QgsAttributes newAttrs = attrs;
215  newAttrs.append( *attrsIt );
216  feat.setAttributes( newAttrs );
217  sink->addFeature( feat, QgsFeatureSink::FastInsert );
218  }
219  }
220  else if ( !discardNonMatching )
221  {
222  sink->addFeature( feat, QgsFeatureSink::FastInsert );
223  }
224  }
225 
226  QVariantMap outputs;
227  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
228  return outputs;
229 }
230 
231 
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
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
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
int count() const
Returns number of items.
Definition: qgsfields.cpp:115
A feature sink output for processing algorithms.
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)
Append 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:58
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:255
Contains information about the context in which a processing algorithm is executed.
A string parameter for processing algorithms.
QgsAttributes attributes
Definition: qgsfeature.h:72