29void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
33 std::unique_ptr<QgsProcessingParameterEnum> predicateParam = std::make_unique<QgsProcessingParameterEnum>( QStringLiteral(
"PREDICATE" ), QObject::tr(
"Features they (geometric predicate)" ), translatedPredicates(),
true, 0 );
34 QVariantMap predicateMetadata;
35 QVariantMap widgetMetadata;
36 widgetMetadata.insert( QStringLiteral(
"useCheckBoxes" ),
true );
37 widgetMetadata.insert( QStringLiteral(
"columns" ), 2 );
38 predicateMetadata.insert( QStringLiteral(
"widget_wrapper" ), widgetMetadata );
39 predicateParam->setMetadata( predicateMetadata );
40 addParameter( predicateParam.release() );
44 QStringList joinMethods;
45 joinMethods << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
46 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
47 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
48 addParameter(
new QgsProcessingParameterEnum( QStringLiteral(
"METHOD" ), QObject::tr(
"Join type" ), joinMethods,
false,
static_cast<int>( OneToMany ) ) );
49 addParameter(
new QgsProcessingParameterBoolean( QStringLiteral(
"DISCARD_NONMATCHING" ), QObject::tr(
"Discard records which could not be joined" ),
false ) );
50 addParameter(
new QgsProcessingParameterString( QStringLiteral(
"PREFIX" ), QObject::tr(
"Joined field prefix" ), QVariant(),
false,
true ) );
53 addOutput(
new QgsProcessingOutputNumber( QStringLiteral(
"JOINED_COUNT" ), QObject::tr(
"Number of joined features from input table" ) ) );
56QString QgsJoinByLocationAlgorithm::name()
const
58 return QStringLiteral(
"joinattributesbylocation" );
61QString QgsJoinByLocationAlgorithm::displayName()
const
63 return QObject::tr(
"Join attributes by location" );
66QStringList QgsJoinByLocationAlgorithm::tags()
const
68 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
71QString QgsJoinByLocationAlgorithm::group()
const
73 return QObject::tr(
"Vector general" );
76QString QgsJoinByLocationAlgorithm::groupId()
const
78 return QStringLiteral(
"vectorgeneral" );
81QString QgsJoinByLocationAlgorithm::shortHelpString()
const
83 return QObject::tr(
"This algorithm takes an input vector layer and creates a new vector layer "
84 "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
85 "The additional attributes and their values are taken from a second vector layer. "
86 "A spatial criteria is applied to select the values from the second layer that are added "
87 "to each feature from the first layer in the resulting one." );
90QString QgsJoinByLocationAlgorithm::shortDescription()
const
92 return QObject::tr(
"Join attributes from one vector layer to another by location." );
100QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
102 return new QgsJoinByLocationAlgorithm();
105QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
107 return { QObject::tr(
"intersect" ), QObject::tr(
"contain" ), QObject::tr(
"equal" ), QObject::tr(
"touch" ), QObject::tr(
"overlap" ), QObject::tr(
"are within" ), QObject::tr(
"cross" ) };
112 mBaseSource.reset( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
116 mJoinSource.reset( parameterAsSource( parameters, QStringLiteral(
"JOIN" ), context ) );
120 mJoinMethod =
static_cast<JoinMethod
>( parameterAsEnum( parameters, QStringLiteral(
"METHOD" ), context ) );
122 const QStringList joinedFieldNames = parameterAsStrings( parameters, QStringLiteral(
"JOIN_FIELDS" ), context );
124 mPredicates = parameterAsEnums( parameters, QStringLiteral(
"PREDICATE" ), context );
125 sortPredicates( mPredicates );
127 QString prefix = parameterAsString( parameters, QStringLiteral(
"PREFIX" ), context );
130 if ( joinedFieldNames.empty() )
132 joinFields = mJoinSource->fields();
137 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
138 for (
const QString &field : joinedFieldNames )
140 int index = mJoinSource->fields().lookupField( field );
143 mJoinedFieldIndices << index;
144 joinFields.
append( mJoinSource->fields().at( index ) );
149 if ( !prefix.isEmpty() )
151 for (
int i = 0; i < joinFields.
count(); ++i )
153 joinFields.
rename( i, prefix + joinFields[i].name() );
159 QString joinedSinkId;
160 mJoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, joinedSinkId, outputFields, mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
162 if ( parameters.value( QStringLiteral(
"OUTPUT" ) ).isValid() && !mJoinedFeatures )
165 mDiscardNonMatching = parameterAsBoolean( parameters, QStringLiteral(
"DISCARD_NONMATCHING" ), context );
167 QString nonMatchingSinkId;
168 mUnjoinedFeatures.reset( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING" ), context, nonMatchingSinkId, mBaseSource->fields(), mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
169 if ( parameters.value( QStringLiteral(
"NON_MATCHING" ) ).isValid() && !mUnjoinedFeatures )
172 switch ( mJoinMethod )
177 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
180 processAlgorithmByIteratingOverInputSource( context, feedback );
190 processAlgorithmByIteratingOverJoinedSource( context, feedback );
195 case JoinToLargestOverlap:
196 processAlgorithmByIteratingOverInputSource( context, feedback );
201 if ( mJoinedFeatures )
203 mJoinedFeatures->finalize();
204 outputs.insert( QStringLiteral(
"OUTPUT" ), joinedSinkId );
206 if ( mUnjoinedFeatures )
208 mUnjoinedFeatures->finalize();
209 outputs.insert( QStringLiteral(
"NON_MATCHING" ), nonMatchingSinkId );
213 mJoinedFeatures.reset();
214 mUnjoinedFeatures.reset();
216 outputs.insert( QStringLiteral(
"JOINED_COUNT" ),
static_cast<long long>( mJoinedCount ) );
220bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
224 for (
const int predicate : predicates )
237 if ( comparingToJoinedFeature )
246 if ( engine->
within( geom ) )
275 if ( comparingToJoinedFeature )
277 if ( engine->
within( geom ) )
307 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
313 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
320 processFeatureFromJoinSource( f, feedback );
326 if ( !mDiscardNonMatching || mUnjoinedFeatures )
329 unjoinedIds.subtract( mAddedIds );
336 emptyAttributes.reserve( mJoinedFieldIndices.count() );
337 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
338 emptyAttributes << QVariant();
345 if ( mJoinedFeatures && !mDiscardNonMatching )
348 attributes.append( emptyAttributes );
350 outputFeature.setAttributes( attributes );
352 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
355 if ( mUnjoinedFeatures )
358 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
367 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
372 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
379 processFeatureFromInputSource( f, context, feedback );
386void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
393 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool {
417 std::unique_ptr<QgsGeometryEngine> engine;
429 switch ( mJoinMethod )
432 if ( mAddedIds.contains( baseFeature.
id() ) )
442 case JoinToLargestOverlap:
443 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
449 engine->prepareGeometry();
450 for (
int ix : std::as_const( mJoinedFieldIndices ) )
452 joinAttributes.append( joinFeature.
attribute( ix ) );
455 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
457 if ( mJoinedFeatures )
460 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
462 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
467 mAddedIds.insert( baseFeature.
id() );
479 if ( mJoinedFeatures && !mDiscardNonMatching )
482 emptyAttributes.reserve( mJoinedFieldIndices.count() );
483 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
484 emptyAttributes << QVariant();
487 attributes.append( emptyAttributes );
489 outputFeature.setAttributes( attributes );
491 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
494 if ( mUnjoinedFeatures )
497 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), QStringLiteral(
"NON_MATCHING" ) ) );
504 std::unique_ptr<QgsGeometryEngine> engine;
511 double largestOverlap = std::numeric_limits<double>::lowest();
522 engine->prepareGeometry();
525 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
527 switch ( mJoinMethod )
531 if ( mJoinedFeatures )
534 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
535 for (
int ix : std::as_const( mJoinedFieldIndices ) )
537 joinAttributes.append( joinFeature.
attribute( ix ) );
541 outputFeature.setAttributes( joinAttributes );
543 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
547 case JoinToLargestOverlap:
550 std::unique_ptr<QgsAbstractGeometry> intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
555 overlap = intersection->length();
559 overlap = intersection->area();
568 if ( overlap > largestOverlap )
570 largestOverlap = overlap;
571 bestMatch = joinFeature;
579 if ( mJoinMethod == JoinToFirst )
584 switch ( mJoinMethod )
590 case JoinToLargestOverlap:
595 if ( mJoinedFeatures )
598 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
599 for (
int ix : std::as_const( mJoinedFieldIndices ) )
601 joinAttributes.append( bestMatch.
attribute( ix ) );
605 outputFeature.setAttributes( joinAttributes );
607 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
621 if ( mJoinedFeatures && !mDiscardNonMatching )
624 emptyAttributes.reserve( mJoinedFieldIndices.count() );
625 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
626 emptyAttributes << QVariant();
629 attributes.append( emptyAttributes );
631 outputFeature.setAttributes( attributes );
633 throw QgsProcessingException( writeFeatureError( mJoinedFeatures.get(), QVariantMap(), QStringLiteral(
"OUTPUT" ) ) );
636 if ( mUnjoinedFeatures )
639 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