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