QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
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
104Qgis::ProcessingAlgorithmDocumentationFlags QgsJoinByLocationAlgorithm::documentationFlags() const
105{
107}
108
109QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance() const
110{
111 return new QgsJoinByLocationAlgorithm();
112}
113
114QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
115{
116 return { QObject::tr( "intersect" ),
117 QObject::tr( "contain" ),
118 QObject::tr( "equal" ),
119 QObject::tr( "touch" ),
120 QObject::tr( "overlap" ),
121 QObject::tr( "are within" ),
122 QObject::tr( "cross" ) };
123}
124
125QVariantMap QgsJoinByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
126{
127 mBaseSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
128 if ( !mBaseSource )
129 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
130
131 mJoinSource.reset( parameterAsSource( parameters, QStringLiteral( "JOIN" ), context ) );
132 if ( !mJoinSource )
133 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "JOIN" ) ) );
134
135 mJoinMethod = static_cast< JoinMethod >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
136
137 const QStringList joinedFieldNames = parameterAsStrings( parameters, QStringLiteral( "JOIN_FIELDS" ), context );
138
139 mPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
140 sortPredicates( mPredicates );
141
142 QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
143
144 QgsFields joinFields;
145 if ( joinedFieldNames.empty() )
146 {
147 joinFields = mJoinSource->fields();
148 mJoinedFieldIndices = joinFields.allAttributesList();
149 }
150 else
151 {
152 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
153 for ( const QString &field : joinedFieldNames )
154 {
155 int index = mJoinSource->fields().lookupField( field );
156 if ( index >= 0 )
157 {
158 mJoinedFieldIndices << index;
159 joinFields.append( mJoinSource->fields().at( index ) );
160 }
161 }
162 }
163
164 if ( !prefix.isEmpty() )
165 {
166 for ( int i = 0; i < joinFields.count(); ++i )
167 {
168 joinFields.rename( i, prefix + joinFields[ i ].name() );
169 }
170 }
171
172 const QgsFields outputFields = QgsProcessingUtils::combineFields( mBaseSource->fields(), joinFields );
173
174 QString joinedSinkId;
175 mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, joinedSinkId, outputFields,
176 mBaseSource->wkbType(), mBaseSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
177
178 if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !mJoinedFeatures )
179 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
180
181 mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
182
183 QString nonMatchingSinkId;
184 mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(),
185 mBaseSource->wkbType(), mBaseSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
186 if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
187 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
188
189 switch ( mJoinMethod )
190 {
191 case OneToMany:
192 case JoinToFirst:
193 {
194 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
195 {
196 // joining FEWER features to a layer with MORE features. So we iterate over the FEW features and find matches from the MANY
197 processAlgorithmByIteratingOverInputSource( context, feedback );
198 }
199 else
200 {
201 // default -- iterate over the join source and match back to the base source. We do this on the assumption that the most common
202 // use case is joining a points layer to a polygon layer (taking polygon attributes and adding them to the points), so by iterating
203 // over the polygons we can take advantage of prepared geometries for the spatial relationship test.
204
205 // TODO - consider using more heuristics to determine whether it's always best to iterate over the join
206 // source.
207 processAlgorithmByIteratingOverJoinedSource( context, feedback );
208 }
209 break;
210 }
211
212 case JoinToLargestOverlap:
213 processAlgorithmByIteratingOverInputSource( context, feedback );
214 break;
215 }
216
217 QVariantMap outputs;
218 if ( mJoinedFeatures )
219 {
220 outputs.insert( QStringLiteral( "OUTPUT" ), joinedSinkId );
221 }
222 if ( mUnjoinedFeatures )
223 {
224 outputs.insert( QStringLiteral( "NON_MATCHING" ), nonMatchingSinkId );
225 }
226
227 // need to release sinks to finalize writing
228 mJoinedFeatures.reset();
229 mUnjoinedFeatures.reset();
230
231 outputs.insert( QStringLiteral( "JOINED_COUNT" ), static_cast< long long >( mJoinedCount ) );
232 return outputs;
233}
234
235bool QgsJoinByLocationAlgorithm::featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature, const QList<int> &predicates )
236{
237 const QgsAbstractGeometry *geom = feature.geometry().constGet();
238 bool ok = false;
239 for ( const int predicate : predicates )
240 {
241 switch ( predicate )
242 {
243 case 0:
244 // intersects
245 if ( engine->intersects( geom ) )
246 {
247 ok = true;
248 }
249 break;
250 case 1:
251 // contains
252 if ( comparingToJoinedFeature )
253 {
254 if ( engine->contains( geom ) )
255 {
256 ok = true;
257 }
258 }
259 else
260 {
261 if ( engine->within( geom ) )
262 {
263 ok = true;
264 }
265 }
266 break;
267 case 2:
268 // equals
269 if ( engine->isEqual( geom ) )
270 {
271 ok = true;
272 }
273 break;
274 case 3:
275 // touches
276 if ( engine->touches( geom ) )
277 {
278 ok = true;
279 }
280 break;
281 case 4:
282 // overlaps
283 if ( engine->overlaps( geom ) )
284 {
285 ok = true;
286 }
287 break;
288 case 5:
289 // within
290 if ( comparingToJoinedFeature )
291 {
292 if ( engine->within( geom ) )
293 {
294 ok = true;
295 }
296 }
297 else
298 {
299 if ( engine->contains( geom ) )
300 {
301 ok = true;
302 }
303 }
304 break;
305 case 6:
306 // crosses
307 if ( engine->crosses( geom ) )
308 {
309 ok = true;
310 }
311 break;
312 }
313 if ( ok )
314 return ok;
315 }
316 return ok;
317}
318
319void QgsJoinByLocationAlgorithm::processAlgorithmByIteratingOverJoinedSource( QgsProcessingContext &context, QgsProcessingFeedback *feedback )
320{
321 if ( mBaseSource->hasSpatialIndex() == Qgis::SpatialIndexPresence::NotPresent )
322 feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
323
324 QgsFeatureIterator joinIter = mJoinSource->getFeatures( QgsFeatureRequest().setDestinationCrs( mBaseSource->sourceCrs(), context.transformContext() ).setSubsetOfAttributes( mJoinedFieldIndices ) );
325 QgsFeature f;
326
327 // Create output vector layer with additional attributes
328 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
329 long i = 0;
330 while ( joinIter.nextFeature( f ) )
331 {
332 if ( feedback->isCanceled() )
333 break;
334
335 processFeatureFromJoinSource( f, feedback );
336
337 i++;
338 feedback->setProgress( i * step );
339 }
340
341 if ( !mDiscardNonMatching || mUnjoinedFeatures )
342 {
343 QgsFeatureIds unjoinedIds = mBaseSource->allFeatureIds();
344 unjoinedIds.subtract( mAddedIds );
345
346 QgsFeature f2;
347 QgsFeatureRequest remainings = QgsFeatureRequest().setFilterFids( unjoinedIds );
348 QgsFeatureIterator remainIter = mBaseSource->getFeatures( remainings );
349
350 QgsAttributes emptyAttributes;
351 emptyAttributes.reserve( mJoinedFieldIndices.count() );
352 for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
353 emptyAttributes << QVariant();
354
355 while ( remainIter.nextFeature( f2 ) )
356 {
357 if ( feedback->isCanceled() )
358 break;
359
360 if ( mJoinedFeatures && !mDiscardNonMatching )
361 {
362 QgsAttributes attributes = f2.attributes();
363 attributes.append( emptyAttributes );
364 QgsFeature outputFeature( f2 );
365 outputFeature.setAttributes( attributes );
366 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
367 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
368 }
369
370 if ( mUnjoinedFeatures )
371 {
372 if ( !mUnjoinedFeatures->addFeature( f2, QgsFeatureSink::FastInsert ) )
373 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
374 }
375 }
376 }
377}
378
379void QgsJoinByLocationAlgorithm::processAlgorithmByIteratingOverInputSource( QgsProcessingContext &context, QgsProcessingFeedback *feedback )
380{
381 if ( mJoinSource->hasSpatialIndex() == Qgis::SpatialIndexPresence::NotPresent )
382 feedback->pushWarning( QObject::tr( "No spatial index exists for join layer, performance will be severely degraded" ) );
383
384 QgsFeatureIterator it = mBaseSource->getFeatures();
385 QgsFeature f;
386
387 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
388 long i = 0;
389 while ( it .nextFeature( f ) )
390 {
391 if ( feedback->isCanceled() )
392 break;
393
394 processFeatureFromInputSource( f, context, feedback );
395
396 i++;
397 feedback->setProgress( i * step );
398 }
399}
400
401void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
402{
403 // Sort predicate list so that faster predicates are earlier in the list
404 // Some predicates in GEOS do not have prepared geometry implementations, and are slow to calculate. So if users
405 // are testing multiple predicates, make sure the optimised ones are always tested first just in case we can shortcut
406 // these slower ones
407
408 std::sort( predicates.begin(), predicates.end(), []( int a, int b ) -> bool
409 {
410 // return true if predicate a is faster than b
411
412 if ( a == 0 ) // intersects is fastest
413 return true;
414 else if ( b == 0 )
415 return false;
416
417 else if ( a == 5 ) // contains is fast for polygons
418 return true;
419 else if ( b == 5 )
420 return false;
421
422 // that's it, the rest don't have optimised prepared methods (as of GEOS 3.8)
423 return a < b;
424 } );
425}
426
427bool QgsJoinByLocationAlgorithm::processFeatureFromJoinSource( QgsFeature &joinFeature, QgsProcessingFeedback *feedback )
428{
429 if ( !joinFeature.hasGeometry() )
430 return false;
431
432 const QgsGeometry featGeom = joinFeature.geometry();
433 std::unique_ptr< QgsGeometryEngine > engine;
435 QgsFeatureIterator it = mBaseSource->getFeatures( req );
436 QgsFeature baseFeature;
437 bool ok = false;
438 QgsAttributes joinAttributes;
439
440 while ( it.nextFeature( baseFeature ) )
441 {
442 if ( feedback->isCanceled() )
443 break;
444
445 switch ( mJoinMethod )
446 {
447 case JoinToFirst:
448 if ( mAddedIds.contains( baseFeature.id() ) )
449 {
450 // already added this feature, and user has opted to only output first match
451 continue;
452 }
453 break;
454
455 case OneToMany:
456 break;
457
458 case JoinToLargestOverlap:
459 Q_ASSERT_X( false, "QgsJoinByLocationAlgorithm::processFeatureFromJoinSource", "processFeatureFromJoinSource should not be used with join to largest overlap method" );
460 }
461
462 if ( !engine )
463 {
464 engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
465 engine->prepareGeometry();
466 for ( int ix : std::as_const( mJoinedFieldIndices ) )
467 {
468 joinAttributes.append( joinFeature.attribute( ix ) );
469 }
470 }
471 if ( featureFilter( baseFeature, engine.get(), false, mPredicates ) )
472 {
473 if ( mJoinedFeatures )
474 {
475 QgsFeature outputFeature( baseFeature );
476 outputFeature.setAttributes( baseFeature.attributes() + joinAttributes );
477 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
478 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
479 }
480 if ( !ok )
481 ok = true;
482
483 mAddedIds.insert( baseFeature.id() );
484 mJoinedCount++;
485 }
486 }
487 return ok;
488}
489
490bool QgsJoinByLocationAlgorithm::processFeatureFromInputSource( QgsFeature &baseFeature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
491{
492 if ( !baseFeature.hasGeometry() )
493 {
494 // no geometry, treat as if we didn't find a match...
495 if ( mJoinedFeatures && !mDiscardNonMatching )
496 {
497 QgsAttributes emptyAttributes;
498 emptyAttributes.reserve( mJoinedFieldIndices.count() );
499 for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
500 emptyAttributes << QVariant();
501
502 QgsAttributes attributes = baseFeature.attributes();
503 attributes.append( emptyAttributes );
504 QgsFeature outputFeature( baseFeature );
505 outputFeature.setAttributes( attributes );
506 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
507 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
508 }
509
510 if ( mUnjoinedFeatures )
511 {
512 if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
513 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
514 }
515
516 return false;
517 }
518
519 const QgsGeometry featGeom = baseFeature.geometry();
520 std::unique_ptr< QgsGeometryEngine > engine;
521 QgsFeatureRequest req = QgsFeatureRequest().setDestinationCrs( mBaseSource->sourceCrs(), context.transformContext() ).setFilterRect( featGeom.boundingBox() ).setSubsetOfAttributes( mJoinedFieldIndices );
522
523 QgsFeatureIterator it = mJoinSource->getFeatures( req );
524 QgsFeature joinFeature;
525 bool ok = false;
526
527 double largestOverlap = std::numeric_limits< double >::lowest();
528 QgsFeature bestMatch;
529
530 while ( it.nextFeature( joinFeature ) )
531 {
532 if ( feedback->isCanceled() )
533 break;
534
535 if ( !engine )
536 {
537 engine.reset( QgsGeometry::createGeometryEngine( featGeom.constGet() ) );
538 engine->prepareGeometry();
539 }
540
541 if ( featureFilter( joinFeature, engine.get(), true, mPredicates ) )
542 {
543 switch ( mJoinMethod )
544 {
545 case JoinToFirst:
546 case OneToMany:
547 if ( mJoinedFeatures )
548 {
549 QgsAttributes joinAttributes = baseFeature.attributes();
550 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
551 for ( int ix : std::as_const( mJoinedFieldIndices ) )
552 {
553 joinAttributes.append( joinFeature.attribute( ix ) );
554 }
555
556 QgsFeature outputFeature( baseFeature );
557 outputFeature.setAttributes( joinAttributes );
558 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
559 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
560 }
561 break;
562
563 case JoinToLargestOverlap:
564 {
565 // calculate area of overlap
566 std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.geometry().constGet() ) );
567 double overlap = 0;
568 switch ( QgsWkbTypes::geometryType( intersection->wkbType() ) )
569 {
571 overlap = intersection->length();
572 break;
573
575 overlap = intersection->area();
576 break;
577
581 break;
582 }
583
584 if ( overlap > largestOverlap )
585 {
586 largestOverlap = overlap;
587 bestMatch = joinFeature;
588 }
589 break;
590 }
591 }
592
593 ok = true;
594
595 if ( mJoinMethod == JoinToFirst )
596 break;
597 }
598 }
599
600 switch ( mJoinMethod )
601 {
602 case OneToMany:
603 case JoinToFirst:
604 break;
605
606 case JoinToLargestOverlap:
607 {
608 if ( bestMatch.isValid() )
609 {
610 // grab attributes from feature with best match
611 if ( mJoinedFeatures )
612 {
613 QgsAttributes joinAttributes = baseFeature.attributes();
614 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
615 for ( int ix : std::as_const( mJoinedFieldIndices ) )
616 {
617 joinAttributes.append( bestMatch.attribute( ix ) );
618 }
619
620 QgsFeature outputFeature( baseFeature );
621 outputFeature.setAttributes( joinAttributes );
622 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
623 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
624 }
625 }
626 else
627 {
628 ok = false; // shouldn't happen...
629 }
630 break;
631 }
632 }
633
634 if ( !ok )
635 {
636 // didn't find a match...
637 if ( mJoinedFeatures && !mDiscardNonMatching )
638 {
639 QgsAttributes emptyAttributes;
640 emptyAttributes.reserve( mJoinedFieldIndices.count() );
641 for ( int i = 0; i < mJoinedFieldIndices.count(); ++i )
642 emptyAttributes << QVariant();
643
644 QgsAttributes attributes = baseFeature.attributes();
645 attributes.append( emptyAttributes );
646 QgsFeature outputFeature( baseFeature );
647 outputFeature.setAttributes( attributes );
648 if ( !mJoinedFeatures->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
649 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral( "OUTPUT" ) ) );
650 }
651
652 if ( mUnjoinedFeatures )
653 {
654 if ( !mUnjoinedFeatures->addFeature( baseFeature, QgsFeatureSink::FastInsert ) )
655 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral( "NON_MATCHING" ) ) );
656 }
657 }
658 else
659 mJoinedCount++;
660
661 return ok;
662}
663
664
666
667
668
@ VectorAnyGeometry
Any vector layer with geometry.
@ NotPresent
No spatial index exists for the source.
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
@ RegeneratesPrimaryKey
Algorithm always drops any existing primary keys or FID values and regenerates them in outputs.
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3359
Abstract base class for all geometries.
A vector of attributes.
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:58
QgsAttributes attributes
Definition qgsfeature.h:67
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isValid() const
Returns the validity of this feature.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
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:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:70
int count
Definition qgsfields.h:50
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
bool rename(int fieldIdx, const QString &name)
Renames a name of field.
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.
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, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
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.
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...
QSet< QgsFeatureId > QgsFeatureIds