QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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( "Join to features in" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
33 
34  QStringList predicates;
35  predicates << QObject::tr( "intersect" )
36  << QObject::tr( "contain" )
37  << QObject::tr( "equal" )
38  << QObject::tr( "touch" )
39  << QObject::tr( "overlap" )
40  << QObject::tr( "are within" )
41  << QObject::tr( "cross" );
42 
43  std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "PREDICATE" ), QObject::tr( "Features they (geometric predicate)" ), predicates, true, 0 );
44  QVariantMap predicateMetadata;
45  QVariantMap widgetMetadata;
46  widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
47  widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
48  predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );
49  predicateParam->setMetadata( predicateMetadata );
50  addParameter( predicateParam.release() );
51  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "JOIN" ),
52  QObject::tr( "By comparing to" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
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  QgsFeature baseFeature;
431  bool ok = false;
432  QgsAttributes joinAttributes;
433 
434  while ( it.nextFeature( baseFeature ) )
435  {
436  if ( feedback->isCanceled() )
437  break;
438 
439  switch ( mJoinMethod )
440  {
441  case JoinToFirst:
442  if ( mAddedIds.contains( baseFeature.id() ) )
443  {
444  // already added this feature, and user has opted to only output first match
445  continue;
446  }
447  break;
448 
449  case OneToMany:
450  break;
451 
452  case JoinToLargestOverlap:
453  Q_ASSERT_X( false, "QgsJoinByLocationAlgorithm::processFeatureFromJoinSource", "processFeatureFromJoinSource should not be used with join to largest overlap method" );
454  }
455 
456  if ( !engine )
457  {
458  engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
459  engine->prepareGeometry();
460  for ( int ix : std::as_const( mJoinedFieldIndices ) )
461  {
462  joinAttributes.append( joinFeature.attribute( ix ) );
463  }
464  }
465  if ( featureFilter( baseFeature, engine.get(), false ) )
466  {
467  if ( mJoinedFeatures )
468  {
469  QgsFeature outputFeature( baseFeature );
470  outputFeature.setAttributes( baseFeature.attributes() + joinAttributes );
471  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
472  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
473  }
474  if ( !ok )
475  ok = true;
476 
477  mAddedIds.insert( baseFeature.id() );
478  mJoinedCount++;
479  }
480  }
481  return ok;
482 }
483 
484 bool QgsJoinByLocationAlgorithm::processFeatureFromInputSource( QgsFeature &baseFeature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
485 {
486  if ( !baseFeature.hasGeometry() )
487  {
488  // no geometry, treat as if we didn't find a match...
489  if ( mJoinedFeatures && !mDiscardNonMatching )
490  {
491  QgsAttributes emptyAttributes;
492  emptyAttributes.reserve( mJoinedFieldIndices.count() );
493  for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
494  emptyAttributes << QVariant();
495 
496  QgsAttributes attributes = baseFeature.attributes();
497  attributes.append( emptyAttributes );
498  QgsFeature outputFeature( baseFeature );
499  outputFeature.setAttributes( attributes );
500  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
501  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
502  }
503 
504  if ( mUnjoinedFeatures )
505  {
506  if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
507  throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
508  }
509 
510  return false;
511  }
512 
513  const QgsGeometry featGeom = baseFeature.geometry();
514  std::unique_ptr< QgsGeometryEngine > engine;
515  QgsFeatureRequest req = QgsFeatureRequest().setDestinationCrs( mBaseSource->sourceCrs(), context.transformContext() ).setFilterRect( featGeom.boundingBox() ).setSubsetOfAttributes( mJoinedFieldIndices );
516 
517  QgsFeatureIterator it = mJoinSource->getFeatures( req );
518  QgsFeature joinFeature;
519  bool ok = false;
520 
521  double largestOverlap = std::numeric_limits< double >::lowest();
522  QgsFeature bestMatch;
523 
524  while ( it.nextFeature( joinFeature ) )
525  {
526  if ( feedback->isCanceled() )
527  break;
528 
529  if ( !engine )
530  {
531  engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
532  engine->prepareGeometry();
533  }
534 
535  if ( featureFilter( joinFeature, engine.get(), true ) )
536  {
537  switch ( mJoinMethod )
538  {
539  case JoinToFirst:
540  case OneToMany:
541  if ( mJoinedFeatures )
542  {
543  QgsAttributes joinAttributes = baseFeature.attributes();
544  joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
545  for ( int ix : std::as_const( mJoinedFieldIndices ) )
546  {
547  joinAttributes.append( joinFeature.attribute( ix ) );
548  }
549 
550  QgsFeature outputFeature( baseFeature );
551  outputFeature.setAttributes( joinAttributes );
552  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
553  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
554  }
555  break;
556 
557  case JoinToLargestOverlap:
558  {
559  // calculate area of overlap
560  std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.geometry().constGet() ) );
561  double overlap = 0;
562  switch ( QgsWkbTypes::geometryType( intersection->wkbType() ) )
563  {
565  overlap = intersection->length();
566  break;
567 
569  overlap = intersection->area();
570  break;
571 
575  break;
576  }
577 
578  if ( overlap > largestOverlap )
579  {
580  largestOverlap = overlap;
581  bestMatch = joinFeature;
582  }
583  break;
584  }
585  }
586 
587  ok = true;
588 
589  if ( mJoinMethod == JoinToFirst )
590  break;
591  }
592  }
593 
594  switch ( mJoinMethod )
595  {
596  case OneToMany:
597  case JoinToFirst:
598  break;
599 
600  case JoinToLargestOverlap:
601  {
602  if ( bestMatch.isValid() )
603  {
604  // grab attributes from feature with best match
605  if ( mJoinedFeatures )
606  {
607  QgsAttributes joinAttributes = baseFeature.attributes();
608  joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
609  for ( int ix : std::as_const( mJoinedFieldIndices ) )
610  {
611  joinAttributes.append( bestMatch.attribute( ix ) );
612  }
613 
614  QgsFeature outputFeature( baseFeature );
615  outputFeature.setAttributes( joinAttributes );
616  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
617  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
618  }
619  }
620  else
621  {
622  ok = false; // shouldn't happen...
623  }
624  break;
625  }
626  }
627 
628  if ( !ok )
629  {
630  // didn't find a match...
631  if ( mJoinedFeatures && !mDiscardNonMatching )
632  {
633  QgsAttributes emptyAttributes;
634  emptyAttributes.reserve( mJoinedFieldIndices.count() );
635  for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
636  emptyAttributes << QVariant();
637 
638  QgsAttributes attributes = baseFeature.attributes();
639  attributes.append( emptyAttributes );
640  QgsFeature outputFeature( baseFeature );
641  outputFeature.setAttributes( attributes );
642  if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
643  throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
644  }
645 
646  if ( mUnjoinedFeatures )
647  {
648  if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
649  throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
650  }
651  }
652  else
653  mJoinedCount++;
654 
655  return ok;
656 }
657 
658 
660 
661 
662 
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:376
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