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 = std::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->
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;
430 QList<QgsFeature> filtered;
440 switch ( mJoinMethod )
443 if ( mAddedIds.contains( baseFeature.
id() ) )
453 case JoinToLargestOverlap:
454 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
460 engine->prepareGeometry();
461 for (
int ix : std::as_const( mJoinedFieldIndices ) )
463 joinAttributes.append( joinFeature.
attribute( ix ) );
466 if ( featureFilter( baseFeature, engine.get(),
false ) )
468 if ( mJoinedFeatures )
471 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
473 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
478 mAddedIds.insert( baseFeature.
id() );
490 if ( mJoinedFeatures && !mDiscardNonMatching )
493 emptyAttributes.reserve( mJoinedFieldIndices.count() );
494 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
495 emptyAttributes << QVariant();
498 attributes.append( emptyAttributes );
500 outputFeature.setAttributes( attributes );
502 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
505 if ( mUnjoinedFeatures )
508 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
515 std::unique_ptr< QgsGeometryEngine > engine;
519 QList<QgsFeature> filtered;
523 double largestOverlap = std::numeric_limits< double >::lowest();
534 engine->prepareGeometry();
537 if ( featureFilter( joinFeature, engine.get(),
true ) )
539 switch ( mJoinMethod )
543 if ( mJoinedFeatures )
546 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
547 for (
int ix : std::as_const( mJoinedFieldIndices ) )
549 joinAttributes.append( joinFeature.
attribute( ix ) );
553 outputFeature.setAttributes( joinAttributes );
555 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
559 case JoinToLargestOverlap:
562 std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
567 overlap = intersection->length();
571 overlap = intersection->area();
580 if ( overlap > largestOverlap )
582 largestOverlap = overlap;
583 bestMatch = joinFeature;
591 if ( mJoinMethod == JoinToFirst )
596 switch ( mJoinMethod )
602 case JoinToLargestOverlap:
607 if ( mJoinedFeatures )
610 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
611 for (
int ix : std::as_const( mJoinedFieldIndices ) )
613 joinAttributes.append( bestMatch.
attribute( ix ) );
617 outputFeature.setAttributes( joinAttributes );
619 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
633 if ( mJoinedFeatures && !mDiscardNonMatching )
636 emptyAttributes.reserve( mJoinedFieldIndices.count() );
637 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
638 emptyAttributes << QVariant();
641 attributes.append( emptyAttributes );
643 outputFeature.setAttributes( attributes );
645 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
648 if ( mUnjoinedFeatures )
651 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
Abstract base class for all geometries.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class 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...
@ SpatialIndexNotPresent
No spatial index exists for the source.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isValid() const
Returns the validity of this feature.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
Container of fields for a vector layer.
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
int count() const
Returns number of items.
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 SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
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).
@ TypeVectorAnyGeometry
Any vector layer with geometry.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
QSet< QgsFeatureId > QgsFeatureIds