29 void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
32 QObject::tr(
"Base Layer" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
34 QObject::tr(
"Join Layer" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) );
36 QStringList predicates;
37 predicates << QObject::tr(
"intersects" )
38 << QObject::tr(
"contains" )
39 << QObject::tr(
"equals" )
40 << QObject::tr(
"touches" )
41 << QObject::tr(
"overlaps" )
42 << QObject::tr(
"within" )
43 << QObject::tr(
"crosses" );
45 std::unique_ptr< QgsProcessingParameterEnum > predicateParam = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral(
"PREDICATE" ), QObject::tr(
"Geometric predicate" ), predicates,
true, 0 );
46 QVariantMap predicateMetadata;
47 QVariantMap widgetMetadata;
48 widgetMetadata.insert( QStringLiteral(
"useCheckBoxes" ),
true );
49 widgetMetadata.insert( QStringLiteral(
"columns" ), 2 );
50 predicateMetadata.insert( QStringLiteral(
"widget_wrapper" ), widgetMetadata );
51 predicateParam->setMetadata( predicateMetadata );
52 addParameter( predicateParam.release() );
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->
reportError( 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 );
363 if ( mUnjoinedFeatures )
372 feedback->
reportError( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
377 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
379 while ( it .nextFeature( f ) )
384 processFeatureFromInputSource( f, context, feedback );
391 void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
398 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool
423 std::unique_ptr< QgsGeometryEngine > engine;
426 QList<QgsFeature> filtered;
436 switch ( mJoinMethod )
439 if ( mAddedIds.contains( baseFeature.
id() ) )
449 case JoinToLargestOverlap:
450 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
456 engine->prepareGeometry();
457 for (
int ix : qgis::as_const( mJoinedFieldIndices ) )
459 joinAttributes.append( joinFeature.
attribute( ix ) );
462 if ( featureFilter( baseFeature, engine.get(),
false ) )
464 if ( mJoinedFeatures )
467 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
473 mAddedIds.insert( baseFeature.
id() );
485 if ( mJoinedFeatures && !mDiscardNonMatching )
488 emptyAttributes.reserve( mJoinedFieldIndices.count() );
489 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
490 emptyAttributes << QVariant();
493 attributes.append( emptyAttributes );
495 outputFeature.setAttributes( attributes );
499 if ( mUnjoinedFeatures )
506 std::unique_ptr< QgsGeometryEngine > engine;
510 QList<QgsFeature> filtered;
514 double largestOverlap = std::numeric_limits< double >::lowest();
525 engine->prepareGeometry();
528 if ( featureFilter( joinFeature, engine.get(),
true ) )
530 switch ( mJoinMethod )
534 if ( mJoinedFeatures )
537 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
538 for (
int ix : qgis::as_const( mJoinedFieldIndices ) )
540 joinAttributes.append( joinFeature.
attribute( ix ) );
544 outputFeature.setAttributes( joinAttributes );
549 case JoinToLargestOverlap:
552 std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
557 overlap = intersection->length();
561 overlap = intersection->area();
570 if ( overlap > largestOverlap )
572 largestOverlap = overlap;
573 bestMatch = joinFeature;
581 if ( mJoinMethod == JoinToFirst )
586 switch ( mJoinMethod )
592 case JoinToLargestOverlap:
597 if ( mJoinedFeatures )
600 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
601 for (
int ix : qgis::as_const( mJoinedFieldIndices ) )
603 joinAttributes.append( bestMatch.
attribute( ix ) );
607 outputFeature.setAttributes( joinAttributes );
622 if ( mJoinedFeatures && !mDiscardNonMatching )
625 emptyAttributes.reserve( mJoinedFieldIndices.count() );
626 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
627 emptyAttributes << QVariant();
630 attributes.append( emptyAttributes );
632 outputFeature.setAttributes( attributes );
636 if ( mUnjoinedFeatures )