29void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
34 std::unique_ptr< QgsProcessingParameterEnum > 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 QObject::tr(
"Fields to add (leave empty to use all fields)" ),
48 QStringList joinMethods;
49 joinMethods << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
50 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
51 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
53 QObject::tr(
"Join type" ),
54 joinMethods,
false,
static_cast< int >( OneToMany ) ) );
56 QObject::tr(
"Discard records which could not be joined" ),
59 QObject::tr(
"Joined field prefix" ), QVariant(),
false,
true ) );
62 addOutput(
new QgsProcessingOutputNumber( QStringLiteral(
"JOINED_COUNT" ), QObject::tr(
"Number of joined features from input table" ) ) );
65QString QgsJoinByLocationAlgorithm::name()
const
67 return QStringLiteral(
"joinattributesbylocation" );
70QString QgsJoinByLocationAlgorithm::displayName()
const
72 return QObject::tr(
"Join attributes by location" );
75QStringList QgsJoinByLocationAlgorithm::tags()
const
77 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
80QString QgsJoinByLocationAlgorithm::group()
const
82 return QObject::tr(
"Vector general" );
85QString QgsJoinByLocationAlgorithm::groupId()
const
87 return QStringLiteral(
"vectorgeneral" );
90QString QgsJoinByLocationAlgorithm::shortHelpString()
const
92 return QObject::tr(
"This algorithm takes an input vector layer and creates a new vector layer "
93 "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
94 "The additional attributes and their values are taken from a second vector layer. "
95 "A spatial criteria is applied to select the values from the second layer that are added "
96 "to each feature from the first layer in the resulting one." );
99QString QgsJoinByLocationAlgorithm::shortDescription()
const
101 return QObject::tr(
"Join attributes from one vector layer to another by location." );
109QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
111 return new QgsJoinByLocationAlgorithm();
114QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
116 return { QObject::tr(
"intersect" ),
117 QObject::tr(
"contain" ),
118 QObject::tr(
"equal" ),
119 QObject::tr(
"touch" ),
120 QObject::tr(
"overlap" ),
121 QObject::tr(
"are within" ),
122 QObject::tr(
"cross" ) };
127 mBaseSource.reset( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
131 mJoinSource.reset( parameterAsSource( parameters, QStringLiteral(
"JOIN" ), context ) );
135 mJoinMethod =
static_cast< JoinMethod
>( parameterAsEnum( parameters, QStringLiteral(
"METHOD" ), context ) );
137 const QStringList joinedFieldNames = parameterAsStrings( parameters, QStringLiteral(
"JOIN_FIELDS" ), context );
139 mPredicates = parameterAsEnums( parameters, QStringLiteral(
"PREDICATE" ), context );
140 sortPredicates( mPredicates );
142 QString prefix = parameterAsString( parameters, QStringLiteral(
"PREFIX" ), context );
145 if ( joinedFieldNames.empty() )
147 joinFields = mJoinSource->fields();
152 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
153 for (
const QString &field : joinedFieldNames )
155 int index = mJoinSource->fields().lookupField( field );
158 mJoinedFieldIndices << index;
159 joinFields.
append( mJoinSource->fields().at( index ) );
164 if ( !prefix.isEmpty() )
166 for (
int i = 0; i < joinFields.
count(); ++i )
168 joinFields.
rename( i, prefix + joinFields[ i ].name() );
174 QString joinedSinkId;
175 mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, joinedSinkId, outputFields,
178 if ( parameters.value( QStringLiteral(
"OUTPUT" ) ).isValid() && !mJoinedFeatures )
181 mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral(
"DISCARD_NONMATCHING" ), context );
183 QString nonMatchingSinkId;
184 mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(),
186 if ( parameters.value( QStringLiteral(
"NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
189 switch ( mJoinMethod )
194 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
197 processAlgorithmByIteratingOverInputSource( context, feedback );
207 processAlgorithmByIteratingOverJoinedSource( context, feedback );
212 case JoinToLargestOverlap:
213 processAlgorithmByIteratingOverInputSource( context, feedback );
218 if ( mJoinedFeatures )
220 mJoinedFeatures->finalize();
221 outputs.insert( QStringLiteral(
"OUTPUT" ), joinedSinkId );
223 if ( mUnjoinedFeatures )
225 mUnjoinedFeatures->finalize();
226 outputs.insert( QStringLiteral(
"NON_MATCHING" ), nonMatchingSinkId );
230 mJoinedFeatures.reset();
231 mUnjoinedFeatures.reset();
233 outputs.insert( QStringLiteral(
"JOINED_COUNT" ),
static_cast< long long >( mJoinedCount ) );
237bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
241 for (
const int predicate : predicates )
254 if ( comparingToJoinedFeature )
263 if ( engine->
within( geom ) )
292 if ( comparingToJoinedFeature )
294 if ( engine->
within( geom ) )
324 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
330 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
337 processFeatureFromJoinSource( f, feedback );
343 if ( !mDiscardNonMatching || mUnjoinedFeatures )
346 unjoinedIds.subtract( mAddedIds );
353 emptyAttributes.reserve( mJoinedFieldIndices.count() );
354 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
355 emptyAttributes << QVariant();
362 if ( mJoinedFeatures && !mDiscardNonMatching )
365 attributes.append( emptyAttributes );
367 outputFeature.setAttributes( attributes );
369 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
372 if ( mUnjoinedFeatures )
375 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
384 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
389 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
391 while ( it .nextFeature( f ) )
396 processFeatureFromInputSource( f, context, feedback );
403void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
410 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool
435 std::unique_ptr< QgsGeometryEngine > engine;
447 switch ( mJoinMethod )
450 if ( mAddedIds.contains( baseFeature.
id() ) )
460 case JoinToLargestOverlap:
461 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
467 engine->prepareGeometry();
468 for (
int ix : std::as_const( mJoinedFieldIndices ) )
470 joinAttributes.append( joinFeature.
attribute( ix ) );
473 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
475 if ( mJoinedFeatures )
478 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
480 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
485 mAddedIds.insert( baseFeature.
id() );
497 if ( mJoinedFeatures && !mDiscardNonMatching )
500 emptyAttributes.reserve( mJoinedFieldIndices.count() );
501 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
502 emptyAttributes << QVariant();
505 attributes.append( emptyAttributes );
507 outputFeature.setAttributes( attributes );
509 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
512 if ( mUnjoinedFeatures )
515 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
522 std::unique_ptr< QgsGeometryEngine > engine;
529 double largestOverlap = std::numeric_limits< double >::lowest();
540 engine->prepareGeometry();
543 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
545 switch ( mJoinMethod )
549 if ( mJoinedFeatures )
552 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
553 for (
int ix : std::as_const( mJoinedFieldIndices ) )
555 joinAttributes.append( joinFeature.
attribute( ix ) );
559 outputFeature.setAttributes( joinAttributes );
561 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
565 case JoinToLargestOverlap:
568 std::unique_ptr< QgsAbstractGeometry > intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
573 overlap = intersection->length();
577 overlap = intersection->area();
586 if ( overlap > largestOverlap )
588 largestOverlap = overlap;
589 bestMatch = joinFeature;
597 if ( mJoinMethod == JoinToFirst )
602 switch ( mJoinMethod )
608 case JoinToLargestOverlap:
613 if ( mJoinedFeatures )
616 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
617 for (
int ix : std::as_const( mJoinedFieldIndices ) )
619 joinAttributes.append( bestMatch.
attribute( ix ) );
623 outputFeature.setAttributes( joinAttributes );
625 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
639 if ( mJoinedFeatures && !mDiscardNonMatching )
642 emptyAttributes.reserve( mJoinedFieldIndices.count() );
643 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
644 emptyAttributes << QVariant();
647 attributes.append( emptyAttributes );
649 outputFeature.setAttributes( attributes );
651 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
654 if ( mUnjoinedFeatures )
657 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.
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...
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