29 void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
32 QObject::tr(
"Join to features in" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
34 QStringList predicates;
35 predicates << QObject::tr(
"intersect" )
36 << QObject::tr(
"contain" )
37 << QObject::tr(
"equal" )
38 << QObject::tr(
"touch" )
39 << QObject::tr(
"overlap" )
40 << QObject::tr(
"are within" )
41 << QObject::tr(
"cross" );
43 std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral(
"PREDICATE" ), QObject::tr(
"Features they (geometric predicate)" ), predicates,
true, 0 );
44 QVariantMap predicateMetadata;
45 QVariantMap widgetMetadata;
46 widgetMetadata.insert( QStringLiteral(
"useCheckBoxes" ),
true );
47 widgetMetadata.insert( QStringLiteral(
"columns" ), 2 );
48 predicateMetadata.insert( QStringLiteral(
"widget_wrapper" ), widgetMetadata );
49 predicateParam->setMetadata( predicateMetadata );
50 addParameter( predicateParam.release() );
52 QObject::tr(
"By comparing to" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
54 QObject::tr(
"Fields to add (leave empty to use all fields)" ),
57 QStringList joinMethods;
58 joinMethods << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
59 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
60 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
62 QObject::tr(
"Join type" ),
63 joinMethods,
false,
static_cast< int >( OneToMany ) ) );
65 QObject::tr(
"Discard records which could not be joined" ),
68 QObject::tr(
"Joined field prefix" ), QVariant(),
false,
true ) );
71 addOutput(
new QgsProcessingOutputNumber( QStringLiteral(
"JOINED_COUNT" ), QObject::tr(
"Number of joined features from input table" ) ) );
74 QString QgsJoinByLocationAlgorithm::name()
const
76 return QStringLiteral(
"joinattributesbylocation" );
79 QString QgsJoinByLocationAlgorithm::displayName()
const
81 return QObject::tr(
"Join attributes by location" );
84 QStringList QgsJoinByLocationAlgorithm::tags()
const
86 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
89 QString QgsJoinByLocationAlgorithm::group()
const
91 return QObject::tr(
"Vector general" );
94 QString QgsJoinByLocationAlgorithm::groupId()
const
96 return QStringLiteral(
"vectorgeneral" );
99 QString QgsJoinByLocationAlgorithm::shortHelpString()
const
101 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." );
108 QString QgsJoinByLocationAlgorithm::shortDescription()
const
110 return QObject::tr(
"Join attributes from one vector layer to another by location." );
113 QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
115 return new QgsJoinByLocationAlgorithm();
121 mBaseSource.reset( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
125 mJoinSource.reset( parameterAsSource( parameters, QStringLiteral(
"JOIN" ), context ) );
129 mJoinMethod =
static_cast< JoinMethod
>( parameterAsEnum( parameters, QStringLiteral(
"METHOD" ), context ) );
131 const QStringList joinedFieldNames = parameterAsFields( parameters, QStringLiteral(
"JOIN_FIELDS" ), context );
133 mPredicates = parameterAsEnums( parameters, QStringLiteral(
"PREDICATE" ), context );
134 sortPredicates( mPredicates );
136 QString prefix = parameterAsString( parameters, QStringLiteral(
"PREFIX" ), context );
139 if ( joinedFieldNames.empty() )
141 joinFields = mJoinSource->fields();
146 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
147 for (
const QString &
field : joinedFieldNames )
149 int index = mJoinSource->fields().lookupField(
field );
152 mJoinedFieldIndices << index;
153 joinFields.
append( mJoinSource->fields().at( index ) );
158 if ( !prefix.isEmpty() )
160 for (
int i = 0; i < joinFields.
count(); ++i )
162 joinFields.
rename( i, prefix + joinFields[ i ].name() );
168 QString joinedSinkId;
169 mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, joinedSinkId, outputFields,
172 if ( parameters.value( QStringLiteral(
"OUTPUT" ) ).isValid() && !mJoinedFeatures )
175 mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral(
"DISCARD_NONMATCHING" ), context );
177 QString nonMatchingSinkId;
178 mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(),
180 if ( parameters.value( QStringLiteral(
"NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
183 switch ( mJoinMethod )
188 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
191 processAlgorithmByIteratingOverInputSource( context, feedback );
201 processAlgorithmByIteratingOverJoinedSource( context, feedback );
206 case JoinToLargestOverlap:
207 processAlgorithmByIteratingOverInputSource( context, feedback );
212 if ( mJoinedFeatures )
214 outputs.insert( QStringLiteral(
"OUTPUT" ), joinedSinkId );
216 if ( mUnjoinedFeatures )
218 outputs.insert( QStringLiteral(
"NON_MATCHING" ), nonMatchingSinkId );
222 mJoinedFeatures.reset();
223 mUnjoinedFeatures.reset();
225 outputs.insert( QStringLiteral(
"JOINED_COUNT" ),
static_cast< long long >( mJoinedCount ) );
229 bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature )
const
233 for (
const int predicate : mPredicates )
246 if ( comparingToJoinedFeature )
255 if ( engine->
within( geom ) )
284 if ( comparingToJoinedFeature )
286 if ( engine->
within( geom ) )
316 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
322 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
329 processFeatureFromJoinSource( f, feedback );
335 if ( !mDiscardNonMatching || mUnjoinedFeatures )
338 unjoinedIds.subtract( mAddedIds );
345 emptyAttributes.reserve( mJoinedFieldIndices.count() );
346 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
347 emptyAttributes << QVariant();
354 if ( mJoinedFeatures && !mDiscardNonMatching )
357 attributes.append( emptyAttributes );
359 outputFeature.setAttributes( attributes );
361 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
364 if ( mUnjoinedFeatures )
367 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
376 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
381 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
383 while ( it .nextFeature( f ) )
388 processFeatureFromInputSource( f, context, feedback );
395 void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
402 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool
427 std::unique_ptr< QgsGeometryEngine > engine;
439 switch ( mJoinMethod )
442 if ( mAddedIds.contains( baseFeature.
id() ) )
452 case JoinToLargestOverlap:
453 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
459 engine->prepareGeometry();
460 for (
int ix : std::as_const( mJoinedFieldIndices ) )
462 joinAttributes.append( joinFeature.
attribute( ix ) );
465 if ( featureFilter( baseFeature, engine.get(),
false ) )
467 if ( mJoinedFeatures )
470 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
472 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
477 mAddedIds.insert( baseFeature.
id() );
489 if ( mJoinedFeatures && !mDiscardNonMatching )
492 emptyAttributes.reserve( mJoinedFieldIndices.count() );
493 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
494 emptyAttributes << QVariant();
497 attributes.append( emptyAttributes );
499 outputFeature.setAttributes( attributes );
501 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
504 if ( mUnjoinedFeatures )
507 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
514 std::unique_ptr< QgsGeometryEngine > engine;
521 double largestOverlap = std::numeric_limits< double >::lowest();
532 engine->prepareGeometry();
535 if ( featureFilter( joinFeature, engine.get(),
true ) )
537 switch ( mJoinMethod )
541 if ( mJoinedFeatures )
544 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
545 for (
int ix : std::as_const( mJoinedFieldIndices ) )
547 joinAttributes.append( joinFeature.
attribute( ix ) );
551 outputFeature.setAttributes( joinAttributes );
553 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
557 case JoinToLargestOverlap:
560 std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
565 overlap = intersection->length();
569 overlap = intersection->area();
578 if ( overlap > largestOverlap )
580 largestOverlap = overlap;
581 bestMatch = joinFeature;
589 if ( mJoinMethod == JoinToFirst )
594 switch ( mJoinMethod )
600 case JoinToLargestOverlap:
605 if ( mJoinedFeatures )
608 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
609 for (
int ix : std::as_const( mJoinedFieldIndices ) )
611 joinAttributes.append( bestMatch.
attribute( ix ) );
615 outputFeature.setAttributes( joinAttributes );
617 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
631 if ( mJoinedFeatures && !mDiscardNonMatching )
634 emptyAttributes.reserve( mJoinedFieldIndices.count() );
635 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
636 emptyAttributes << QVariant();
639 attributes.append( emptyAttributes );
641 outputFeature.setAttributes( attributes );
643 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
646 if ( mUnjoinedFeatures )
649 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );