25using namespace Qt::StringLiterals;
29QString QgsDistanceMatrixAlgorithm::name()
const
31 return u
"distancematrix"_s;
34QString QgsDistanceMatrixAlgorithm::displayName()
const
36 return QObject::tr(
"Distance matrix" );
39QStringList QgsDistanceMatrixAlgorithm::tags()
const
41 return QObject::tr(
"point,distance,matrix,nearest,closest,summary" ).split(
',' );
44QString QgsDistanceMatrixAlgorithm::group()
const
46 return QObject::tr(
"Vector analysis" );
49QString QgsDistanceMatrixAlgorithm::groupId()
const
51 return u
"vectoranalysis"_s;
54QString QgsDistanceMatrixAlgorithm::shortHelpString()
const
57 "This algorithm creates a table containing a distance matrix, with "
58 "distances between all the points in a points layer.\n\n"
59 "The algorithm supports both Cartesian and ellipsoidal distance calculations.\n\n"
60 "Cartesian calculations are performed using a spatial index and offer "
61 "high performance, but can give inaccurate results when used with CRSs "
62 "with high distance distortion or where shortest distances accounting for "
63 "great circles is required.\n\n"
64 "If ellipsoidal calculations are enabled, the algorithm bypasses the spatial "
65 "index and provides accurate geodetic measurements at a considerable performance cost."
69QString QgsDistanceMatrixAlgorithm::shortDescription()
const
71 return QObject::tr(
"Creates a table containing a matrix of distances between all the points in a points layer." );
79QgsDistanceMatrixAlgorithm *QgsDistanceMatrixAlgorithm::createInstance()
const
81 return new QgsDistanceMatrixAlgorithm();
84void QgsDistanceMatrixAlgorithm::initAlgorithm(
const QVariantMap & )
86 auto inputParam = std::make_unique<QgsProcessingParameterFeatureSource>( u
"INPUT"_s, QObject::tr(
"Input point layer" ), QList<int>() <<
static_cast<int>(
Qgis::ProcessingSourceType::VectorPoint ) );
87 inputParam->setHelp( QObject::tr(
"The layer containing the points from which distances will be calculated." ) );
88 addParameter( inputParam.release() );
89 auto inputFieldParam = std::make_unique<QgsProcessingParameterField>( u
"INPUT_FIELD"_s, QObject::tr(
"Input unique ID field" ), QVariant(), u
"INPUT"_s );
90 inputFieldParam->setHelp( QObject::tr(
"A field of the input layer with unique values to identify each starting point in the output table." ) );
91 addParameter( inputFieldParam.release() );
92 auto targetParam = std::make_unique<QgsProcessingParameterFeatureSource>( u
"TARGET"_s, QObject::tr(
"Target point layer" ), QList<int>() <<
static_cast<int>(
Qgis::ProcessingSourceType::VectorPoint ) );
93 targetParam->setHelp( QObject::tr(
"The layer containing the points to which distances will be measured. If the same as the input layer, distances between points in the same layer are calculated." ) );
94 addParameter( targetParam.release() );
95 auto targetFieldParam = std::make_unique<QgsProcessingParameterField>( u
"TARGET_FIELD"_s, QObject::tr(
"Target unique ID field" ), QVariant(), u
"TARGET"_s );
96 targetFieldParam->setHelp( QObject::tr(
"A field of the target layer with unique values to identify each destination point in the output table." ) );
97 addParameter( targetFieldParam.release() );
98 auto matrixTypeParam = std::make_unique<QgsProcessingParameterEnum>(
100 QObject::tr(
"Output matrix type" ),
101 QStringList() << QObject::tr(
"Linear (N*k x 3) distance matrix" ) << QObject::tr(
"Standard (N x T) distance matrix" ) << QObject::tr(
"Summary distance matrix (mean, std. dev., min, max)" ),
105 matrixTypeParam->setHelp(
107 "The format of the output table. Linear for a list of point pairs and their distance; "
108 "standard for one row per input point with columns for each target and distance; "
109 "summary for statistics (mean, min, max, etc.) for each input point."
112 addParameter( matrixTypeParam.release() );
115 pointsParam->setHelp( QObject::tr(
"Limit the calculation to a specific number of the closest target points. If set to 0, distances to all target points will be calculated." ) );
116 addParameter( pointsParam.release() );
117 auto ellipsoidParam = std::make_unique<QgsProcessingParameterBoolean>( u
"USE_ELLIPSOID"_s, QObject::tr(
"Use ellipsoidal calculations" ),
true );
118 ellipsoidParam->setHelp(
119 QObject::tr(
"If checked, the algorithm will bypass the spatial index and calculate ellipsoidal distances for all point combinations. This will reduce performance but provide accurate results." )
121 addParameter( ellipsoidParam.release() );
127 mSource.reset( parameterAsSource( parameters, u
"INPUT"_s, context ) );
135 throw QgsProcessingException( QObject::tr(
"Input point layer is a MultiPoint layer - convert to single points before using this algorithm." ) );
138 mTarget.reset( parameterAsSource( parameters, u
"TARGET"_s, context ) );
146 throw QgsProcessingException( QObject::tr(
"Target point layer is a MultiPoint layer - convert to single points before using this algorithm." ) );
149 mSourceField = parameterAsString( parameters, u
"INPUT_FIELD"_s, context );
150 mTargetField = parameterAsString( parameters, u
"TARGET_FIELD"_s, context );
152 const int sourceFieldIndex = mSource->fields().lookupField( mSourceField );
153 if ( sourceFieldIndex < 0 )
158 const int targetFieldIndex = mTarget->fields().lookupField( mTargetField );
159 if ( targetFieldIndex < 0 )
164 mMatrixType =
static_cast<MatrixType
>( parameterAsEnum( parameters, u
"MATRIX_TYPE"_s, context ) );
165 mSameLayer = ( parameters.value( u
"INPUT"_s ) == parameters.value( u
"TARGET"_s ) );
167 mKPoints = parameterAsInt( parameters, u
"NEAREST_POINTS"_s, context );
170 mKPoints =
static_cast<int>( mTarget->featureCount() );
173 mUseEllipsoid = parameterAsBool( parameters, u
"USE_ELLIPSOID"_s, context );
180 QGS_MARK_ALGORITHM_SOURCE
184 switch ( mMatrixType )
188 return linearMatrixEllipsoid( parameters, context, feedback );
191 return regularMatrixEllipsoid( parameters, context, feedback );
196 switch ( mMatrixType )
200 return linearMatrixCartesian( parameters, context, feedback );
203 return regularMatrixCartesian( parameters, context, feedback );
213 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
215 const int sourceIndex = mSource->fields().lookupField( mSourceField );
216 const int targetIndex = mTarget->fields().lookupField( mTargetField );
219 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
220 inputIdField.setName( u
"InputID"_s );
221 fields.
append( inputIdField );
223 if ( mMatrixType == Linear )
225 QgsField targetIdField( mTarget->fields().at( targetIndex ) );
226 targetIdField.setName( u
"TargetID"_s );
227 fields.
append( targetIdField );
228 fields.
append(
QgsField( u
"Distance"_s, QMetaType::Type::Double ) );
233 fields.
append(
QgsField( u
"STDDEV"_s, QMetaType::Type::Double ) );
241 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
248 QHash<QgsFeatureId, QVariant> targetIdCache;
249 double step = mTarget->featureCount() > 0 ? 50.0 /
static_cast<double>( mTarget->featureCount() ) : 1;
250 long long current = 0;
259 targetIdCache.insert( f.
id(), f.
attribute( targetIndex ) );
260 feedback->
setProgress(
static_cast<double>( current ) * step );
272 QVector<double> distancesList;
273 distancesList.reserve( nPoints );
276 step = mSource->featureCount() > 0 ? 50.0 /
static_cast<double>( mSource->featureCount() ) : 0;
287 distancesList.clear();
289 const QString sourceId = sourceFeature.
attribute( sourceIndex ).toString();
290 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
292 if ( mMatrixType == Linear )
301 if ( mSameLayer && sourceFeature.
id() == targetId )
306 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
307 distance = da.measureLine( sourcePoint, targetPoint );
328 if ( mSameLayer && sourceFeature.
id() == targetId )
333 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
334 distancesList << da.measureLine( sourcePoint, targetPoint );
339 if ( distancesList.isEmpty() )
346 double sumSquares = 0;
347 double minDistance = std::numeric_limits<double>::max();
348 double maxDistance = std::numeric_limits<double>::lowest();
350 for (
const double d : std::as_const( distancesList ) )
354 minDistance = std::min( minDistance, d );
355 maxDistance = std::max( maxDistance, d );
358 const long long n = distancesList.size();
359 const double mean = sum /
static_cast<double>( n );
360 const double variance = std::max( 0.0, ( sumSquares /
static_cast<double>( n ) ) - ( mean * mean ) );
361 const double stdDev = std::sqrt( variance );
373 feedback->
setProgress( 50.0 +
static_cast<double>( current ) * step );
381 outputs.insert( u
"OUTPUT"_s, dest );
389 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
391 const int sourceIndex = mSource->fields().lookupField( mSourceField );
392 const int targetIndex = mTarget->fields().lookupField( mTargetField );
395 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
396 inputIdField.setName( u
"InputID"_s );
397 fields.
append( inputIdField );
399 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
401 for (
long long i = 0; i < nPoints - 1; i++ )
403 fields.
append(
QgsField( u
"TargetID_%1"_s.arg( i + 1 ), targetIdType ) );
404 fields.
append(
QgsField( u
"Dist_%1"_s.arg( i + 1 ), QMetaType::Type::Double ) );
408 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
415 QHash<QgsFeatureId, QVariant> targetIdCache;
416 double step = mTarget->featureCount() > 0 ? 50.0 /
static_cast<double>( mTarget->featureCount() ) : 1;
417 long long current = 0;
425 targetIdCache.insert( f.
id(), f.
attribute( targetIndex ) );
427 feedback->
setProgress(
static_cast<double>( current ) * step );
440 step = mSource->featureCount() > 0 ? 50.0 /
static_cast<double>( mSource->featureCount() ) : 0;
452 const QString sourceId = sourceFeature.
attribute( sourceIndex ).toString();
453 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
456 attrs.reserve( 1 + nPoints * 2 );
465 if ( mSameLayer && sourceFeature.
id() == targetId )
470 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
471 distance = da.measureLine( sourcePoint, targetPoint );
472 attrs << targetIdCache.value( targetId ) << distance;
484 feedback->
setProgress( 50.0 +
static_cast<double>( current ) * step );
492 outputs.insert( u
"OUTPUT"_s, dest );
499 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
501 const int sourceIndex = mSource->fields().lookupField( mSourceField );
502 const int targetIndex = mTarget->fields().lookupField( mTargetField );
505 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
506 inputIdField.setName( u
"InputID"_s );
507 fields.
append( inputIdField );
509 if ( mMatrixType == Linear )
511 QgsField targetIdField( mTarget->fields().at( targetIndex ) );
512 targetIdField.setName( u
"TargetID"_s );
513 fields.
append( targetIdField );
514 fields.
append(
QgsField( u
"Distance"_s, QMetaType::Type::Double ) );
519 fields.
append(
QgsField( u
"STDDEV"_s, QMetaType::Type::Double ) );
527 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
533 QHash<QgsFeatureId, QVariant> targetIdCache;
534 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
535 targetPointsCache.reserve( mTarget->featureCount() );
537 double step = mTarget->featureCount() > 0 ? 50.0 /
static_cast<double>( mTarget->featureCount() ) : 1;
538 long long current = 0;
547 targetIdCache.insert( f.
id(), f.
attribute( targetIndex ) );
549 feedback->
setProgress(
static_cast<double>( current ) * step );
557 QVector<double> distancesList;
558 distancesList.reserve( nPoints );
561 step = mSource->featureCount() > 0 ? 50.0 /
static_cast<double>( mSource->featureCount() ) : 0;
572 distancesList.clear();
574 const QString sourceId = sourceFeature.
attribute( sourceIndex ).toString();
575 const std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
577 if ( mMatrixType == Linear )
579 for (
const auto &targetPoint : nearestPoints )
586 if ( mSameLayer && sourceFeature.
id() == targetPoint.featureId )
603 for (
const auto &targetPoint : nearestPoints )
610 if ( mSameLayer && sourceFeature.
id() == targetPoint.featureId )
615 distancesList << targetPoint.
distance;
620 if ( distancesList.isEmpty() )
627 double sumSquares = 0;
628 double minDistance = std::numeric_limits<double>::max();
629 double maxDistance = std::numeric_limits<double>::lowest();
631 for (
const double d : std::as_const( distancesList ) )
635 minDistance = std::min( minDistance, d );
636 maxDistance = std::max( maxDistance, d );
639 const long long n = distancesList.size();
640 const double mean = sum /
static_cast<double>( n );
641 const double variance = std::max( 0.0, ( sumSquares /
static_cast<double>( n ) ) - ( mean * mean ) );
642 const double stdDev = std::sqrt( variance );
654 feedback->
setProgress( 50.0 +
static_cast<double>( current ) * step );
662 outputs.insert( u
"OUTPUT"_s, dest );
670 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
672 const int sourceIndex = mSource->fields().lookupField( mSourceField );
673 const int targetIndex = mTarget->fields().lookupField( mTargetField );
676 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
677 inputIdField.setName( u
"InputID"_s );
678 fields.
append( inputIdField );
680 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
682 for (
long long i = 0; i < nPoints - 1; i++ )
684 fields.
append(
QgsField( u
"TargetID_%1"_s.arg( i + 1 ), targetIdType ) );
685 fields.
append(
QgsField( u
"Dist_%1"_s.arg( i + 1 ), QMetaType::Type::Double ) );
689 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
695 QHash<QgsFeatureId, QVariant> targetIdCache;
696 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
697 targetPointsCache.reserve( mTarget->featureCount() );
699 double step = mTarget->featureCount() > 0 ? 50.0 /
static_cast<double>( mTarget->featureCount() ) : 1;
700 long long current = 0;
709 targetIdCache.insert( f.
id(), f.
attribute( targetIndex ) );
712 feedback->
setProgress(
static_cast<double>( current ) * step );
721 step = mSource->featureCount() > 0 ? 50.0 /
static_cast<double>( mSource->featureCount() ) : 0;
733 const QString sourceId = sourceFeature.
attribute( sourceIndex ).toString();
734 std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
737 attrs.reserve( 1 + nPoints * 2 );
739 for (
const auto &targetPoint : nearestPoints )
746 if ( mSameLayer && sourceFeature.
id() == targetPoint.featureId )
751 attrs << targetIdCache.value( targetPoint.featureId ) << targetPoint.
distance;
763 feedback->
setProgress( 50.0 +
static_cast<double>( current ) * step );
771 outputs.insert( u
"OUTPUT"_s, dest );
@ VectorPoint
Vector point layers.
@ RespectsEllipsoid
Algorithm respects the context's ellipsoid settings, and uses ellipsoidal based measurements.
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
WkbType
The WKB type describes the number of dimensions a geometry has.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
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 & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
Encapsulate a field in an attribute table or data source.
Container of fields for a vector layer.
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters ¶meters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr)
Compute the unary union on a list of geometries.
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QString ellipsoid() const
Returns the ellipsoid to use for distance and area calculations.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
A feature sink output for processing algorithms.
A spatial index for QgsFeature objects.
@ FlagStoreFeatureGeometries
Indicates that the spatial index should also store feature geometries. This requires more memory,...
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features