QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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 > () << 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
74QString QgsJoinByLocationAlgorithm::name() const
75{
76 return QStringLiteral( "joinattributesbylocation" );
77}
78
79QString QgsJoinByLocationAlgorithm::displayName() const
80{
81 return QObject::tr( "Join attributes by location" );
82}
83
84QStringList QgsJoinByLocationAlgorithm::tags() const
85{
86 return QObject::tr( "join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split( ',' );
87}
88
89QString QgsJoinByLocationAlgorithm::group() const
90{
91 return QObject::tr( "Vector general" );
92}
93
94QString QgsJoinByLocationAlgorithm::groupId() const
95{
96 return QStringLiteral( "vectorgeneral" );
97}
98
99QString 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
108QString QgsJoinByLocationAlgorithm::shortDescription() const
109{
110 return QObject::tr( "Join attributes from one vector layer to another by location." );
111}
112
113QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance() const
114{
115 return new QgsJoinByLocationAlgorithm();
116}
117
118
119QVariantMap 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 = parameterAsStrings( 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
229bool 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
313void 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
373void 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
395void 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
421bool 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
484bool 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 {
564 case Qgis::GeometryType::Line:
565 overlap = intersection->length();
566 break;
567
568 case Qgis::GeometryType::Polygon:
569 overlap = intersection->area();
570 break;
571
572 case Qgis::GeometryType::Unknown:
573 case Qgis::GeometryType::Point:
574 case Qgis::GeometryType::Null:
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:59
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: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 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: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:164
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:84
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:49
static Qgis::GeometryType geometryType(Qgis::WkbType type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:865
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
const QgsField & field
Definition: qgsfield.h:554