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