29using namespace Qt::StringLiterals;
34void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
38 auto predicateParam = std::make_unique<QgsProcessingParameterEnum>( u
"PREDICATE"_s, QObject::tr(
"Features they (geometric predicate)" ), translatedPredicates(),
true, 0 );
39 QVariantMap predicateMetadata;
40 QVariantMap widgetMetadata;
41 widgetMetadata.insert( u
"useCheckBoxes"_s,
true );
42 widgetMetadata.insert( u
"columns"_s, 2 );
43 predicateMetadata.insert( u
"widget_wrapper"_s, widgetMetadata );
44 predicateParam->setMetadata( predicateMetadata );
45 addParameter( predicateParam.release() );
51 QStringList joinMethods;
53 << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
54 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
55 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
56 addParameter(
new QgsProcessingParameterEnum( u
"METHOD"_s, QObject::tr(
"Join type" ), joinMethods,
false,
static_cast<int>( OneToMany ) ) );
57 addParameter(
new QgsProcessingParameterBoolean( u
"DISCARD_NONMATCHING"_s, QObject::tr(
"Discard records which could not be joined" ),
false ) );
61 addOutput(
new QgsProcessingOutputNumber( u
"JOINED_COUNT"_s, QObject::tr(
"Number of joined features from input table" ) ) );
64QString QgsJoinByLocationAlgorithm::name()
const
66 return u
"joinattributesbylocation"_s;
69QString QgsJoinByLocationAlgorithm::displayName()
const
71 return QObject::tr(
"Join attributes by location" );
74QStringList QgsJoinByLocationAlgorithm::tags()
const
76 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
79QString QgsJoinByLocationAlgorithm::group()
const
81 return QObject::tr(
"Vector general" );
84QString QgsJoinByLocationAlgorithm::groupId()
const
86 return u
"vectorgeneral"_s;
89QString QgsJoinByLocationAlgorithm::shortHelpString()
const
92 "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."
100QString QgsJoinByLocationAlgorithm::shortDescription()
const
102 return QObject::tr(
"Joins attributes from one vector layer to another by location." );
110QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
112 return new QgsJoinByLocationAlgorithm();
115QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
117 return { QObject::tr(
"intersect" ), QObject::tr(
"contain" ), QObject::tr(
"equal" ), QObject::tr(
"touch" ), QObject::tr(
"overlap" ), QObject::tr(
"are within" ), QObject::tr(
"cross" ) };
122 mBaseSource.reset( parameterAsSource( parameters, u
"INPUT"_s, context ) );
126 mJoinSource.reset( parameterAsSource( parameters, u
"JOIN"_s, context ) );
130 mJoinMethod =
static_cast<JoinMethod
>( parameterAsEnum( parameters, u
"METHOD"_s, context ) );
132 const QStringList joinedFieldNames = parameterAsStrings( parameters, u
"JOIN_FIELDS"_s, context );
134 mPredicates = parameterAsEnums( parameters, u
"PREDICATE"_s, context );
135 sortPredicates( mPredicates );
137 QString prefix = parameterAsString( parameters, u
"PREFIX"_s, context );
140 if ( joinedFieldNames.empty() )
142 joinFields = mJoinSource->fields();
147 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
148 for (
const QString &field : joinedFieldNames )
150 int index = mJoinSource->fields().lookupField( field );
153 mJoinedFieldIndices << index;
154 joinFields.
append( mJoinSource->fields().at( index ) );
159 if ( !prefix.isEmpty() )
161 for (
int i = 0; i < joinFields.
count(); ++i )
163 joinFields.
rename( i, prefix + joinFields[i].name() );
169 QString joinedSinkId;
170 mJoinedFeatures.reset( parameterAsSink( parameters, u
"OUTPUT"_s, context, joinedSinkId, outputFields, mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
172 if ( parameters.value( u
"OUTPUT"_s ).isValid() && !mJoinedFeatures )
175 mDiscardNonMatching = parameterAsBoolean( parameters, u
"DISCARD_NONMATCHING"_s, context );
177 QString nonMatchingSinkId;
178 mUnjoinedFeatures.reset(
179 parameterAsSink( parameters, u
"NON_MATCHING"_s, context, nonMatchingSinkId, mBaseSource->fields(), mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey )
181 if ( parameters.value( u
"NON_MATCHING"_s ).isValid() && !mUnjoinedFeatures )
184 switch ( mJoinMethod )
189 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
192 processAlgorithmByIteratingOverInputSource( context, feedback );
202 processAlgorithmByIteratingOverJoinedSource( context, feedback );
207 case JoinToLargestOverlap:
208 processAlgorithmByIteratingOverInputSource( context, feedback );
213 if ( mJoinedFeatures )
215 mJoinedFeatures->finalize();
217 outputs.insert( u
"OUTPUT"_s, joinedSinkId );
219 if ( mUnjoinedFeatures )
221 mUnjoinedFeatures->finalize();
223 outputs.insert( u
"NON_MATCHING"_s, nonMatchingSinkId );
227 mJoinedFeatures.reset();
228 mUnjoinedFeatures.reset();
230 outputs.insert( u
"JOINED_COUNT"_s,
static_cast<long long>( mJoinedCount ) );
234bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
238 for (
const int predicate : predicates )
251 if ( comparingToJoinedFeature )
260 if ( engine->
within( geom ) )
289 if ( comparingToJoinedFeature )
291 if ( engine->
within( geom ) )
321 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
327 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
334 processFeatureFromJoinSource( f, feedback );
340 if ( !mDiscardNonMatching || mUnjoinedFeatures )
343 unjoinedIds.subtract( mAddedIds );
350 emptyAttributes.reserve( mJoinedFieldIndices.count() );
351 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
352 emptyAttributes << QVariant();
359 if ( mJoinedFeatures && !mDiscardNonMatching )
362 attributes.append( emptyAttributes );
364 outputFeature.setAttributes( attributes );
369 if ( mUnjoinedFeatures )
372 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
381 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
386 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
393 processFeatureFromInputSource( f, context, feedback );
400void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
407 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool {
431 std::unique_ptr<QgsGeometryEngine> engine;
443 switch ( mJoinMethod )
446 if ( mAddedIds.contains( baseFeature.
id() ) )
456 case JoinToLargestOverlap:
457 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
463 engine->prepareGeometry();
464 for (
int ix : std::as_const( mJoinedFieldIndices ) )
466 joinAttributes.append( joinFeature.
attribute( ix ) );
469 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
471 if ( mJoinedFeatures )
474 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
481 mAddedIds.insert( baseFeature.
id() );
493 if ( mJoinedFeatures && !mDiscardNonMatching )
496 emptyAttributes.reserve( mJoinedFieldIndices.count() );
497 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
498 emptyAttributes << QVariant();
501 attributes.append( emptyAttributes );
503 outputFeature.setAttributes( attributes );
508 if ( mUnjoinedFeatures )
511 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
518 std::unique_ptr<QgsGeometryEngine> engine;
525 double largestOverlap = std::numeric_limits<double>::lowest();
536 engine->prepareGeometry();
539 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
541 switch ( mJoinMethod )
545 if ( mJoinedFeatures )
548 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
549 for (
int ix : std::as_const( mJoinedFieldIndices ) )
551 joinAttributes.append( joinFeature.
attribute( ix ) );
555 outputFeature.setAttributes( joinAttributes );
561 case JoinToLargestOverlap:
569 overlap = intersection->length();
573 overlap = intersection->area();
582 if ( overlap > largestOverlap )
584 largestOverlap = overlap;
585 bestMatch = joinFeature;
593 if ( mJoinMethod == JoinToFirst )
598 switch ( mJoinMethod )
604 case JoinToLargestOverlap:
609 if ( mJoinedFeatures )
612 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
613 for (
int ix : std::as_const( mJoinedFieldIndices ) )
615 joinAttributes.append( bestMatch.
attribute( ix ) );
619 outputFeature.setAttributes( joinAttributes );
635 if ( mJoinedFeatures && !mDiscardNonMatching )
638 emptyAttributes.reserve( mJoinedFieldIndices.count() );
639 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
640 emptyAttributes << QVariant();
643 attributes.append( emptyAttributes );
645 outputFeature.setAttributes( attributes );
650 if ( mUnjoinedFeatures )
653 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
@ 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 overlaps(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom overlaps this.
virtual bool isEqual(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Check if geometries are topologically equivalent.
virtual bool crosses(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom crosses this.
virtual bool within(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom is within this.
virtual bool intersects(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom intersects this.
virtual bool contains(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom contains this.
virtual bool touches(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const =0
Checks if geom touches this.
Encapsulates parameters under which a geometry operation is performed.
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.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
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