QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsalgorithmjoinbylocation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmjoinbylocation.cpp
3  ---------------------
4  begin : January 2020
5  copyright : (C) 2020 by Alexis Roy-Lizotte
6  email : roya2 at premiertech 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 "qgsprocessing.h"
20 #include "qgsgeometryengine.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsapplication.h"
23 #include "qgsfeature.h"
24 #include "qgsfeaturesource.h"
25 
27 
28 
29 void QgsJoinByLocationAlgorithm::initAlgorithm( const QVariantMap & )
30 {
31  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
32  QObject::tr( "Base Layer" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
33  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "JOIN" ),
34  QObject::tr( "Join Layer" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
35 
36  QStringList predicates;
37  predicates << QObject::tr( "intersects" )
38  << QObject::tr( "contains" )
39  << QObject::tr( "equals" )
40  << QObject::tr( "touches" )
41  << QObject::tr( "overlaps" )
42  << QObject::tr( "within" )
43  << QObject::tr( "crosses" );
44 
45  std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "PREDICATE" ), QObject::tr( "Geometric predicate" ), predicates, true, 0 );
46  QVariantMap predicateMetadata;
47  QVariantMap widgetMetadata;
48  widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
49  widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
50  predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );
51  predicateParam->setMetadata( predicateMetadata );
52  addParameter( predicateParam.release() );
53  addParameter( new QgsProcessingParameterField( QStringLiteral( "JOIN_FIELDS" ),
54  QObject::tr( "Fields to add (leave empty to use all fields)" ),
55  QVariant(), QStringLiteral( "JOIN" ), QgsProcessingParameterField::Any, true, true ) );
56 
57  QStringList joinMethods;
58  joinMethods << QObject::tr( "Create separate feature for each matching feature (one-to-many)" )
59  << QObject::tr( "Take attributes of the first matching feature only (one-to-one)" )
60  << QObject::tr( "Take attributes of the feature with largest overlap only (one-to-one)" );
61  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
62  QObject::tr( "Join type" ),
63  joinMethods, false, static_cast< int >( OneToMany ) ) );
64  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ),
65  QObject::tr( "Discard records which could not be joined" ),
66  false ) );
67  addParameter( new QgsProcessingParameterString( QStringLiteral( "PREFIX" ),
68  QObject::tr( "Joined field prefix" ), QVariant(), false, true ) );
69  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
70  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "NON_MATCHING" ), QObject::tr( "Unjoinable features from first layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, false ) );
71  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "JOINED_COUNT" ), QObject::tr( "Number of joined features from input table" ) ) );
72 }
73 
74 QString QgsJoinByLocationAlgorithm::name() const
75 {
76  return QStringLiteral( "joinattributesbylocation" );
77 }
78 
79 QString QgsJoinByLocationAlgorithm::displayName() const
80 {
81  return QObject::tr( "Join attributes by location" );
82 }
83 
84 QStringList QgsJoinByLocationAlgorithm::tags() const
85 {
86  return QObject::tr( "join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split( ',' );
87 }
88 
89 QString QgsJoinByLocationAlgorithm::group() const
90 {
91  return QObject::tr( "Vector general" );
92 }
93 
94 QString QgsJoinByLocationAlgorithm::groupId() const
95 {
96  return QStringLiteral( "vectorgeneral" );
97 }
98 
99 QString QgsJoinByLocationAlgorithm::shortHelpString() const
100 {
101  return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer "
102  "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
103  "The additional attributes and their values are taken from a second vector layer. "
104  "A spatial criteria is applied to select the values from the second layer that are added "
105  "to each feature from the first layer in the resulting one." );
106 }
107 
108 QString QgsJoinByLocationAlgorithm::shortDescription() const
109 {
110  return QObject::tr( "Join attributes from one vector layer to another by location." );
111 }
112 
113 QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance() const
114 {
115  return new QgsJoinByLocationAlgorithm();
116 }
117 
118 
119 QVariantMap QgsJoinByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
120 {
121  mBaseSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
122  if ( !mBaseSource )
123  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
124 
125  mJoinSource.reset( parameterAsSource( parameters, QStringLiteral( "JOIN" ), context ) );
126  if ( !mJoinSource )
127  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "JOIN" ) ) );
128 
129  mJoinMethod = static_cast< JoinMethod >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
130 
131  const QStringList joinedFieldNames = parameterAsFields( parameters, QStringLiteral( "JOIN_FIELDS" ), context );
132 
133  mPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
134  sortPredicates( mPredicates );
135 
136  QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
137 
138  QgsFields joinFields;
139  if ( joinedFieldNames.empty() )
140  {
141  joinFields = mJoinSource->fields();
142  mJoinedFieldIndices = joinFields.allAttributesList();
143  }
144  else
145  {
146  mJoinedFieldIndices.reserve( joinedFieldNames.count() );
147  for ( const QString &field : joinedFieldNames )
148  {
149  int index = mJoinSource->fields().lookupField( field );
150  if ( index >= 0 )
151  {
152  mJoinedFieldIndices << index;
153  joinFields.append( mJoinSource->fields().at( index ) );
154  }
155  }
156  }
157 
158  if ( !prefix.isEmpty() )
159  {
160  for ( int i = 0; i < joinFields.count(); ++i )
161  {
162  joinFields.rename( i, prefix + joinFields[ i ].name() );
163  }
164  }
165 
166  const QgsFields outputFields = QgsProcessingUtils::combineFields( mBaseSource->fields(), joinFields );
167 
168  QString joinedSinkId;
169  mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, joinedSinkId, outputFields,
170  mBaseSource->wkbType(), mBaseSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
171 
172  if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !mJoinedFeatures )
173  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
174 
175  mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
176 
177  QString nonMatchingSinkId;
178  mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(),
179  mBaseSource->wkbType(), mBaseSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
180  if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
181  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
182 
183  switch ( mJoinMethod )
184  {
185  case OneToMany:
186  case JoinToFirst:
187  {
188  if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
189  {
190  // joining FEWER features to a layer with MORE features. So we iterate over the FEW features and find matches from the MANY
191  processAlgorithmByIteratingOverInputSource( context, feedback );
192  }
193  else
194  {
195  // default -- iterate over the join source and match back to the base source. We do this on the assumption that the most common
196  // use case is joining a points layer to a polygon layer (taking polygon attributes and adding them to the points), so by iterating
197  // over the polygons we can take advantage of prepared geometries for the spatial relationship test.
198 
199  // TODO - consider using more heuristics to determine whether it's always best to iterate over the join
200  // source.
201  processAlgorithmByIteratingOverJoinedSource( context, feedback );
202  }
203  break;
204  }
205 
206  case JoinToLargestOverlap:
207  processAlgorithmByIteratingOverInputSource( context, feedback );
208  break;
209  }
210 
211  QVariantMap outputs;
212  if ( mJoinedFeatures )
213  {
214  outputs.insert( QStringLiteral( "OUTPUT" ), joinedSinkId );
215  }
216  if ( mUnjoinedFeatures )
217  {
218  outputs.insert( QStringLiteral( "NON_MATCHING" ), nonMatchingSinkId );
219  }
220 
221  // need to release sinks to finalize writing
222  mJoinedFeatures.reset();
223  mUnjoinedFeatures.reset();
224 
225  outputs.insert( QStringLiteral( "JOINED_COUNT" ), static_cast< long long >( mJoinedCount ) );
226  return outputs;
227 }
228 
229 bool QgsJoinByLocationAlgorithm::featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature ) const
230 {
231  const QgsAbstractGeometry *geom = feature.geometry().constGet();
232  bool ok = false;
233  for ( const int predicate : mPredicates )
234  {
235  switch ( predicate )
236  {
237  case 0:
238  // intersects
239  if ( engine->intersects( geom ) )
240  {
241  ok = true;
242  }
243  break;
244  case 1:
245  // contains
246  if ( comparingToJoinedFeature )
247  {
248  if ( engine->contains( geom ) )
249  {
250  ok = true;
251  }
252  }
253  else
254  {
255  if ( engine->within( geom ) )
256  {
257  ok = true;
258  }
259  }
260  break;
261  case 2:
262  // equals
263  if ( engine->isEqual( geom ) )
264  {
265  ok = true;
266  }
267  break;
268  case 3:
269  // touches
270  if ( engine->touches( geom ) )
271  {
272  ok = true;
273  }
274  break;
275  case 4:
276  // overlaps
277  if ( engine->overlaps( geom ) )
278  {
279  ok = true;
280  }
281  break;
282  case 5:
283  // within
284  if ( comparingToJoinedFeature )
285  {
286  if ( engine->within( geom ) )
287  {
288  ok = true;
289  }
290  }
291  else
292  {
293  if ( engine->contains( geom ) )
294  {
295  ok = true;
296  }
297  }
298  break;
299  case 6:
300  // crosses
301  if ( engine->crosses( geom ) )
302  {
303  ok = true;
304  }
305  break;
306  }
307  if ( ok )
308  return ok;
309  }
310  return ok;
311 }
312 
313 void QgsJoinByLocationAlgorithm::processAlgorithmByIteratingOverJoinedSource( QgsProcessingContext &context, QgsProcessingFeedback *feedback )
314 {
315  if ( mBaseSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent )
316  feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
317 
318  QgsFeatureIterator joinIter = mJoinSource->getFeatures( QgsFeatureRequest().setDestinationCrs( mBaseSource->sourceCrs(), context.transformContext() ).setSubsetOfAttributes( mJoinedFieldIndices ) );
319  QgsFeature f;
320 
321  // Create output vector layer with additional attributes
322  const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
323  long i = 0;
324  while ( joinIter.nextFeature( f ) )
325  {
326  if ( feedback->isCanceled() )
327  break;
328 
329  processFeatureFromJoinSource( f, feedback );
330 
331  i++;
332  feedback->setProgress( i * step );
333  }
334 
335  if ( !mDiscardNonMatching || mUnjoinedFeatures )
336  {
337  QgsFeatureIds unjoinedIds = mBaseSource->allFeatureIds();
338  unjoinedIds.subtract( mAddedIds );
339 
340  QgsFeature f2;
341  QgsFeatureRequest remainings = QgsFeatureRequest().setFilterFids( unjoinedIds );
342  QgsFeatureIterator remainIter = mBaseSource->getFeatures( remainings );
343 
344  QgsAttributes emptyAttributes;
345  emptyAttributes.reserve( mJoinedFieldIndices.count() );
346  for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
347  emptyAttributes << QVariant();
348 
349  while ( remainIter.nextFeature( f2 ) )
350  {
351  if ( feedback->isCanceled() )
352  break;
353 
354  if ( mJoinedFeatures && !mDiscardNonMatching )
355  {
356  QgsAttributes attributes = f2.attributes();
357  attributes.append( emptyAttributes );
358  QgsFeature outputFeature( f2 );
359  outputFeature.setAttributes( attributes );
360  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
361  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
362  }
363 
364  if ( mUnjoinedFeatures )
365  {
366  if ( !mUnjoinedFeatures->addFeature( f2, QgsFeatureSink::FastInsert ) )
367  throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
368  }
369  }
370  }
371 }
372 
373 void QgsJoinByLocationAlgorithm::processAlgorithmByIteratingOverInputSource( QgsProcessingContext &context, QgsProcessingFeedback *feedback )
374 {
375  if ( mJoinSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent )
376  feedback->pushWarning( QObject::tr( "No spatial index exists for join layer, performance will be severely degraded" ) );
377 
378  QgsFeatureIterator it = mBaseSource->getFeatures();
379  QgsFeature f;
380 
381  const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
382  long i = 0;
383  while ( it .nextFeature( f ) )
384  {
385  if ( feedback->isCanceled() )
386  break;
387 
388  processFeatureFromInputSource( f, context, feedback );
389 
390  i++;
391  feedback->setProgress( i * step );
392  }
393 }
394 
395 void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
396 {
397  // Sort predicate list so that faster predicates are earlier in the list
398  // Some predicates in GEOS do not have prepared geometry implementations, and are slow to calculate. So if users
399  // are testing multiple predicates, make sure the optimised ones are always tested first just in case we can shortcut
400  // these slower ones
401 
402  std::sort( predicates.begin(), predicates.end(), []( int a, int b ) -> bool
403  {
404  // return true if predicate a is faster than b
405 
406  if ( a == 0 ) // intersects is fastest
407  return true;
408  else if ( b == 0 )
409  return false;
410 
411  else if ( a == 5 ) // contains is fast for polygons
412  return true;
413  else if ( b == 5 )
414  return false;
415 
416  // that's it, the rest don't have optimised prepared methods (as of GEOS 3.8)
417  return a < b;
418  } );
419 }
420 
421 bool QgsJoinByLocationAlgorithm::processFeatureFromJoinSource( QgsFeature &joinFeature, QgsProcessingFeedback *feedback )
422 {
423  if ( !joinFeature.hasGeometry() )
424  return false;
425 
426  const QgsGeometry featGeom = joinFeature.geometry();
427  std::unique_ptr< QgsGeometryEngine > engine;
429  QgsFeatureIterator it = mBaseSource->getFeatures( req );
430  QList<QgsFeature> filtered;
431  QgsFeature baseFeature;
432  bool ok = false;
433  QgsAttributes joinAttributes;
434 
435  while ( it.nextFeature( baseFeature ) )
436  {
437  if ( feedback->isCanceled() )
438  break;
439 
440  switch ( mJoinMethod )
441  {
442  case JoinToFirst:
443  if ( mAddedIds.contains( baseFeature.id() ) )
444  {
445  // already added this feature, and user has opted to only output first match
446  continue;
447  }
448  break;
449 
450  case OneToMany:
451  break;
452 
453  case JoinToLargestOverlap:
454  Q_ASSERT_X( false, "QgsJoinByLocationAlgorithm::processFeatureFromJoinSource", "processFeatureFromJoinSource should not be used with join to largest overlap method" );
455  }
456 
457  if ( !engine )
458  {
459  engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
460  engine->prepareGeometry();
461  for ( int ix : std::as_const( mJoinedFieldIndices ) )
462  {
463  joinAttributes.append( joinFeature.attribute( ix ) );
464  }
465  }
466  if ( featureFilter( baseFeature, engine.get(), false ) )
467  {
468  if ( mJoinedFeatures )
469  {
470  QgsFeature outputFeature( baseFeature );
471  outputFeature.setAttributes( baseFeature.attributes() + joinAttributes );
472  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
473  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
474  }
475  if ( !ok )
476  ok = true;
477 
478  mAddedIds.insert( baseFeature.id() );
479  mJoinedCount++;
480  }
481  }
482  return ok;
483 }
484 
485 bool QgsJoinByLocationAlgorithm::processFeatureFromInputSource( QgsFeature &baseFeature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
486 {
487  if ( !baseFeature.hasGeometry() )
488  {
489  // no geometry, treat as if we didn't find a match...
490  if ( mJoinedFeatures && !mDiscardNonMatching )
491  {
492  QgsAttributes emptyAttributes;
493  emptyAttributes.reserve( mJoinedFieldIndices.count() );
494  for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
495  emptyAttributes << QVariant();
496 
497  QgsAttributes attributes = baseFeature.attributes();
498  attributes.append( emptyAttributes );
499  QgsFeature outputFeature( baseFeature );
500  outputFeature.setAttributes( attributes );
501  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
502  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
503  }
504 
505  if ( mUnjoinedFeatures )
506  {
507  if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
508  throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
509  }
510 
511  return false;
512  }
513 
514  const QgsGeometry featGeom = baseFeature.geometry();
515  std::unique_ptr< QgsGeometryEngine > engine;
516  QgsFeatureRequest req = QgsFeatureRequest().setDestinationCrs( mBaseSource->sourceCrs(), context.transformContext() ).setFilterRect( featGeom.boundingBox() ).setSubsetOfAttributes( mJoinedFieldIndices );
517 
518  QgsFeatureIterator it = mJoinSource->getFeatures( req );
519  QList<QgsFeature> filtered;
520  QgsFeature joinFeature;
521  bool ok = false;
522 
523  double largestOverlap = std::numeric_limits< double >::lowest();
524  QgsFeature bestMatch;
525 
526  while ( it.nextFeature( joinFeature ) )
527  {
528  if ( feedback->isCanceled() )
529  break;
530 
531  if ( !engine )
532  {
533  engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
534  engine->prepareGeometry();
535  }
536 
537  if ( featureFilter( joinFeature, engine.get(), true ) )
538  {
539  switch ( mJoinMethod )
540  {
541  case JoinToFirst:
542  case OneToMany:
543  if ( mJoinedFeatures )
544  {
545  QgsAttributes joinAttributes = baseFeature.attributes();
546  joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
547  for ( int ix : std::as_const( mJoinedFieldIndices ) )
548  {
549  joinAttributes.append( joinFeature.attribute( ix ) );
550  }
551 
552  QgsFeature outputFeature( baseFeature );
553  outputFeature.setAttributes( joinAttributes );
554  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
555  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
556  }
557  break;
558 
559  case JoinToLargestOverlap:
560  {
561  // calculate area of overlap
562  std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.geometry().constGet() ) );
563  double overlap = 0;
564  switch ( QgsWkbTypes::geometryType( intersection->wkbType() ) )
565  {
567  overlap = intersection->length();
568  break;
569 
571  overlap = intersection->area();
572  break;
573 
577  break;
578  }
579 
580  if ( overlap > largestOverlap )
581  {
582  largestOverlap = overlap;
583  bestMatch = joinFeature;
584  }
585  break;
586  }
587  }
588 
589  ok = true;
590 
591  if ( mJoinMethod == JoinToFirst )
592  break;
593  }
594  }
595 
596  switch ( mJoinMethod )
597  {
598  case OneToMany:
599  case JoinToFirst:
600  break;
601 
602  case JoinToLargestOverlap:
603  {
604  if ( bestMatch.isValid() )
605  {
606  // grab attributes from feature with best match
607  if ( mJoinedFeatures )
608  {
609  QgsAttributes joinAttributes = baseFeature.attributes();
610  joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
611  for ( int ix : std::as_const( mJoinedFieldIndices ) )
612  {
613  joinAttributes.append( bestMatch.attribute( ix ) );
614  }
615 
616  QgsFeature outputFeature( baseFeature );
617  outputFeature.setAttributes( joinAttributes );
618  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
619  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
620  }
621  }
622  else
623  {
624  ok = false; // shouldn't happen...
625  }
626  break;
627  }
628  }
629 
630  if ( !ok )
631  {
632  // didn't find a match...
633  if ( mJoinedFeatures && !mDiscardNonMatching )
634  {
635  QgsAttributes emptyAttributes;
636  emptyAttributes.reserve( mJoinedFieldIndices.count() );
637  for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
638  emptyAttributes << QVariant();
639 
640  QgsAttributes attributes = baseFeature.attributes();
641  attributes.append( emptyAttributes );
642  QgsFeature outputFeature( baseFeature );
643  outputFeature.setAttributes( attributes );
644  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
645  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
646  }
647 
648  if ( mUnjoinedFeatures )
649  {
650  if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
651  throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
652  }
653  }
654  else
655  mJoinedCount++;
656 
657  return ok;
658 }
659 
660 
662 
663 
664 
Abstract base class for all geometries.
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).
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
@ 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...
@ SpatialIndexNotPresent
No spatial index exists for the source.
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
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:209
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
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
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:371
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
bool rename(int fieldIdx, const QString &name)
Renames a name of field.
Definition: qgsfields.cpp:72
A geometry engine is a low-level representation of a QgsAbstractGeometry object, optimised for use wi...
virtual bool isEqual(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if this is equal to geom.
virtual bool intersects(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom intersects this.
virtual bool touches(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom touches this.
virtual bool crosses(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom crosses this.
virtual bool overlaps(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom overlaps this.
virtual bool within(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom is within this.
virtual bool contains(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const =0
Checks if geom contains this.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
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).
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:48
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
const QgsField & field
Definition: qgsfield.h:463