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