29using namespace Qt::StringLiterals;
32#define EXCLUDE_CPPCHECK
33#ifdef EXCLUDE_CPPCHECK
38void QgsJoinByLocationAlgorithm::initAlgorithm(
const QVariantMap & )
42 auto predicateParam = std::make_unique<QgsProcessingParameterEnum>( u
"PREDICATE"_s, QObject::tr(
"Features they (geometric predicate)" ), translatedPredicates(),
true, 0 );
43 QVariantMap predicateMetadata;
44 QVariantMap widgetMetadata;
45 widgetMetadata.insert( u
"useCheckBoxes"_s,
true );
46 widgetMetadata.insert( u
"columns"_s, 2 );
47 predicateMetadata.insert( u
"widget_wrapper"_s, widgetMetadata );
48 predicateParam->setMetadata( predicateMetadata );
49 addParameter( predicateParam.release() );
55 QStringList joinMethods;
57 << QObject::tr(
"Create separate feature for each matching feature (one-to-many)" )
58 << QObject::tr(
"Take attributes of the first matching feature only (one-to-one)" )
59 << QObject::tr(
"Take attributes of the feature with largest overlap only (one-to-one)" );
60 addParameter(
new QgsProcessingParameterEnum( u
"METHOD"_s, QObject::tr(
"Join type" ), joinMethods,
false,
static_cast<int>( OneToMany ) ) );
61 addParameter(
new QgsProcessingParameterBoolean( u
"DISCARD_NONMATCHING"_s, QObject::tr(
"Discard records which could not be joined" ),
false ) );
65 addOutput(
new QgsProcessingOutputNumber( u
"JOINED_COUNT"_s, QObject::tr(
"Number of joined features from input table" ) ) );
68QString QgsJoinByLocationAlgorithm::name()
const
70 return u
"joinattributesbylocation"_s;
73QString QgsJoinByLocationAlgorithm::displayName()
const
75 return QObject::tr(
"Join attributes by location" );
78QStringList QgsJoinByLocationAlgorithm::tags()
const
80 return QObject::tr(
"join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial" ).split(
',' );
83QString QgsJoinByLocationAlgorithm::group()
const
85 return QObject::tr(
"Vector general" );
88QString QgsJoinByLocationAlgorithm::groupId()
const
90 return u
"vectorgeneral"_s;
93QString QgsJoinByLocationAlgorithm::shortHelpString()
const
96 "This algorithm takes an input vector layer and creates a new vector layer "
97 "that is an extended version of the input one, with additional attributes in its attribute table.\n\n"
98 "The additional attributes and their values are taken from a second vector layer. "
99 "A spatial criteria is applied to select the values from the second layer that are added "
100 "to each feature from the first layer in the resulting one."
104QString QgsJoinByLocationAlgorithm::shortDescription()
const
106 return QObject::tr(
"Joins attributes from one vector layer to another by location." );
114QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance()
const
116 return new QgsJoinByLocationAlgorithm();
119QStringList QgsJoinByLocationAlgorithm::translatedPredicates()
121 return { QObject::tr(
"intersect" ), QObject::tr(
"contain" ), QObject::tr(
"equal" ), QObject::tr(
"touch" ), QObject::tr(
"overlap" ), QObject::tr(
"are within" ), QObject::tr(
"cross" ) };
126 QGS_MARK_ALGORITHM_SOURCE
128 mBaseSource.reset( parameterAsSource( parameters, u
"INPUT"_s, context ) );
132 mJoinSource.reset( parameterAsSource( parameters, u
"JOIN"_s, context ) );
136 mJoinMethod =
static_cast<JoinMethod
>( parameterAsEnum( parameters, u
"METHOD"_s, context ) );
138 const QStringList joinedFieldNames = parameterAsStrings( parameters, u
"JOIN_FIELDS"_s, context );
140 mPredicates = parameterAsEnums( parameters, u
"PREDICATE"_s, context );
141 sortPredicates( mPredicates );
143 QString prefix = parameterAsString( parameters, u
"PREFIX"_s, context );
146 if ( joinedFieldNames.empty() )
148 joinFields = mJoinSource->fields();
153 mJoinedFieldIndices.reserve( joinedFieldNames.count() );
154 for (
const QString &field : joinedFieldNames )
156 int index = mJoinSource->fields().lookupField( field );
159 mJoinedFieldIndices << index;
160 joinFields.
append( mJoinSource->fields().at( index ) );
165 if ( !prefix.isEmpty() )
167 for (
int i = 0; i < joinFields.
count(); ++i )
169 joinFields.
rename( i, prefix + joinFields[i].name() );
175 QString joinedSinkId;
176 mJoinedFeatures.reset( parameterAsSink( parameters, u
"OUTPUT"_s, context, joinedSinkId, outputFields, mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey ) );
178 if ( parameters.value( u
"OUTPUT"_s ).isValid() && !mJoinedFeatures )
181 mDiscardNonMatching = parameterAsBoolean( parameters, u
"DISCARD_NONMATCHING"_s, context );
183 QString nonMatchingSinkId;
184 mUnjoinedFeatures.reset(
185 parameterAsSink( parameters, u
"NON_MATCHING"_s, context, nonMatchingSinkId, mBaseSource->fields(), mBaseSource->wkbType(), mBaseSource->sourceCrs(),
QgsFeatureSink::RegeneratePrimaryKey )
187 if ( parameters.value( u
"NON_MATCHING"_s ).isValid() && !mUnjoinedFeatures )
190 switch ( mJoinMethod )
195 if ( mBaseSource->featureCount() > 0 && mJoinSource->featureCount() > 0 && mBaseSource->featureCount() < mJoinSource->featureCount() )
198 processAlgorithmByIteratingOverInputSource( context, feedback );
208 processAlgorithmByIteratingOverJoinedSource( context, feedback );
213 case JoinToLargestOverlap:
214 processAlgorithmByIteratingOverInputSource( context, feedback );
219 if ( mJoinedFeatures )
221 mJoinedFeatures->finalize();
223 outputs.insert( u
"OUTPUT"_s, joinedSinkId );
225 if ( mUnjoinedFeatures )
227 mUnjoinedFeatures->finalize();
229 outputs.insert( u
"NON_MATCHING"_s, nonMatchingSinkId );
233 mJoinedFeatures.reset();
234 mUnjoinedFeatures.reset();
236 outputs.insert( u
"JOINED_COUNT"_s,
static_cast<long long>( mJoinedCount ) );
240bool QgsJoinByLocationAlgorithm::featureFilter(
const QgsFeature &feature,
QgsGeometryEngine *engine,
bool comparingToJoinedFeature,
const QList<int> &predicates )
244 for (
const int predicate : predicates )
257 if ( comparingToJoinedFeature )
266 if ( engine->
within( geom ) )
295 if ( comparingToJoinedFeature )
297 if ( engine->
within( geom ) )
327 feedback->
pushWarning( QObject::tr(
"No spatial index exists for input layer, performance will be severely degraded" ) );
333 const double step = mJoinSource->featureCount() > 0 ? 100.0 / mJoinSource->featureCount() : 1;
340 processFeatureFromJoinSource( f, feedback );
346 if ( !mDiscardNonMatching || mUnjoinedFeatures )
349 unjoinedIds.subtract( mAddedIds );
356 emptyAttributes.reserve( mJoinedFieldIndices.count() );
357 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
358 emptyAttributes << QVariant();
365 if ( mJoinedFeatures && !mDiscardNonMatching )
368 attributes.append( emptyAttributes );
370 outputFeature.setAttributes( attributes );
375 if ( mUnjoinedFeatures )
378 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
387 feedback->
pushWarning( QObject::tr(
"No spatial index exists for join layer, performance will be severely degraded" ) );
392 const double step = mBaseSource->featureCount() > 0 ? 100.0 / mBaseSource->featureCount() : 1;
399 processFeatureFromInputSource( f, context, feedback );
406void QgsJoinByLocationAlgorithm::sortPredicates( QList<int> &predicates )
413 std::sort( predicates.begin(), predicates.end(), [](
int a,
int b ) ->
bool {
437 std::unique_ptr<QgsGeometryEngine> engine;
449 switch ( mJoinMethod )
452 if ( mAddedIds.contains( baseFeature.
id() ) )
462 case JoinToLargestOverlap:
463 Q_ASSERT_X(
false,
"QgsJoinByLocationAlgorithm::processFeatureFromJoinSource",
"processFeatureFromJoinSource should not be used with join to largest overlap method" );
469 engine->prepareGeometry();
470 for (
int ix : std::as_const( mJoinedFieldIndices ) )
472 joinAttributes.append( joinFeature.
attribute( ix ) );
475 if ( featureFilter( baseFeature, engine.get(),
false, mPredicates ) )
477 if ( mJoinedFeatures )
480 outputFeature.setAttributes( baseFeature.
attributes() + joinAttributes );
487 mAddedIds.insert( baseFeature.
id() );
499 if ( mJoinedFeatures && !mDiscardNonMatching )
502 emptyAttributes.reserve( mJoinedFieldIndices.count() );
503 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
504 emptyAttributes << QVariant();
507 attributes.append( emptyAttributes );
509 outputFeature.setAttributes( attributes );
514 if ( mUnjoinedFeatures )
517 throw QgsProcessingException( writeFeatureError( mUnjoinedFeatures.get(), QVariantMap(), u
"NON_MATCHING"_s ) );
524 std::unique_ptr<QgsGeometryEngine> engine;
531 double largestOverlap = std::numeric_limits<double>::lowest();
542 engine->prepareGeometry();
545 if ( featureFilter( joinFeature, engine.get(),
true, mPredicates ) )
547 switch ( mJoinMethod )
551 if ( mJoinedFeatures )
554 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
555 for (
int ix : std::as_const( mJoinedFieldIndices ) )
557 joinAttributes.append( joinFeature.
attribute( ix ) );
561 outputFeature.setAttributes( joinAttributes );
567 case JoinToLargestOverlap:
575 overlap = intersection->length();
579 overlap = intersection->area();
588 if ( overlap > largestOverlap )
590 largestOverlap = overlap;
591 bestMatch = joinFeature;
599 if ( mJoinMethod == JoinToFirst )
604 switch ( mJoinMethod )
610 case JoinToLargestOverlap:
615 if ( mJoinedFeatures )
618 joinAttributes.reserve( joinAttributes.size() + mJoinedFieldIndices.size() );
619 for (
int ix : std::as_const( mJoinedFieldIndices ) )
621 joinAttributes.append( bestMatch.
attribute( ix ) );
625 outputFeature.setAttributes( joinAttributes );
641 if ( mJoinedFeatures && !mDiscardNonMatching )
644 emptyAttributes.reserve( mJoinedFieldIndices.count() );
645 for (
int i = 0; i < mJoinedFieldIndices.count(); ++i )
646 emptyAttributes << QVariant();
649 attributes.append( emptyAttributes );
651 outputFeature.setAttributes( attributes );
656 if ( mUnjoinedFeatures )
659 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