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