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() );
49 QStringList joinMethods;
50 joinMethods << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
51 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
52 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
53 addParameter(
new QgsProcessingParameterEnum( u
"METHOD"_s, QObject::tr(
"Join type" ), joinMethods,
false,
static_cast<int>( OneToMany ) ) );
54 addParameter(
new QgsProcessingParameterBoolean( u
"DISCARD_NONMATCHING"_s, QObject::tr(
"Discard records which could not be joined" ),
false ) );
58 addOutput(
new QgsProcessingOutputNumber( u
"JOINED_COUNT"_s, QObject::tr(
"Number of joined features from input table" ) ) );
61QString QgsJoinByLocationAlgorithm::name()
const
63 return u
"joinattributesbylocation"_s;
66QString QgsJoinByLocationAlgorithm::displayName()
const
68 return QObject::tr(
"Join attributes by location" );
71QStringList QgsJoinByLocationAlgorithm::tags()
const
73 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
76QString QgsJoinByLocationAlgorithm::group()
const
78 return QObject::tr(
"Vector general" );
81QString QgsJoinByLocationAlgorithm::groupId()
const
83 return u
"vectorgeneral"_s;
86QString QgsJoinByLocationAlgorithm::shortHelpString()
const
88 return QObject::tr(
"This algorithm takes an input vector layer and creates a new vector layer "
89 "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
90 "The additional attributes and their values are taken from a second vector layer. "
91 "A spatial criteria is applied to select the values from the second layer that are added "
92 "to each feature from the first layer in the resulting one." );
95QString QgsJoinByLocationAlgorithm::shortDescription()
const
97 return QObject::tr(
"Joins attributes from one vector layer to another by location." );
105QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
107 return new QgsJoinByLocationAlgorithm();
110QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
112 return { QObject::tr(
"intersect" ), QObject::tr(
"contain" ), QObject::tr(
"equal" ), QObject::tr(
"touch" ), QObject::tr(
"overlap" ), QObject::tr(
"are within" ), QObject::tr(
"cross" ) };
117 mBaseSource.reset( parameterAsSource( parameters, u
"INPUT"_s, context ) );
121 mJoinSource.reset( parameterAsSource( parameters, u
"JOIN"_s, context ) );
125 mJoinMethod =
static_cast<JoinMethod
>( parameterAsEnum( parameters, u
"METHOD"_s, context ) );
127 const QStringList joinedFieldNames = parameterAsStrings( parameters, u
"JOIN_FIELDS"_s, context );
129 mPredicates = parameterAsEnums( parameters, u
"PREDICATE"_s, context );
130 sortPredicates( mPredicates );
132 QString prefix = parameterAsString( parameters, u
"PREFIX"_s, context );
135 if ( joinedFieldNames.empty() )
137 joinFields = mJoinSource->fields();
142 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
143 for (
const QString &field : joinedFieldNames )
145 int index = mJoinSource->fields().lookupField( field );
148 mJoinedFieldIndices << index;
149 joinFields.
append( mJoinSource->fields().at( index ) );
154 if ( !prefix.isEmpty() )
156 for (
int i = 0; i < joinFields.
count(); ++i )
158 joinFields.
rename( i, prefix + joinFields[i].name() );
164 QString joinedSinkId;
165 mJoinedFeatures.reset( parameterAsSink( parameters, u
"OUTPUT"_s, context, joinedSinkId, outputFields, mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
167 if ( parameters.value( u
"OUTPUT"_s ).isValid() && !mJoinedFeatures )
170 mDiscardNonMatching = parameterAsBoolean( parameters, u
"DISCARD_NONMATCHING"_s, context );
172 QString nonMatchingSinkId;
173 mUnjoinedFeatures.reset( parameterAsSink( parameters, u
"NON_MATCHING"_s, context, nonMatchingSinkId, mBaseSource->fields(), mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
174 if ( parameters.value( u
"NON_MATCHING"_s ).isValid() && !mUnjoinedFeatures )
177 switch ( mJoinMethod )
182 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
185 processAlgorithmByIteratingOverInputSource( context, feedback );
195 processAlgorithmByIteratingOverJoinedSource( context, feedback );
200 case JoinToLargestOverlap:
201 processAlgorithmByIteratingOverInputSource( context, feedback );
206 if ( mJoinedFeatures )
208 mJoinedFeatures->finalize();
209 outputs.insert( u
"OUTPUT"_s, joinedSinkId );
211 if ( mUnjoinedFeatures )
213 mUnjoinedFeatures->finalize();
214 outputs.insert( u
"NON_MATCHING"_s, nonMatchingSinkId );
218 mJoinedFeatures.reset();
219 mUnjoinedFeatures.reset();
221 outputs.insert( u
"JOINED_COUNT"_s,
static_cast<long long>( mJoinedCount ) );
225bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
229 for (
const int predicate : predicates )
242 if ( comparingToJoinedFeature )
251 if ( engine->
within( geom ) )
280 if ( comparingToJoinedFeature )
282 if ( engine->
within( geom ) )
312 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
318 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
325 processFeatureFromJoinSource( f, feedback );
331 if ( !mDiscardNonMatching || mUnjoinedFeatures )
334 unjoinedIds.subtract( mAddedIds );
341 emptyAttributes.reserve( mJoinedFieldIndices.count() );
342 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
343 emptyAttributes << QVariant();
350 if ( mJoinedFeatures && !mDiscardNonMatching )
353 attributes.append( emptyAttributes );
355 outputFeature.setAttributes( attributes );
360 if ( mUnjoinedFeatures )
363 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
372 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
377 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
384 processFeatureFromInputSource( f, context, feedback );
391void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
398 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool {
422 std::unique_ptr<QgsGeometryEngine> engine;
434 switch ( mJoinMethod )
437 if ( mAddedIds.contains( baseFeature.
id() ) )
447 case JoinToLargestOverlap:
448 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
454 engine->prepareGeometry();
455 for (
int ix : std::as_const( mJoinedFieldIndices ) )
457 joinAttributes.append( joinFeature.
attribute( ix ) );
460 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
462 if ( mJoinedFeatures )
465 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
472 mAddedIds.insert( baseFeature.
id() );
484 if ( mJoinedFeatures && !mDiscardNonMatching )
487 emptyAttributes.reserve( mJoinedFieldIndices.count() );
488 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
489 emptyAttributes << QVariant();
492 attributes.append( emptyAttributes );
494 outputFeature.setAttributes( attributes );
499 if ( mUnjoinedFeatures )
502 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
509 std::unique_ptr<QgsGeometryEngine> engine;
516 double largestOverlap = std::numeric_limits<double>::lowest();
527 engine->prepareGeometry();
530 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
532 switch ( mJoinMethod )
536 if ( mJoinedFeatures )
539 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
540 for (
int ix : std::as_const( mJoinedFieldIndices ) )
542 joinAttributes.append( joinFeature.
attribute( ix ) );
546 outputFeature.setAttributes( joinAttributes );
552 case JoinToLargestOverlap:
555 std::unique_ptr<QgsAbstractGeometry> intersection( engine->intersection( joinFeature.
geometry().
constGet() ) );
560 overlap = intersection->length();
564 overlap = intersection->area();
573 if ( overlap > largestOverlap )
575 largestOverlap = overlap;
576 bestMatch = joinFeature;
584 if ( mJoinMethod == JoinToFirst )
589 switch ( mJoinMethod )
595 case JoinToLargestOverlap:
600 if ( mJoinedFeatures )
603 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
604 for (
int ix : std::as_const( mJoinedFieldIndices ) )
606 joinAttributes.append( bestMatch.
attribute( ix ) );
610 outputFeature.setAttributes( joinAttributes );
626 if ( mJoinedFeatures && !mDiscardNonMatching )
629 emptyAttributes.reserve( mJoinedFieldIndices.count() );
630 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
631 emptyAttributes << QVariant();
634 attributes.append( emptyAttributes );
636 outputFeature.setAttributes( attributes );
641 if ( mUnjoinedFeatures )
644 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 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