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