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