QGIS API Documentation 4.3.0-Master (0d5b841b09e)
Loading...
Searching...
No Matches
qgsalgorithmvalidatenetwork.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmvalidatenetwork.cpp
3 -------------------------------
4 begin : January 2026
5 copyright : (C) 2026 by Nyall Dawson
6 email : nyall dot dawson 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 "qgis.h"
21#include "qgsapplication.h"
22#include "qgsgraph.h"
23#include "qgsgraphbuilder.h"
24#include "qgslinestring.h"
28
29#include <QString>
30
31using namespace Qt::StringLiterals;
32
34
35QString QgsValidateNetworkAlgorithm::name() const
36{
37 return u"validatenetwork"_s;
38}
39
40QString QgsValidateNetworkAlgorithm::displayName() const
41{
42 return QObject::tr( "Validate network" );
43}
44
45QStringList QgsValidateNetworkAlgorithm::tags() const
46{
47 return QObject::tr( "topological,topology,check,graph,shortest,path" ).split( ',' );
48}
49
50QString QgsValidateNetworkAlgorithm::group() const
51{
52 return QObject::tr( "Network analysis" );
53}
54
55QString QgsValidateNetworkAlgorithm::groupId() const
56{
57 return u"networkanalysis"_s;
58}
59
60QIcon QgsValidateNetworkAlgorithm::icon() const
61{
62 return QgsApplication::getThemeIcon( u"/algorithms/mAlgorithmNetworkAnalysis.svg"_s );
63}
64
65QString QgsValidateNetworkAlgorithm::svgIconPath() const
66{
67 return QgsApplication::iconPath( u"/algorithms/mAlgorithmNetworkAnalysis.svg"_s );
68}
69
70QString QgsValidateNetworkAlgorithm::shortDescription() const
71{
72 return QObject::tr( "Validates a network line layer, identifying data and topology errors that may affect network analysis tools." );
73}
74
75QString QgsValidateNetworkAlgorithm::shortHelpString() const
76{
77 return QObject::tr(
78 "This algorithm analyzes a network vector layer to identify data and topology errors "
79 "that may affect network analysis tools (like shortest path).\n\n"
80 "Optional checks include:\n\n"
81 "1. Validating the 'Direction' field to ensure all direction field values in the input layer "
82 "match the configured forward/backward/both values. Errors will be reported if the direction field "
83 "value is non-null and does not match one of the configured values.\n"
84 "2. Checking node-to-node separation. This check identifies nodes from the network graph that "
85 "are closer to other nodes than the specified tolerance distance. This often indicates missed "
86 "snaps or short segments in the input layer. In the case that a node violates this condition with multiple other "
87 "nodes, only the closest violation will be reported.\n"
88 "3. Checking node-to-segment separation: This check identifies nodes that are closer to a line "
89 "segment (e.g. a graph edge) than the specified tolerance distance, without being connected to it. In the case "
90 "that a node violates this condition with multiple other edges, only the closest violation will be reported.\n\n"
91 "Topology checks (node-to-node and node-to-segment) can optionally be restricted to only evaluate nodes that are topological dead-ends (connected to only one other distinct node). This is useful "
92 "for specifically targeting dangles or undershoots.\n\n"
93 "Two layers are output by this algorithm:\n"
94 "1. An output containing features from the original network layer which failed the direction validation checks.\n"
95 "2. An output representing the problematic node locations with a 'error' field explaining the error. This is "
96 "a line layer, where the output features join the problematic node to the node or "
97 "segment which failed the tolerance checks."
98 );
99}
100
101QgsValidateNetworkAlgorithm *QgsValidateNetworkAlgorithm::createInstance() const
102{
103 return new QgsValidateNetworkAlgorithm();
104}
105
106void QgsValidateNetworkAlgorithm::initAlgorithm( const QVariantMap & )
107{
108 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Vector layer representing network" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorLine ) ) );
109
110 auto separationNodeNodeParam = std::make_unique<QgsProcessingParameterDistance>( u"TOLERANCE_NODE_NODE"_s, QObject::tr( "Minimum separation between nodes" ), QVariant(), u"INPUT"_s, true );
111 separationNodeNodeParam->setFlags( separationNodeNodeParam->flags() | Qgis::ProcessingParameterFlag::Optional );
112 separationNodeNodeParam->setHelp(
113 QObject::tr(
114 "The minimum allowed distance between two distinct graph nodes.\n\n"
115 "Nodes closer than this distance (but not identical) will be flagged as errors.\n\n"
116 "Leave empty to disable this check."
117 )
118 );
119 addParameter( separationNodeNodeParam.release() );
120
121 auto separationNodeSegmentParam
122 = std::make_unique<QgsProcessingParameterDistance>( u"TOLERANCE_NODE_SEGMENT"_s, QObject::tr( "Minimum separation between nodes and non-noded segments" ), QVariant(), u"INPUT"_s, true );
123 separationNodeSegmentParam->setFlags( separationNodeSegmentParam->flags() | Qgis::ProcessingParameterFlag::Optional );
124 separationNodeSegmentParam->setHelp(
125 QObject::tr(
126 "The minimum allowed distance between a graph node and a graph edge (segment) "
127 "that is not connected to the node.\n\n"
128 "Nodes closer to a segment than this distance "
129 "will be flagged. Leave empty to disable this check."
130 )
131 );
132 addParameter( separationNodeSegmentParam.release() );
133
134 auto endpointsOnlyParam = std::make_unique<QgsProcessingParameterBoolean>( u"ENDPOINTS_ONLY"_s, QObject::tr( "Only check for errors at end points" ), false );
135 endpointsOnlyParam->setHelp(
136 QObject::tr( "If checked, topology checks (node-to-node and node-to-segment) will only be evaluated for nodes that are topological dead-ends (connected to only one other distinct node)." )
137 );
138 addParameter( endpointsOnlyParam.release() );
139
140 auto directionField
141 = std::make_unique<QgsProcessingParameterField>( u"DIRECTION_FIELD"_s, QObject::tr( "Direction field" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, false, true );
142 directionField->setHelp( QObject::tr( "The attribute field specifying the direction of traffic flow for each segment." ) );
143 addParameter( directionField.release() );
144
145 auto forwardValue = std::make_unique<QgsProcessingParameterString>( u"VALUE_FORWARD"_s, QObject::tr( "Value for forward direction" ), QVariant(), false, true );
146 forwardValue->setHelp( QObject::tr( "The string value in the direction field that indicates one-way traffic in the digitized direction." ) );
147 addParameter( forwardValue.release() );
148
149 auto backwardValue = std::make_unique<QgsProcessingParameterString>( u"VALUE_BACKWARD"_s, QObject::tr( "Value for backward direction" ), QVariant(), false, true );
150 backwardValue->setHelp( QObject::tr( "The string value in the direction field that indicates one-way traffic opposite to the digitized direction." ) );
151 addParameter( backwardValue.release() );
152
153 auto bothValue = std::make_unique<QgsProcessingParameterString>( u"VALUE_BOTH"_s, QObject::tr( "Value for both directions" ), QVariant(), false, true );
154 bothValue->setHelp( QObject::tr( "The string value in the direction field that indicates two-way traffic." ) );
155 addParameter( bothValue.release() );
156
157 std::unique_ptr<QgsProcessingParameterNumber> tolerance = std::make_unique<QgsProcessingParameterDistance>( u"TOLERANCE"_s, QObject::tr( "Topology tolerance" ), 0, u"INPUT"_s, false, 0 );
158 tolerance->setFlags( tolerance->flags() | Qgis::ProcessingParameterFlag::Advanced );
159 addParameter( tolerance.release() );
160
161 auto invalidNetworkOutput
162 = std::make_unique< QgsProcessingParameterFeatureSink >( u"OUTPUT_INVALID_NETWORK"_s, QObject::tr( "Invalid network features" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true );
163 invalidNetworkOutput->setHelp(
164 QObject::tr(
165 "Output line layer containing geometries representing features from the network layer with validity errors.\n\n"
166 "This output includes an attribute explaining why each feature is invalid."
167 )
168 );
169 addParameter( invalidNetworkOutput.release() );
170
171 addOutput( new QgsProcessingOutputNumber( u"COUNT_INVALID_NETWORK_FEATURES"_s, QObject::tr( "Count of invalid network features" ) ) );
172
173 auto invalidNodeOutput
174 = std::make_unique< QgsProcessingParameterFeatureSink >( u"OUTPUT_INVALID_NODES"_s, QObject::tr( "Invalid network nodes" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true );
175 invalidNodeOutput->setHelp(
176 QObject::tr(
177 "Output line layer containing geometries representing nodes from the network layer with validity errors.\n\n"
178 "This output includes an attribute explaining why each node is invalid."
179 )
180 );
181 addParameter( invalidNodeOutput.release() );
182
183 addOutput( new QgsProcessingOutputNumber( u"COUNT_INVALID_NODES"_s, QObject::tr( "Count of invalid network nodes" ) ) );
184}
185
186QVariantMap QgsValidateNetworkAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
187{
188 QGS_MARK_ALGORITHM_SOURCE
189
190 std::unique_ptr<QgsFeatureSource> networkSource( parameterAsSource( parameters, u"INPUT"_s, context ) );
191 if ( !networkSource )
192 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
193
194 const QString directionFieldName = parameterAsString( parameters, u"DIRECTION_FIELD"_s, context );
195 const QString forwardValue = parameterAsString( parameters, u"VALUE_FORWARD"_s, context );
196 const QString backwardValue = parameterAsString( parameters, u"VALUE_BACKWARD"_s, context );
197 const QString bothValue = parameterAsString( parameters, u"VALUE_BOTH"_s, context );
198 const double tolerance = parameterAsDouble( parameters, u"TOLERANCE"_s, context );
199
200 const bool checkEndpointsOnly = parameterAsBoolean( parameters, u"ENDPOINTS_ONLY"_s, context );
201
202 double toleranceNodeToNode = 0;
203 bool checkNodeToNodeDistance = false;
204 if ( parameters.value( u"TOLERANCE_NODE_NODE"_s ).isValid() )
205 {
206 toleranceNodeToNode = parameterAsDouble( parameters, u"TOLERANCE_NODE_NODE"_s, context );
207 checkNodeToNodeDistance = ( toleranceNodeToNode > 0 );
208 }
209
210 double toleranceNodeToSegment = 0;
211 bool checkNodeToSegmentDistance = false;
212 if ( parameters.value( u"TOLERANCE_NODE_SEGMENT"_s ).isValid() )
213 {
214 toleranceNodeToSegment = parameterAsDouble( parameters, u"TOLERANCE_NODE_SEGMENT"_s, context );
215 checkNodeToSegmentDistance = ( toleranceNodeToSegment > 0 );
216 }
217
218 QgsFields newNetworkErrorFields;
219 newNetworkErrorFields.append( QgsField( u"error"_s, QMetaType::Type::QString ) );
220 const QgsFields networkErrorFields = QgsProcessingUtils::combineFields( networkSource->fields(), newNetworkErrorFields );
221
222 QString networkErrorDest;
223 std::unique_ptr<QgsFeatureSink> networkErrorSink(
224 parameterAsSink( parameters, u"OUTPUT_INVALID_NETWORK"_s, context, networkErrorDest, networkErrorFields, networkSource->wkbType(), networkSource->sourceCrs() )
225 );
226
227 QgsFields nodeErrorFields;
228 nodeErrorFields.append( QgsField( u"error"_s, QMetaType::Type::QString ) );
229
230 QString nodeErrorDest;
231 std::unique_ptr<QgsFeatureSink> nodeErrorSink( parameterAsSink( parameters, u"OUTPUT_INVALID_NODES"_s, context, nodeErrorDest, nodeErrorFields, Qgis::WkbType::LineString, networkSource->sourceCrs() ) );
232
233 QgsProcessingMultiStepFeedback multiFeedback( 4, feedback );
234 multiFeedback.setStepWeights( { 10, 40, 10, 40 } );
235 multiFeedback.setCurrentStep( 0 );
236
237 QVariantMap outputs;
238 if ( networkErrorSink )
239 outputs.insert( u"OUTPUT_INVALID_NETWORK"_s, networkErrorDest );
240 if ( nodeErrorSink )
241 outputs.insert( u"OUTPUT_INVALID_NODES"_s, nodeErrorDest );
242
243 // attribute validation
244 int directionFieldIdx = -1;
245 long long countInvalidFeatures = 0;
246 if ( !directionFieldName.isEmpty() )
247 {
248 directionFieldIdx = networkSource->fields().lookupField( directionFieldName );
249 if ( directionFieldIdx < 0 )
250 {
251 throw QgsProcessingException( QObject::tr( "Missing field %1 in input layer" ).arg( directionFieldName ) );
252 }
253
254 multiFeedback.pushInfo( QObject::tr( "Validating direction attributes…" ) );
255 const long long count = networkSource->featureCount();
256 long long current = 0;
257 const double step = count > 0 ? 100.0 / static_cast< double >( count ) : 1;
258
259 QgsFeatureIterator fit = networkSource->getFeatures();
260 QgsFeature feature;
261 while ( fit.nextFeature( feature ) )
262 {
263 if ( multiFeedback.isCanceled() )
264 break;
265
266 const QVariant val = feature.attribute( directionFieldIdx );
267 if ( !QgsVariantUtils::isNull( val ) )
268 {
269 const QString directionValueString = val.toString();
270 if ( directionValueString != forwardValue && directionValueString != backwardValue && directionValueString != bothValue )
271 {
272 if ( networkErrorSink )
273 {
274 QgsFeature outputFeature = feature;
275 QgsAttributes outputFeatureAttrs = outputFeature.attributes();
276 outputFeatureAttrs.append( QObject::tr( "Invalid direction value: '%1'" ).arg( directionValueString ) );
277 outputFeature.setAttributes( outputFeatureAttrs );
278 if ( !networkErrorSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
279 {
280 throw QgsProcessingException( writeFeatureError( networkErrorSink.get(), parameters, u"OUTPUT_INVALID_NETWORK"_s ) );
281 }
282 else
283 {
284 feedback->featureAddedToSink( u"OUTPUT_INVALID_NETWORK"_s );
285 }
286 }
287 countInvalidFeatures++;
288 }
289 }
290
291 current++;
292 multiFeedback.setProgress( static_cast< double >( current ) * step );
293 }
294
295 if ( networkErrorSink )
296 {
297 networkErrorSink->finalize();
298 feedback->featureSinkFinalized( u"OUTPUT_INVALID_NETWORK"_s );
299 }
300 }
301
302 outputs.insert( u"COUNT_INVALID_NETWORK_FEATURES"_s, countInvalidFeatures );
303 if ( countInvalidFeatures > 0 )
304 {
305 multiFeedback.reportError( QObject::tr( "Found %1 invalid network features" ).arg( countInvalidFeatures ) );
306 }
307
308 if ( !checkNodeToNodeDistance && !checkNodeToSegmentDistance )
309 {
310 // nothing more to do
311 return outputs;
312 }
313
314 multiFeedback.pushInfo( QObject::tr( "Building graph for topology validation…" ) );
315 multiFeedback.setCurrentStep( 1 );
316
317 QgsVectorLayerDirector director( networkSource.get(), directionFieldIdx, forwardValue, backwardValue, bothValue, QgsVectorLayerDirector::DirectionBoth );
318 QgsGraphBuilder builder( networkSource->sourceCrs(), true, tolerance, context.ellipsoid() );
319
320 QVector<QgsPointXY> snappedPoints;
321 director.makeGraph( &builder, {}, snappedPoints, &multiFeedback );
322
323 std::unique_ptr<QgsGraph> graph( builder.takeGraph() );
324
325 if ( multiFeedback.isCanceled() )
326 return outputs;
327
328 multiFeedback.pushInfo( QObject::tr( "Indexing graph nodes and edges…" ) );
329 multiFeedback.setCurrentStep( 2 );
330
331 // better index choice for point node index -- we satisfy the requirements
332 // of point geometries only, finalized once before reading
333 QgsSpatialIndexKDBush nodeIndex;
334 // standard QgsSpatialIndex for edges -- we can't use the faster KDBush index for these, as that is point only
336
337 const int vertexCount = graph->vertexCount();
338
339 const long long totalGraphElements = ( checkNodeToNodeDistance ? vertexCount : 0 ) + ( checkNodeToSegmentDistance ? graph->edgeCount() : 0 );
340 const double indexStep = totalGraphElements > 0 ? 100.0 / static_cast< double >( totalGraphElements ) : 1;
341 long long elementsProcessed = 0;
342
343 if ( checkNodeToNodeDistance )
344 {
345 for ( int i = 0; i < vertexCount; ++i )
346 {
347 if ( multiFeedback.isCanceled() )
348 break;
349 nodeIndex.addFeature( i, graph->vertex( i ).point() );
350 elementsProcessed++;
351 multiFeedback.setProgress( static_cast< double >( elementsProcessed ) * indexStep );
352 }
353 nodeIndex.finalize();
354 }
355
356 if ( checkNodeToSegmentDistance )
357 {
358 for ( int i = 0; i < graph->edgeCount(); ++i )
359 {
360 if ( multiFeedback.isCanceled() )
361 break;
362
363 const QgsGraphEdge &edge = graph->edge( i );
364 const QgsPointXY p1 = graph->vertex( edge.fromVertex() ).point();
365 const QgsPointXY p2 = graph->vertex( edge.toVertex() ).point();
366
367 edgeIndex.addFeature( i, QgsRectangle( p1, p2 ) );
368 elementsProcessed++;
369 multiFeedback.setProgress( static_cast< double >( elementsProcessed ) * indexStep );
370 }
371 }
372
373 // perform topology checks
374 multiFeedback.pushInfo( QObject::tr( "Validating graph topology…" ) );
375 multiFeedback.setCurrentStep( 2 );
376
377 const double topoStep = vertexCount > 0 ? 100.0 / vertexCount : 1;
378
379 struct NodeError
380 {
381 long long id = 0;
382 QgsPointXY pt;
383 double distance = std::numeric_limits<double>::max();
384 };
385
386 QSet< QPair< long long, long long > > alreadyReportedNodes;
387 long long countInvalidNodes = 0;
388
389 for ( long long i = 0; i < vertexCount; ++i )
390 {
391 if ( multiFeedback.isCanceled() )
392 break;
393
394 const QgsGraphVertex &v = graph->vertex( i );
395 const QgsPointXY &pt = v.point();
396
397 // whether we need to perform validation on this node
398 bool evaluateNode = true;
399
400 if ( checkEndpointsOnly )
401 {
402 // count unique neighbors to handle bidirectional segments (A->B and B->A) counting as one connection
403 QSet<int> adjacentNodeIndices;
404 for ( int edgeId : v.outgoingEdges() )
405 {
406 adjacentNodeIndices.insert( graph->edge( edgeId ).toVertex() );
407 }
408 for ( int edgeId : v.incomingEdges() )
409 {
410 adjacentNodeIndices.insert( graph->edge( edgeId ).fromVertex() );
411 }
412 if ( adjacentNodeIndices.count() != 1 )
413 {
414 evaluateNode = false;
415 }
416 }
417
418 if ( evaluateNode && checkNodeToNodeDistance )
419 {
420 const std::vector< QgsVectorLayerDirector::VertexSourceInfo > &fidsFirstNode = director.sourcesForVertex( i );
421
422 const QList<QgsSpatialIndexKDBushData> candidates = nodeIndex.intersects( QgsRectangle::fromCenterAndSize( pt, toleranceNodeToNode * 2, toleranceNodeToNode * 2 ) );
423
424 // only keep the closest violation
425 NodeError closestError;
426 for ( const QgsSpatialIndexKDBushData &data : candidates )
427 {
428 // skip self
429 if ( data.id == i )
430 continue;
431
432 // ignore nodes which are directly connected to each other
433 bool skip = false;
434 for ( const int edge : v.incomingEdges() )
435 {
436 if ( graph->edge( edge ).fromVertex() == i )
437 {
438 skip = true;
439 break;
440 }
441 }
442 if ( skip )
443 continue;
444 for ( const int edge : v.outgoingEdges() )
445 {
446 if ( graph->edge( edge ).toVertex() == data.id )
447 {
448 skip = true;
449 break;
450 }
451 }
452 if ( skip )
453 continue;
454
455 const std::vector<QgsVectorLayerDirector::VertexSourceInfo> &fidsSecondNode = director.sourcesForVertex( data.id );
456
457 bool shareCommonFeature = false;
458 for ( const QgsVectorLayerDirector::VertexSourceInfo &info1 : fidsFirstNode )
459 {
460 for ( const QgsVectorLayerDirector::VertexSourceInfo &info2 : fidsSecondNode )
461 {
462 if ( info1 == info2 )
463 {
464 shareCommonFeature = true;
465 break;
466 }
467 }
468 if ( shareCommonFeature )
469 break;
470 }
471
472 if ( shareCommonFeature )
473 {
474 // if there is a common feature joining these nodes, then don't consider them as invalid
475 continue;
476 }
477
478 const double distanceNodeToNode = pt.distance( data.point() );
479 if ( distanceNodeToNode < toleranceNodeToNode && distanceNodeToNode < closestError.distance )
480 {
481 closestError.distance = distanceNodeToNode;
482 closestError.id = data.id;
483 closestError.pt = data.point();
484 }
485 }
486
487 if ( !closestError.pt.isEmpty() )
488 {
489 const QPair< long long, long long > nodeId = qMakePair( std::min( closestError.id, i ), std::max( closestError.id, i ) );
490 if ( alreadyReportedNodes.contains( nodeId ) )
491 {
492 // already reported this -- eg when checking the other node in the pair
493 continue;
494 }
495 alreadyReportedNodes.insert( nodeId );
496
497 if ( nodeErrorSink )
498 {
499 QgsFeature nodeErrorFeature( nodeErrorFields );
500 nodeErrorFeature.setGeometry( std::make_unique< QgsLineString >( QVector<QgsPointXY>() << pt << closestError.pt ) );
501 nodeErrorFeature.setAttributes( QgsAttributes() << QObject::tr( "Node too close to adjacent node (%1 < %2)" ).arg( closestError.distance ).arg( toleranceNodeToNode ) );
502 if ( !nodeErrorSink->addFeature( nodeErrorFeature, QgsFeatureSink::FastInsert ) )
503 throw QgsProcessingException( writeFeatureError( nodeErrorSink.get(), parameters, u"OUTPUT_INVALID_NODES"_s ) );
504 else
505 feedback->featureAddedToSink( u"OUTPUT_INVALID_NODES"_s );
506 }
507 countInvalidNodes++;
508 }
509 }
510
511 if ( evaluateNode && checkNodeToSegmentDistance )
512 {
513 // only keep the closest violation
514 NodeError closestError;
515
516 const QList<QgsFeatureId> edgeIds = edgeIndex.intersects( QgsRectangle::fromCenterAndSize( pt, toleranceNodeToSegment * 2, toleranceNodeToSegment * 2 ) );
517 for ( QgsFeatureId edgeIdx : edgeIds )
518 {
519 const QgsGraphEdge &edge = graph->edge( static_cast< int >( edgeIdx ) );
520 // skip edges connected to this node
521 if ( edge.fromVertex() == i || edge.toVertex() == i )
522 continue;
523
524 const QgsPointXY p1 = graph->vertex( edge.fromVertex() ).point();
525 const QgsPointXY p2 = graph->vertex( edge.toVertex() ).point();
526
527 QgsPointXY closestPt;
528 const double distanceToSegment = std::sqrt( pt.sqrDistToSegment( p1.x(), p1.y(), p2.x(), p2.y(), closestPt ) );
529 if ( distanceToSegment >= toleranceNodeToSegment )
530 continue;
531
532 // we don't consider this a node-to-segment error if the closest point is actually one of the segment endpoints.
533 // in that case it's a node-to-NODE error.
534 if ( closestPt.compare( p1 ) || closestPt.compare( p2 ) )
535 {
536 continue;
537 }
538
539 if ( distanceToSegment > closestError.distance )
540 {
541 continue;
542 }
543 closestError.distance = distanceToSegment;
544 closestError.pt = closestPt;
545 }
546
547 if ( !closestError.pt.isEmpty() )
548 {
549 if ( nodeErrorSink )
550 {
551 QgsFeature nodeErrorFeature( nodeErrorFields );
552 nodeErrorFeature.setGeometry( std::make_unique< QgsLineString >( QVector<QgsPointXY>() << pt << closestError.pt ) );
553 nodeErrorFeature.setAttributes( QgsAttributes() << QObject::tr( "Node too close to non-noded segment (%1 < %2)" ).arg( closestError.distance ).arg( toleranceNodeToSegment ) );
554 if ( !nodeErrorSink->addFeature( nodeErrorFeature, QgsFeatureSink::FastInsert ) )
555 throw QgsProcessingException( writeFeatureError( nodeErrorSink.get(), parameters, u"OUTPUT_INVALID_NODES"_s ) );
556 else
557 feedback->featureAddedToSink( u"OUTPUT_INVALID_NODES"_s );
558 }
559 countInvalidNodes++;
560 }
561 }
562
563 multiFeedback.setProgress( static_cast< double >( i ) * topoStep );
564 }
565
566 if ( nodeErrorSink )
567 {
568 nodeErrorSink->finalize();
569 feedback->featureSinkFinalized( u"OUTPUT_INVALID_NODES"_s );
570 }
571
572 feedback->setProgress( 100 );
573 if ( countInvalidNodes > 0 )
574 {
575 multiFeedback.reportError( QObject::tr( "Found %1 invalid network nodes" ).arg( countInvalidNodes ) );
576 }
577
578 outputs.insert( u"COUNT_INVALID_NODES"_s, countInvalidNodes );
579
580 return outputs;
581}
582
@ VectorLine
Vector line layers.
Definition qgis.h:3753
@ LineString
LineString.
Definition qgis.h:297
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3984
@ Optional
Parameter is optional.
Definition qgis.h:3986
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A vector of attributes.
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.
@ 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
QgsAttributes attributes
Definition qgsfeature.h:64
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 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
Used for making the QgsGraph object.
Represents an edge in a graph.
Definition qgsgraph.h:44
int fromVertex() const
Returns the index of the vertex at the start of this edge.
Definition qgsgraph.cpp:180
int toVertex() const
Returns the index of the vertex at the end of this edge.
Definition qgsgraph.cpp:185
Represents vertex in a graph.
Definition qgsgraph.h:89
QgsGraphEdgeIds outgoingEdges() const
Returns outgoing edge ids, i.e.
Definition qgsgraph.cpp:199
QgsGraphEdgeIds incomingEdges() const
Returns the incoming edge ids, i.e.
Definition qgsgraph.cpp:194
QgsPointXY point() const
Returns point associated with graph vertex.
Definition qgsgraph.cpp:204
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
bool compare(const QgsPointXY &other, double epsilon=4 *std::numeric_limits< double >::epsilon()) const
Compares this point with another point with a fuzzy tolerance.
Definition qgspointxy.h:256
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
double sqrDistToSegment(double x1, double y1, double x2, double y2, QgsPointXY &minDistPoint, double epsilon=Qgis::DEFAULT_SEGMENT_EPSILON) const
Returns the minimum distance between this point and a segment.
Contains information about the context in which a processing algorithm is executed.
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.
Processing feedback object for multi-step operations.
A numeric output for processing algorithms.
An input feature source (such as vector layers) 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).
A rectangle specified with double values.
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
A container for data stored inside a QgsSpatialIndexKDBush index.
A very fast static spatial index for 2D points based on a flat KD-tree.
void finalize()
Finalizes the index after manually adding features.
QList< QgsSpatialIndexKDBushData > intersects(const QgsRectangle &rectangle) const
Returns the list of features which fall within the specified rectangle.
bool addFeature(QgsFeatureId id, const QgsPointXY &point)
Adds a single feature to the index.
A spatial index for QgsFeature objects.
@ FlagStoreFeatureGeometries
Indicates that the spatial index should also store feature geometries. This requires more memory,...
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Determines creating a graph from a vector line layer.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Represents information about a graph node's source vertex.