QGIS API Documentation 4.3.0-Master (d3b565c628d)
Loading...
Searching...
No Matches
qgsalgorithmdistancematrix.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmdistancematrix.cpp
3 ---------------------
4 begin : May 2026
5 copyright : (C) 2026 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsdistanceutils.h"
21#include "qgsspatialindex.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QString QgsDistanceMatrixAlgorithm::name() const
30{
31 return u"distancematrix"_s;
32}
33
34QString QgsDistanceMatrixAlgorithm::displayName() const
35{
36 return QObject::tr( "Distance matrix" );
37}
38
39QStringList QgsDistanceMatrixAlgorithm::tags() const
40{
41 return QObject::tr( "point,distance,matrix,nearest,closest,summary" ).split( ',' );
42}
43
44QString QgsDistanceMatrixAlgorithm::group() const
45{
46 return QObject::tr( "Vector analysis" );
47}
48
49QString QgsDistanceMatrixAlgorithm::groupId() const
50{
51 return u"vectoranalysis"_s;
52}
53
54QString QgsDistanceMatrixAlgorithm::shortHelpString() const
55{
56 return QObject::tr(
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."
66 );
67}
68
69QString QgsDistanceMatrixAlgorithm::shortDescription() const
70{
71 return QObject::tr( "Creates a table containing a matrix of distances between all the points in a points layer." );
72}
73
74Qgis::ProcessingAlgorithmDocumentationFlags QgsDistanceMatrixAlgorithm::documentationFlags() const
75{
77}
78
79QgsDistanceMatrixAlgorithm *QgsDistanceMatrixAlgorithm::createInstance() const
80{
81 return new QgsDistanceMatrixAlgorithm();
82}
83
84void QgsDistanceMatrixAlgorithm::initAlgorithm( const QVariantMap & )
85{
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>(
99 u"MATRIX_TYPE"_s,
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)" ),
102 false,
103 0
104 );
105 matrixTypeParam->setHelp(
106 QObject::tr(
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."
110 )
111 );
112 addParameter( matrixTypeParam.release() );
113 auto pointsParam
114 = std::make_unique<QgsProcessingParameterNumber>( u"NEAREST_POINTS"_s, QObject::tr( "Use only the nearest (k) target points" ), Qgis::ProcessingNumberParameterType::Integer, 0, false, 0 );
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." )
120 );
121 addParameter( ellipsoidParam.release() );
122 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Distance matrix" ), Qgis::ProcessingSourceType::VectorPoint, QVariant() ) );
123}
124
125bool QgsDistanceMatrixAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
126{
127 mSource.reset( parameterAsSource( parameters, u"INPUT"_s, context ) );
128 if ( !mSource )
129 {
130 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
131 }
132
133 if ( QgsWkbTypes::isMultiType( mSource->wkbType() ) )
134 {
135 throw QgsProcessingException( QObject::tr( "Input point layer is a MultiPoint layer - convert to single points before using this algorithm." ) );
136 }
137
138 mTarget.reset( parameterAsSource( parameters, u"TARGET"_s, context ) );
139 if ( !mTarget )
140 {
141 throw QgsProcessingException( invalidSourceError( parameters, u"TARGET"_s ) );
142 }
143
144 if ( QgsWkbTypes::isMultiType( mTarget->wkbType() ) )
145 {
146 throw QgsProcessingException( QObject::tr( "Target point layer is a MultiPoint layer - convert to single points before using this algorithm." ) );
147 }
148
149 mSourceField = parameterAsString( parameters, u"INPUT_FIELD"_s, context );
150 mTargetField = parameterAsString( parameters, u"TARGET_FIELD"_s, context );
151
152 const int sourceFieldIndex = mSource->fields().lookupField( mSourceField );
153 if ( sourceFieldIndex < 0 )
154 {
155 throw QgsProcessingException( QObject::tr( "Missing field %1 in source layer" ).arg( mSourceField ) );
156 }
157
158 const int targetFieldIndex = mTarget->fields().lookupField( mTargetField );
159 if ( targetFieldIndex < 0 )
160 {
161 throw QgsProcessingException( QObject::tr( "Missing field %1 in target layer" ).arg( mTargetField ) );
162 }
163
164 mMatrixType = static_cast<MatrixType>( parameterAsEnum( parameters, u"MATRIX_TYPE"_s, context ) );
165 mSameLayer = ( parameters.value( u"INPUT"_s ) == parameters.value( u"TARGET"_s ) );
166
167 mKPoints = parameterAsInt( parameters, u"NEAREST_POINTS"_s, context );
168 if ( mKPoints < 1 )
169 {
170 mKPoints = static_cast<int>( mTarget->featureCount() );
171 }
172
173 mUseEllipsoid = parameterAsBool( parameters, u"USE_ELLIPSOID"_s, context );
174
175 return true;
176}
177
178QVariantMap QgsDistanceMatrixAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
179{
180 QGS_MARK_ALGORITHM_SOURCE
181
182 if ( mUseEllipsoid )
183 {
184 switch ( mMatrixType )
185 {
186 case Linear:
187 case Summary:
188 return linearMatrixEllipsoid( parameters, context, feedback );
189
190 case Standard:
191 return regularMatrixEllipsoid( parameters, context, feedback );
192 }
193 }
194 else
195 {
196 switch ( mMatrixType )
197 {
198 case Linear:
199 case Summary:
200 return linearMatrixCartesian( parameters, context, feedback );
201
202 case Standard:
203 return regularMatrixCartesian( parameters, context, feedback );
204 }
205 }
206
207 return {};
208}
209
210QVariantMap QgsDistanceMatrixAlgorithm::linearMatrixCartesian( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
211{
212 // when using the same layer we need to fetch an extra point from the index, since the closest match will always be the same as the input feature
213 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
214
215 const int sourceIndex = mSource->fields().lookupField( mSourceField );
216 const int targetIndex = mTarget->fields().lookupField( mTargetField );
217
218 QgsFields fields;
219 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
220 inputIdField.setName( u"InputID"_s );
221 fields.append( inputIdField );
222
223 if ( mMatrixType == Linear )
224 {
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 ) );
229 }
230 else
231 {
232 fields.append( QgsField( u"MEAN"_s, QMetaType::Type::Double ) );
233 fields.append( QgsField( u"STDDEV"_s, QMetaType::Type::Double ) );
234 fields.append( QgsField( u"MIN"_s, QMetaType::Type::Double ) );
235 fields.append( QgsField( u"MAX"_s, QMetaType::Type::Double ) );
236 }
237
238 const Qgis::WkbType outputWkbType = ( mMatrixType == Linear ) ? QgsWkbTypes::multiType( mSource->wkbType() ) : mSource->wkbType();
239
240 QString dest;
241 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
242 if ( !sink )
243 {
244 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
245 }
246
247 const QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
248 QHash<QgsFeatureId, QVariant> targetIdCache;
249 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
250 long long current = 0;
251 const QgsSpatialIndex index(
252 targetIterator,
253 [&]( const QgsFeature &f ) -> bool {
254 if ( feedback->isCanceled() )
255 {
256 return false;
257 }
258
259 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
260 feedback->setProgress( static_cast<double>( current ) * step );
261 current++;
262
263 return true;
264 },
266 );
267
269 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
270
271 double distance = 0;
272 QVector<double> distancesList;
273 distancesList.reserve( nPoints );
274
275 current = 0;
276 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
277 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
278 QgsFeature sourceFeature;
279
280 while ( features.nextFeature( sourceFeature ) )
281 {
282 if ( feedback->isCanceled() )
283 {
284 break;
285 }
286
287 distancesList.clear();
288 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
289 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
290 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
291
292 if ( mMatrixType == Linear )
293 {
294 for ( const QgsFeatureId targetId : nearestIds )
295 {
296 if ( feedback->isCanceled() )
297 {
298 break;
299 }
300
301 if ( mSameLayer && sourceFeature.id() == targetId )
302 {
303 continue;
304 }
305
306 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
307 distance = da.measureLine( sourcePoint, targetPoint );
308
309 QgsFeature f;
310 f.setGeometry( QgsGeometry::unaryUnion( { sourceFeature.geometry(), index.geometry( targetId ) } ) );
311 f.setAttributes( QgsAttributes() << sourceId << targetIdCache.value( targetId ) << distance );
312 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
313 {
314 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
315 }
316 feedback->featureAddedToSink( u"OUTPUT"_s );
317 }
318 }
319 else // Summary
320 {
321 for ( const QgsFeatureId targetId : nearestIds )
322 {
323 if ( feedback->isCanceled() )
324 {
325 break;
326 }
327
328 if ( mSameLayer && sourceFeature.id() == targetId )
329 {
330 continue;
331 }
332
333 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
334 distancesList << da.measureLine( sourcePoint, targetPoint );
335 }
336
337 QgsFeature f;
338 f.setGeometry( sourceFeature.geometry() );
339 if ( distancesList.isEmpty() )
340 {
341 f.setAttributes( QgsAttributes() << sourceId << QVariant() << QVariant() << QVariant() << QVariant() );
342 }
343 else
344 {
345 double sum = 0;
346 double sumSquares = 0;
347 double minDistance = std::numeric_limits<double>::max();
348 double maxDistance = std::numeric_limits<double>::lowest();
349
350 for ( const double d : std::as_const( distancesList ) )
351 {
352 sum += d;
353 sumSquares += d * d;
354 minDistance = std::min( minDistance, d );
355 maxDistance = std::max( maxDistance, d );
356 }
357
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 );
362
363 f.setAttributes( QgsAttributes() << sourceId << mean << stdDev << minDistance << maxDistance );
364 }
365
366 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
367 {
368 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
369 }
370 feedback->featureAddedToSink( u"OUTPUT"_s );
371 }
372
373 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
374 current++;
375 }
376
377 sink->finalize();
378 feedback->featureSinkFinalized( u"OUTPUT"_s );
379
380 QVariantMap outputs;
381 outputs.insert( u"OUTPUT"_s, dest );
382 return outputs;
383}
384
385QVariantMap QgsDistanceMatrixAlgorithm::regularMatrixCartesian( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
386{
387 // when using the same layer we need to fetch an extra point from the index, since the closest match will always be the same as the input feature,
388 // however if all features are requested we do not need to fetch more features
389 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
390
391 const int sourceIndex = mSource->fields().lookupField( mSourceField );
392 const int targetIndex = mTarget->fields().lookupField( mTargetField );
393
394 QgsFields fields;
395 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
396 inputIdField.setName( u"InputID"_s );
397 fields.append( inputIdField );
398
399 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
400 // creating nPoints - 1 fields, as we do not take into account the input feature
401 for ( long long i = 0; i < nPoints - 1; i++ )
402 {
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 ) );
405 }
406
407 QString dest;
408 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
409 if ( !sink )
410 {
411 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
412 }
413
414 const QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
415 QHash<QgsFeatureId, QVariant> targetIdCache;
416 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
417 long long current = 0;
418 const QgsSpatialIndex index(
419 targetIterator,
420 [&]( const QgsFeature &f ) -> bool {
421 if ( feedback->isCanceled() )
422 {
423 return false;
424 }
425 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
426
427 feedback->setProgress( static_cast<double>( current ) * step );
428 current++;
429
430 return true;
431 },
433 );
434
436 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
437 double distance = 0;
438
439 current = 0;
440 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
441 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
442 QgsFeature sourceFeature;
443
444 while ( features.nextFeature( sourceFeature ) )
445 {
446 if ( feedback->isCanceled() )
447 {
448 break;
449 }
450
451 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
452 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
453 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
454
455 QgsAttributes attrs;
456 attrs.reserve( 1 + nPoints * 2 );
457 attrs << sourceId;
458 for ( const QgsFeatureId targetId : nearestIds )
459 {
460 if ( feedback->isCanceled() )
461 {
462 break;
463 }
464
465 if ( mSameLayer && sourceFeature.id() == targetId )
466 {
467 continue;
468 }
469
470 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
471 distance = da.measureLine( sourcePoint, targetPoint );
472 attrs << targetIdCache.value( targetId ) << distance;
473 }
474
475 QgsFeature f;
476 f.setGeometry( sourceFeature.geometry() );
477 f.setAttributes( attrs );
478 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
479 {
480 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
481 }
482 feedback->featureAddedToSink( u"OUTPUT"_s );
483
484 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
485 current++;
486 }
487
488 sink->finalize();
489 feedback->featureSinkFinalized( u"OUTPUT"_s );
490
491 QVariantMap outputs;
492 outputs.insert( u"OUTPUT"_s, dest );
493 return outputs;
494}
495
496QVariantMap QgsDistanceMatrixAlgorithm::linearMatrixEllipsoid( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
497{
498 // when using the same layer we need to fetch an extra point from the index, since the closest match will always be the same as the input feature
499 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
500
501 const int sourceIndex = mSource->fields().lookupField( mSourceField );
502 const int targetIndex = mTarget->fields().lookupField( mTargetField );
503
504 QgsFields fields;
505 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
506 inputIdField.setName( u"InputID"_s );
507 fields.append( inputIdField );
508
509 if ( mMatrixType == Linear )
510 {
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 ) );
515 }
516 else
517 {
518 fields.append( QgsField( u"MEAN"_s, QMetaType::Type::Double ) );
519 fields.append( QgsField( u"STDDEV"_s, QMetaType::Type::Double ) );
520 fields.append( QgsField( u"MIN"_s, QMetaType::Type::Double ) );
521 fields.append( QgsField( u"MAX"_s, QMetaType::Type::Double ) );
522 }
523
524 const Qgis::WkbType outputWkbType = ( mMatrixType == Linear ) ? QgsWkbTypes::multiType( mSource->wkbType() ) : mSource->wkbType();
525
526 QString dest;
527 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
528 if ( !sink )
529 {
530 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
531 }
532
533 QHash<QgsFeatureId, QVariant> targetIdCache;
534 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
535 targetPointsCache.reserve( mTarget->featureCount() );
536 QgsFeatureIterator targetFeatuIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
537 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
538 long long current = 0;
539 QgsFeature f;
540
541 while ( targetFeatuIterator.nextFeature( f ) )
542 {
543 if ( feedback->isCanceled() )
544 {
545 break;
546 }
547 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
548 targetPointsCache.emplace_back( f.id(), f.geometry().asPoint() );
549 feedback->setProgress( static_cast<double>( current ) * step );
550 current++;
551 }
552
554 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
555 da.setEllipsoid( context.ellipsoid() );
556
557 QVector<double> distancesList;
558 distancesList.reserve( nPoints );
559
560 current = 0;
561 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
562 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
563 QgsFeature sourceFeature;
564
565 while ( features.nextFeature( sourceFeature ) )
566 {
567 if ( feedback->isCanceled() )
568 {
569 break;
570 }
571
572 distancesList.clear();
573 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
574 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
575 const std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
576
577 if ( mMatrixType == Linear )
578 {
579 for ( const auto &targetPoint : nearestPoints )
580 {
581 if ( feedback->isCanceled() )
582 {
583 break;
584 }
585
586 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
587 {
588 continue;
589 }
590
591 QgsFeature f;
592 f.setGeometry( QgsGeometry::unaryUnion( { sourceFeature.geometry(), QgsGeometry::fromPointXY( targetPoint.point ) } ) );
593 f.setAttributes( QgsAttributes() << sourceId << targetIdCache.value( targetPoint.featureId ) << targetPoint.distance );
594 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
595 {
596 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
597 }
598 feedback->featureAddedToSink( u"OUTPUT"_s );
599 }
600 }
601 else // Summary
602 {
603 for ( const auto &targetPoint : nearestPoints )
604 {
605 if ( feedback->isCanceled() )
606 {
607 break;
608 }
609
610 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
611 {
612 continue;
613 }
614
615 distancesList << targetPoint.distance;
616 }
617
618 QgsFeature f;
619 f.setGeometry( sourceFeature.geometry() );
620 if ( distancesList.isEmpty() )
621 {
622 f.setAttributes( QgsAttributes() << sourceId << QVariant() << QVariant() << QVariant() << QVariant() );
623 }
624 else
625 {
626 double sum = 0;
627 double sumSquares = 0;
628 double minDistance = std::numeric_limits<double>::max();
629 double maxDistance = std::numeric_limits<double>::lowest();
630
631 for ( const double d : std::as_const( distancesList ) )
632 {
633 sum += d;
634 sumSquares += d * d;
635 minDistance = std::min( minDistance, d );
636 maxDistance = std::max( maxDistance, d );
637 }
638
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 );
643
644 f.setAttributes( QgsAttributes() << sourceId << mean << stdDev << minDistance << maxDistance );
645 }
646
647 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
648 {
649 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
650 }
651 feedback->featureAddedToSink( u"OUTPUT"_s );
652 }
653
654 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
655 current++;
656 }
657
658 sink->finalize();
659 feedback->featureSinkFinalized( u"OUTPUT"_s );
660
661 QVariantMap outputs;
662 outputs.insert( u"OUTPUT"_s, dest );
663 return outputs;
664}
665
666QVariantMap QgsDistanceMatrixAlgorithm::regularMatrixEllipsoid( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
667{
668 // when using the same layer we need to fetch an extra point from the index, since the closest match will always be the same as the input feature,
669 // however if all features are requested we do not need to fetch more features
670 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
671
672 const int sourceIndex = mSource->fields().lookupField( mSourceField );
673 const int targetIndex = mTarget->fields().lookupField( mTargetField );
674
675 QgsFields fields;
676 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
677 inputIdField.setName( u"InputID"_s );
678 fields.append( inputIdField );
679
680 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
681 // creating nPoints - 1 fields, as we do not take into account the input feature
682 for ( long long i = 0; i < nPoints - 1; i++ )
683 {
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 ) );
686 }
687
688 QString dest;
689 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
690 if ( !sink )
691 {
692 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
693 }
694
695 QHash<QgsFeatureId, QVariant> targetIdCache;
696 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
697 targetPointsCache.reserve( mTarget->featureCount() );
698 QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
699 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
700 long long current = 0;
701
702 QgsFeature f;
703 while ( targetIterator.nextFeature( f ) )
704 {
705 if ( feedback->isCanceled() )
706 {
707 break;
708 }
709 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
710 targetPointsCache.emplace_back( f.id(), f.geometry().asPoint() );
711
712 feedback->setProgress( static_cast<double>( current ) * step );
713 current++;
714 }
715
717 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
718 da.setEllipsoid( context.ellipsoid() );
719
720 current = 0;
721 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
722 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
723 QgsFeature sourceFeature;
724
725 while ( features.nextFeature( sourceFeature ) )
726 {
727 if ( feedback->isCanceled() )
728 {
729 break;
730 }
731
732 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
733 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
734 std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
735
736 QgsAttributes attrs;
737 attrs.reserve( 1 + nPoints * 2 );
738 attrs << sourceId;
739 for ( const auto &targetPoint : nearestPoints )
740 {
741 if ( feedback->isCanceled() )
742 {
743 break;
744 }
745
746 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
747 {
748 continue;
749 }
750
751 attrs << targetIdCache.value( targetPoint.featureId ) << targetPoint.distance;
752 }
753
754 QgsFeature f;
755 f.setGeometry( sourceFeature.geometry() );
756 f.setAttributes( attrs );
757 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
758 {
759 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
760 }
761 feedback->featureAddedToSink( u"OUTPUT"_s );
762
763 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
764 current++;
765 }
766
767 sink->finalize();
768 feedback->featureSinkFinalized( u"OUTPUT"_s );
769
770 QVariantMap outputs;
771 outputs.insert( u"OUTPUT"_s, dest );
772 return outputs;
773}
774
@ VectorPoint
Vector point layers.
Definition qgis.h:3750
@ RespectsEllipsoid
Algorithm respects the context's ellipsoid settings, and uses ellipsoidal based measurements.
Definition qgis.h:3838
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3847
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
A vector of attributes.
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...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:66
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.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
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 &parameters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr)
Compute the unary union on a list of geometries.
Represents a 2D point.
Definition qgspointxy.h:62
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:209
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