QGIS API Documentation 4.3.0-Master (7c7bd4d7018)
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 if ( mUseEllipsoid )
181 {
182 switch ( mMatrixType )
183 {
184 case Linear:
185 case Summary:
186 return linearMatrixEllipsoid( parameters, context, feedback );
187
188 case Standard:
189 return regularMatrixEllipsoid( parameters, context, feedback );
190 }
191 }
192 else
193 {
194 switch ( mMatrixType )
195 {
196 case Linear:
197 case Summary:
198 return linearMatrixCartesian( parameters, context, feedback );
199
200 case Standard:
201 return regularMatrixCartesian( parameters, context, feedback );
202 }
203 }
204
205 return {};
206}
207
208QVariantMap QgsDistanceMatrixAlgorithm::linearMatrixCartesian( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
209{
210 // 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
211 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
212
213 const int sourceIndex = mSource->fields().lookupField( mSourceField );
214 const int targetIndex = mTarget->fields().lookupField( mTargetField );
215
216 QgsFields fields;
217 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
218 inputIdField.setName( u"InputID"_s );
219 fields.append( inputIdField );
220
221 if ( mMatrixType == Linear )
222 {
223 QgsField targetIdField( mTarget->fields().at( targetIndex ) );
224 targetIdField.setName( u"TargetID"_s );
225 fields.append( targetIdField );
226 fields.append( QgsField( u"Distance"_s, QMetaType::Type::Double ) );
227 }
228 else
229 {
230 fields.append( QgsField( u"MEAN"_s, QMetaType::Type::Double ) );
231 fields.append( QgsField( u"STDDEV"_s, QMetaType::Type::Double ) );
232 fields.append( QgsField( u"MIN"_s, QMetaType::Type::Double ) );
233 fields.append( QgsField( u"MAX"_s, QMetaType::Type::Double ) );
234 }
235
236 const Qgis::WkbType outputWkbType = ( mMatrixType == Linear ) ? QgsWkbTypes::multiType( mSource->wkbType() ) : mSource->wkbType();
237
238 QString dest;
239 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
240 if ( !sink )
241 {
242 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
243 }
244
245 const QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
246 QHash<QgsFeatureId, QVariant> targetIdCache;
247 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
248 long long current = 0;
249 const QgsSpatialIndex index(
250 targetIterator,
251 [&]( const QgsFeature &f ) -> bool {
252 if ( feedback->isCanceled() )
253 {
254 return false;
255 }
256
257 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
258 feedback->setProgress( static_cast<double>( current ) * step );
259 current++;
260
261 return true;
262 },
264 );
265
267 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
268
269 double distance = 0;
270 QVector<double> distancesList;
271 distancesList.reserve( nPoints );
272
273 current = 0;
274 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
275 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
276 QgsFeature sourceFeature;
277
278 while ( features.nextFeature( sourceFeature ) )
279 {
280 if ( feedback->isCanceled() )
281 {
282 break;
283 }
284
285 distancesList.clear();
286 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
287 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
288 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
289
290 if ( mMatrixType == Linear )
291 {
292 for ( const QgsFeatureId targetId : nearestIds )
293 {
294 if ( feedback->isCanceled() )
295 {
296 break;
297 }
298
299 if ( mSameLayer && sourceFeature.id() == targetId )
300 {
301 continue;
302 }
303
304 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
305 distance = da.measureLine( sourcePoint, targetPoint );
306
307 QgsFeature f;
308 f.setGeometry( QgsGeometry::unaryUnion( { sourceFeature.geometry(), index.geometry( targetId ) } ) );
309 f.setAttributes( QgsAttributes() << sourceId << targetIdCache.value( targetId ) << distance );
310 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
311 {
312 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
313 }
314 feedback->featureAddedToSink( u"OUTPUT"_s );
315 }
316 }
317 else // Summary
318 {
319 for ( const QgsFeatureId targetId : nearestIds )
320 {
321 if ( feedback->isCanceled() )
322 {
323 break;
324 }
325
326 if ( mSameLayer && sourceFeature.id() == targetId )
327 {
328 continue;
329 }
330
331 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
332 distancesList << da.measureLine( sourcePoint, targetPoint );
333 }
334
335 QgsFeature f;
336 f.setGeometry( sourceFeature.geometry() );
337 if ( distancesList.isEmpty() )
338 {
339 f.setAttributes( QgsAttributes() << sourceId << QVariant() << QVariant() << QVariant() << QVariant() );
340 }
341 else
342 {
343 double sum = 0;
344 double sumSquares = 0;
345 double minDistance = std::numeric_limits<double>::max();
346 double maxDistance = std::numeric_limits<double>::lowest();
347
348 for ( const double d : std::as_const( distancesList ) )
349 {
350 sum += d;
351 sumSquares += d * d;
352 minDistance = std::min( minDistance, d );
353 maxDistance = std::max( maxDistance, d );
354 }
355
356 const long long n = distancesList.size();
357 const double mean = sum / static_cast<double>( n );
358 const double variance = std::max( 0.0, ( sumSquares / static_cast<double>( n ) ) - ( mean * mean ) );
359 const double stdDev = std::sqrt( variance );
360
361 f.setAttributes( QgsAttributes() << sourceId << mean << stdDev << minDistance << maxDistance );
362 }
363
364 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
365 {
366 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
367 }
368 feedback->featureAddedToSink( u"OUTPUT"_s );
369 }
370
371 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
372 current++;
373 }
374
375 sink->finalize();
376 feedback->featureSinkFinalized( u"OUTPUT"_s );
377
378 QVariantMap outputs;
379 outputs.insert( u"OUTPUT"_s, dest );
380 return outputs;
381}
382
383QVariantMap QgsDistanceMatrixAlgorithm::regularMatrixCartesian( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
384{
385 // 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,
386 // however if all features are requested we do not need to fetch more features
387 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
388
389 const int sourceIndex = mSource->fields().lookupField( mSourceField );
390 const int targetIndex = mTarget->fields().lookupField( mTargetField );
391
392 QgsFields fields;
393 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
394 inputIdField.setName( u"InputID"_s );
395 fields.append( inputIdField );
396
397 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
398 // creating nPoints - 1 fields, as we do not take into account the input feature
399 for ( long long i = 0; i < nPoints - 1; i++ )
400 {
401 fields.append( QgsField( u"TargetID_%1"_s.arg( i + 1 ), targetIdType ) );
402 fields.append( QgsField( u"Dist_%1"_s.arg( i + 1 ), QMetaType::Type::Double ) );
403 }
404
405 QString dest;
406 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
407 if ( !sink )
408 {
409 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
410 }
411
412 const QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
413 QHash<QgsFeatureId, QVariant> targetIdCache;
414 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
415 long long current = 0;
416 const QgsSpatialIndex index(
417 targetIterator,
418 [&]( const QgsFeature &f ) -> bool {
419 if ( feedback->isCanceled() )
420 {
421 return false;
422 }
423 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
424
425 feedback->setProgress( static_cast<double>( current ) * step );
426 current++;
427
428 return true;
429 },
431 );
432
434 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
435 double distance = 0;
436
437 current = 0;
438 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
439 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
440 QgsFeature sourceFeature;
441
442 while ( features.nextFeature( sourceFeature ) )
443 {
444 if ( feedback->isCanceled() )
445 {
446 break;
447 }
448
449 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
450 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
451 const QList<QgsFeatureId> nearestIds = index.nearestNeighbor( sourcePoint, nPoints );
452
453 QgsAttributes attrs;
454 attrs.reserve( 1 + nPoints * 2 );
455 attrs << sourceId;
456 for ( const QgsFeatureId targetId : nearestIds )
457 {
458 if ( feedback->isCanceled() )
459 {
460 break;
461 }
462
463 if ( mSameLayer && sourceFeature.id() == targetId )
464 {
465 continue;
466 }
467
468 QgsPointXY targetPoint = index.geometry( targetId ).asPoint();
469 distance = da.measureLine( sourcePoint, targetPoint );
470 attrs << targetIdCache.value( targetId ) << distance;
471 }
472
473 QgsFeature f;
474 f.setGeometry( sourceFeature.geometry() );
475 f.setAttributes( attrs );
476 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
477 {
478 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
479 }
480 feedback->featureAddedToSink( u"OUTPUT"_s );
481
482 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
483 current++;
484 }
485
486 sink->finalize();
487 feedback->featureSinkFinalized( u"OUTPUT"_s );
488
489 QVariantMap outputs;
490 outputs.insert( u"OUTPUT"_s, dest );
491 return outputs;
492}
493
494QVariantMap QgsDistanceMatrixAlgorithm::linearMatrixEllipsoid( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
495{
496 // 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
497 long long nPoints = mSameLayer ? mKPoints + 1 : mKPoints;
498
499 const int sourceIndex = mSource->fields().lookupField( mSourceField );
500 const int targetIndex = mTarget->fields().lookupField( mTargetField );
501
502 QgsFields fields;
503 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
504 inputIdField.setName( u"InputID"_s );
505 fields.append( inputIdField );
506
507 if ( mMatrixType == Linear )
508 {
509 QgsField targetIdField( mTarget->fields().at( targetIndex ) );
510 targetIdField.setName( u"TargetID"_s );
511 fields.append( targetIdField );
512 fields.append( QgsField( u"Distance"_s, QMetaType::Type::Double ) );
513 }
514 else
515 {
516 fields.append( QgsField( u"MEAN"_s, QMetaType::Type::Double ) );
517 fields.append( QgsField( u"STDDEV"_s, QMetaType::Type::Double ) );
518 fields.append( QgsField( u"MIN"_s, QMetaType::Type::Double ) );
519 fields.append( QgsField( u"MAX"_s, QMetaType::Type::Double ) );
520 }
521
522 const Qgis::WkbType outputWkbType = ( mMatrixType == Linear ) ? QgsWkbTypes::multiType( mSource->wkbType() ) : mSource->wkbType();
523
524 QString dest;
525 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, outputWkbType, mSource->sourceCrs() ) );
526 if ( !sink )
527 {
528 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
529 }
530
531 QHash<QgsFeatureId, QVariant> targetIdCache;
532 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
533 targetPointsCache.reserve( mTarget->featureCount() );
534 QgsFeatureIterator targetFeatuIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
535 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
536 long long current = 0;
537 QgsFeature f;
538
539 while ( targetFeatuIterator.nextFeature( f ) )
540 {
541 if ( feedback->isCanceled() )
542 {
543 break;
544 }
545 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
546 targetPointsCache.emplace_back( f.id(), f.geometry().asPoint() );
547 feedback->setProgress( static_cast<double>( current ) * step );
548 current++;
549 }
550
552 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
553 da.setEllipsoid( context.ellipsoid() );
554
555 QVector<double> distancesList;
556 distancesList.reserve( nPoints );
557
558 current = 0;
559 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
560 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
561 QgsFeature sourceFeature;
562
563 while ( features.nextFeature( sourceFeature ) )
564 {
565 if ( feedback->isCanceled() )
566 {
567 break;
568 }
569
570 distancesList.clear();
571 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
572 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
573 const std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
574
575 if ( mMatrixType == Linear )
576 {
577 for ( const auto &targetPoint : nearestPoints )
578 {
579 if ( feedback->isCanceled() )
580 {
581 break;
582 }
583
584 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
585 {
586 continue;
587 }
588
589 QgsFeature f;
590 f.setGeometry( QgsGeometry::unaryUnion( { sourceFeature.geometry(), QgsGeometry::fromPointXY( targetPoint.point ) } ) );
591 f.setAttributes( QgsAttributes() << sourceId << targetIdCache.value( targetPoint.featureId ) << targetPoint.distance );
592 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
593 {
594 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
595 }
596 feedback->featureAddedToSink( u"OUTPUT"_s );
597 }
598 }
599 else // Summary
600 {
601 for ( const auto &targetPoint : nearestPoints )
602 {
603 if ( feedback->isCanceled() )
604 {
605 break;
606 }
607
608 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
609 {
610 continue;
611 }
612
613 distancesList << targetPoint.distance;
614 }
615
616 QgsFeature f;
617 f.setGeometry( sourceFeature.geometry() );
618 if ( distancesList.isEmpty() )
619 {
620 f.setAttributes( QgsAttributes() << sourceId << QVariant() << QVariant() << QVariant() << QVariant() );
621 }
622 else
623 {
624 double sum = 0;
625 double sumSquares = 0;
626 double minDistance = std::numeric_limits<double>::max();
627 double maxDistance = std::numeric_limits<double>::lowest();
628
629 for ( const double d : std::as_const( distancesList ) )
630 {
631 sum += d;
632 sumSquares += d * d;
633 minDistance = std::min( minDistance, d );
634 maxDistance = std::max( maxDistance, d );
635 }
636
637 const long long n = distancesList.size();
638 const double mean = sum / static_cast<double>( n );
639 const double variance = std::max( 0.0, ( sumSquares / static_cast<double>( n ) ) - ( mean * mean ) );
640 const double stdDev = std::sqrt( variance );
641
642 f.setAttributes( QgsAttributes() << sourceId << mean << stdDev << minDistance << maxDistance );
643 }
644
645 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
646 {
647 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
648 }
649 feedback->featureAddedToSink( u"OUTPUT"_s );
650 }
651
652 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
653 current++;
654 }
655
656 sink->finalize();
657 feedback->featureSinkFinalized( u"OUTPUT"_s );
658
659 QVariantMap outputs;
660 outputs.insert( u"OUTPUT"_s, dest );
661 return outputs;
662}
663
664QVariantMap QgsDistanceMatrixAlgorithm::regularMatrixEllipsoid( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
665{
666 // 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,
667 // however if all features are requested we do not need to fetch more features
668 long long nPoints = mSameLayer ? mKPoints == mTarget->featureCount() ? mKPoints : mKPoints + 1 : mKPoints;
669
670 const int sourceIndex = mSource->fields().lookupField( mSourceField );
671 const int targetIndex = mTarget->fields().lookupField( mTargetField );
672
673 QgsFields fields;
674 QgsField inputIdField( mSource->fields().at( sourceIndex ) );
675 inputIdField.setName( u"InputID"_s );
676 fields.append( inputIdField );
677
678 QMetaType::Type targetIdType = mTarget->fields().at( targetIndex ).type();
679 // creating nPoints - 1 fields, as we do not take into account the input feature
680 for ( long long i = 0; i < nPoints - 1; i++ )
681 {
682 fields.append( QgsField( u"TargetID_%1"_s.arg( i + 1 ), targetIdType ) );
683 fields.append( QgsField( u"Dist_%1"_s.arg( i + 1 ), QMetaType::Type::Double ) );
684 }
685
686 QString dest;
687 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, mSource->wkbType(), mSource->sourceCrs() ) );
688 if ( !sink )
689 {
690 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
691 }
692
693 QHash<QgsFeatureId, QVariant> targetIdCache;
694 std::vector<std::pair<QgsFeatureId, QgsPointXY>> targetPointsCache;
695 targetPointsCache.reserve( mTarget->featureCount() );
696 QgsFeatureIterator targetIterator = mTarget->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { targetIndex } ).setDestinationCrs( mSource->sourceCrs(), context.transformContext() ) );
697 double step = mTarget->featureCount() > 0 ? 50.0 / static_cast<double>( mTarget->featureCount() ) : 1;
698 long long current = 0;
699
700 QgsFeature f;
701 while ( targetIterator.nextFeature( f ) )
702 {
703 if ( feedback->isCanceled() )
704 {
705 break;
706 }
707 targetIdCache.insert( f.id(), f.attribute( targetIndex ) );
708 targetPointsCache.emplace_back( f.id(), f.geometry().asPoint() );
709
710 feedback->setProgress( static_cast<double>( current ) * step );
711 current++;
712 }
713
715 da.setSourceCrs( mSource->sourceCrs(), context.transformContext() );
716 da.setEllipsoid( context.ellipsoid() );
717
718 current = 0;
719 step = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 0;
720 QgsFeatureIterator features = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( { sourceIndex } ) );
721 QgsFeature sourceFeature;
722
723 while ( features.nextFeature( sourceFeature ) )
724 {
725 if ( feedback->isCanceled() )
726 {
727 break;
728 }
729
730 const QgsPointXY sourcePoint = sourceFeature.geometry().asPoint();
731 const QString sourceId = sourceFeature.attribute( sourceIndex ).toString();
732 std::vector<QgsDistanceUtils::NeighborResult> nearestPoints = QgsDistanceUtils::nearestNeighbors( sourcePoint, targetPointsCache, da, nPoints, feedback );
733
734 QgsAttributes attrs;
735 attrs.reserve( 1 + nPoints * 2 );
736 attrs << sourceId;
737 for ( const auto &targetPoint : nearestPoints )
738 {
739 if ( feedback->isCanceled() )
740 {
741 break;
742 }
743
744 if ( mSameLayer && sourceFeature.id() == targetPoint.featureId )
745 {
746 continue;
747 }
748
749 attrs << targetIdCache.value( targetPoint.featureId ) << targetPoint.distance;
750 }
751
752 QgsFeature f;
753 f.setGeometry( sourceFeature.geometry() );
754 f.setAttributes( attrs );
755 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
756 {
757 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
758 }
759 feedback->featureAddedToSink( u"OUTPUT"_s );
760
761 feedback->setProgress( 50.0 + static_cast<double>( current ) * step );
762 current++;
763 }
764
765 sink->finalize();
766 feedback->featureSinkFinalized( u"OUTPUT"_s );
767
768 QVariantMap outputs;
769 outputs.insert( u"OUTPUT"_s, dest );
770 return outputs;
771}
772
@ VectorPoint
Vector point layers.
Definition qgis.h:3716
@ RespectsEllipsoid
Algorithm respects the context's ellipsoid settings, and uses ellipsoidal based measurements.
Definition qgis.h:3804
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3813
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:46
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