30void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
34 auto predicateParam = std::make_unique<QgsProcessingParameterEnum>( QStringLiteral(
"PREDICATE" ), QObject::tr(
"Features they (geometric predicate)" ), translatedPredicates(),
true, 0 );
35 QVariantMap predicateMetadata;
36 QVariantMap widgetMetadata;
37 widgetMetadata.insert( QStringLiteral(
"useCheckBoxes" ),
true );
38 widgetMetadata.insert( QStringLiteral(
"columns" ), 2 );
39 predicateMetadata.insert( QStringLiteral(
"widget_wrapper" ), widgetMetadata );
40 predicateParam->setMetadata( predicateMetadata );
41 addParameter( predicateParam.release() );
45 QStringList joinMethods;
46 joinMethods << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
47 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
48 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
49 addParameter(
new QgsProcessingParameterEnum( QStringLiteral(
"METHOD" ), QObject::tr(
"Join type" ), joinMethods,
false,
static_cast<int>( OneToMany ) ) );
50 addParameter(
new QgsProcessingParameterBoolean( QStringLiteral(
"DISCARD_NONMATCHING" ), QObject::tr(
"Discard records which could not be joined" ),
false ) );
51 addParameter(
new QgsProcessingParameterString( QStringLiteral(
"PREFIX" ), QObject::tr(
"Joined field prefix" ), QVariant(),
false,
true ) );
54 addOutput(
new QgsProcessingOutputNumber( QStringLiteral(
"JOINED_COUNT" ), QObject::tr(
"Number of joined features from input table" ) ) );
57QString QgsJoinByLocationAlgorithm::name()
const
59 return QStringLiteral(
"joinattributesbylocation" );
62QString QgsJoinByLocationAlgorithm::displayName()
const
64 return QObject::tr(
"Join attributes by location" );
67QStringList QgsJoinByLocationAlgorithm::tags()
const
69 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
72QString QgsJoinByLocationAlgorithm::group()
const
74 return QObject::tr(
"Vector general" );
77QString QgsJoinByLocationAlgorithm::groupId()
const
79 return QStringLiteral(
"vectorgeneral" );
82QString QgsJoinByLocationAlgorithm::shortHelpString()
const
84 return QObject::tr(
"This algorithm takes an input vector layer and creates a new vector layer "
85 "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
86 "The additional attributes and their values are taken from a second vector layer. "
87 "A spatial criteria is applied to select the values from the second layer that are added "
88 "to each feature from the first layer in the resulting one." );
91QString QgsJoinByLocationAlgorithm::shortDescription()
const
93 return QObject::tr(
"Joins attributes from one vector layer to another by location." );
101QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
103 return new QgsJoinByLocationAlgorithm();
106QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
108 return { QObject::tr(
"intersect" ), QObject::tr(
"contain" ), QObject::tr(
"equal" ), QObject::tr(
"touch" ), QObject::tr(
"overlap" ), QObject::tr(
"are within" ), QObject::tr(
"cross" ) };
113 mBaseSource.reset( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
117 mJoinSource.reset( parameterAsSource( parameters, QStringLiteral(
"JOIN" ), context ) );
121 mJoinMethod =
static_cast<JoinMethod
>( parameterAsEnum( parameters, QStringLiteral(
"METHOD" ), context ) );
123 const QStringList joinedFieldNames = parameterAsStrings( parameters, QStringLiteral(
"JOIN_FIELDS" ), context );
125 mPredicates = parameterAsEnums( parameters, QStringLiteral(
"PREDICATE" ), context );
126 sortPredicates( mPredicates );
128 QString prefix = parameterAsString( parameters, QStringLiteral(
"PREFIX" ), context );
131 if ( joinedFieldNames.empty() )
133 joinFields = mJoinSource->fields();
138 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
139 for (
const QString &field : joinedFieldNames )
141 int index = mJoinSource->fields().lookupField( field );
144 mJoinedFieldIndices << index;
145 joinFields.
append( mJoinSource->fields().at( index ) );
150 if ( !prefix.isEmpty() )
152 for (
int i = 0; i < joinFields.
count(); ++i )
154 joinFields.
rename( i, prefix + joinFields[i].name() );
160 QString joinedSinkId;
161 mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, joinedSinkId, outputFields, mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
163 if ( parameters.value( QStringLiteral(
"OUTPUT" ) ).isValid() && !mJoinedFeatures )
166 mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral(
"DISCARD_NONMATCHING" ), context );
168 QString nonMatchingSinkId;
169 mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(), mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
170 if ( parameters.value( QStringLiteral(
"NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
173 switch ( mJoinMethod )
178 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
181 processAlgorithmByIteratingOverInputSource( context, feedback );
191 processAlgorithmByIteratingOverJoinedSource( context, feedback );
196 case JoinToLargestOverlap:
197 processAlgorithmByIteratingOverInputSource( context, feedback );
202 if ( mJoinedFeatures )
204 mJoinedFeatures->finalize();
205 outputs.insert( QStringLiteral(
"OUTPUT" ), joinedSinkId );
207 if ( mUnjoinedFeatures )
209 mUnjoinedFeatures->finalize();
210 outputs.insert( QStringLiteral(
"NON_MATCHING" ), nonMatchingSinkId );
214 mJoinedFeatures.reset();
215 mUnjoinedFeatures.reset();
217 outputs.insert( QStringLiteral(
"JOINED_COUNT" ),
static_cast<long long>( mJoinedCount ) );
221bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
225 for (
const int predicate : predicates )
238 if ( comparingToJoinedFeature )
247 if ( engine->
within( geom ) )
276 if ( comparingToJoinedFeature )
278 if ( engine->
within( geom ) )
308 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
314 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
321 processFeatureFromJoinSource( f, feedback );
327 if ( !mDiscardNonMatching || mUnjoinedFeatures )
330 unjoinedIds.subtract( mAddedIds );
337 emptyAttributes.reserve( mJoinedFieldIndices.count() );
338 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
339 emptyAttributes << QVariant();
346 if ( mJoinedFeatures && !mDiscardNonMatching )
349 attributes.append( emptyAttributes );
351 outputFeature.setAttributes( attributes );
353 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
356 if ( mUnjoinedFeatures )
359 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
368 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
373 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
380 processFeatureFromInputSource( f, context, feedback );
387void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
394 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool {
418 std::unique_ptr<QgsGeometryEngine> engine;
430 switch ( mJoinMethod )
433 if ( mAddedIds.contains( baseFeature.
id() ) )
443 case JoinToLargestOverlap:
444 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
450 engine->prepareGeometry();
451 for (
int ix : std::as_const( mJoinedFieldIndices ) )
453 joinAttributes.append( joinFeature.
attribute( ix ) );
456 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
458 if ( mJoinedFeatures )
461 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
463 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
468 mAddedIds.insert( baseFeature.
id() );
480 if ( mJoinedFeatures && !mDiscardNonMatching )
483 emptyAttributes.reserve( mJoinedFieldIndices.count() );
484 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
485 emptyAttributes << QVariant();
488 attributes.append( emptyAttributes );
490 outputFeature.setAttributes( attributes );
492 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
495 if ( mUnjoinedFeatures )
498 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
505 std::unique_ptr<QgsGeometryEngine> engine;
512 double largestOverlap = std::numeric_limits<double>::lowest();
523 engine->prepareGeometry();
526 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
528 switch ( mJoinMethod )
532 if ( mJoinedFeatures )
535 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
536 for (
int ix : std::as_const( mJoinedFieldIndices ) )
538 joinAttributes.append( joinFeature.
attribute( ix ) );
542 outputFeature.setAttributes( joinAttributes );
544 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
548 case JoinToLargestOverlap:
551 std::unique_ptr<QgsAbstractGeometry> intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
556 overlap = intersection->length();
560 overlap = intersection->area();
569 if ( overlap > largestOverlap )
571 largestOverlap = overlap;
572 bestMatch = joinFeature;
580 if ( mJoinMethod == JoinToFirst )
585 switch ( mJoinMethod )
591 case JoinToLargestOverlap:
596 if ( mJoinedFeatures )
599 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
600 for (
int ix : std::as_const( mJoinedFieldIndices ) )
602 joinAttributes.append( bestMatch.
attribute( ix ) );
606 outputFeature.setAttributes( joinAttributes );
608 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
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 );
634 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
637 if ( mUnjoinedFeatures )
640 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
@ VectorAnyGeometry
Any vector layer with geometry.
@ NotPresent
No spatial index exists for the source.
@ RegeneratesPrimaryKey
Algorithm always drops any existing primary keys or FID values and regenerates them in outputs.
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Abstract base class for all geometries.
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...
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.
void setProgress(double progress)
Sets the current progress for the feedback object.
Container of fields for a vector layer.
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
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