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