QGIS API Documentation 4.1.0-Master (ca2ac17535b)
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 std::unique_ptr<QgsFeatureSource> networkSource( parameterAsSource( parameters, u"INPUT"_s, context ) );
189 if ( !networkSource )
190 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
191
192 const QString directionFieldName = parameterAsString( parameters, u"DIRECTION_FIELD"_s, context );
193 const QString forwardValue = parameterAsString( parameters, u"VALUE_FORWARD"_s, context );
194 const QString backwardValue = parameterAsString( parameters, u"VALUE_BACKWARD"_s, context );
195 const QString bothValue = parameterAsString( parameters, u"VALUE_BOTH"_s, context );
196 const double tolerance = parameterAsDouble( parameters, u"TOLERANCE"_s, context );
197
198 const bool checkEndpointsOnly = parameterAsBoolean( parameters, u"ENDPOINTS_ONLY"_s, context );
199
200 double toleranceNodeToNode = 0;
201 bool checkNodeToNodeDistance = false;
202 if ( parameters.value( u"TOLERANCE_NODE_NODE"_s ).isValid() )
203 {
204 toleranceNodeToNode = parameterAsDouble( parameters, u"TOLERANCE_NODE_NODE"_s, context );
205 checkNodeToNodeDistance = ( toleranceNodeToNode > 0 );
206 }
207
208 double toleranceNodeToSegment = 0;
209 bool checkNodeToSegmentDistance = false;
210 if ( parameters.value( u"TOLERANCE_NODE_SEGMENT"_s ).isValid() )
211 {
212 toleranceNodeToSegment = parameterAsDouble( parameters, u"TOLERANCE_NODE_SEGMENT"_s, context );
213 checkNodeToSegmentDistance = ( toleranceNodeToSegment > 0 );
214 }
215
216 QgsFields newNetworkErrorFields;
217 newNetworkErrorFields.append( QgsField( u"error"_s, QMetaType::Type::QString ) );
218 const QgsFields networkErrorFields = QgsProcessingUtils::combineFields( networkSource->fields(), newNetworkErrorFields );
219
220 QString networkErrorDest;
221 std::unique_ptr<QgsFeatureSink> networkErrorSink(
222 parameterAsSink( parameters, u"OUTPUT_INVALID_NETWORK"_s, context, networkErrorDest, networkErrorFields, networkSource->wkbType(), networkSource->sourceCrs() )
223 );
224
225 QgsFields nodeErrorFields;
226 nodeErrorFields.append( QgsField( u"error"_s, QMetaType::Type::QString ) );
227
228 QString nodeErrorDest;
229 std::unique_ptr<QgsFeatureSink> nodeErrorSink( parameterAsSink( parameters, u"OUTPUT_INVALID_NODES"_s, context, nodeErrorDest, nodeErrorFields, Qgis::WkbType::LineString, networkSource->sourceCrs() ) );
230
231 QgsProcessingMultiStepFeedback multiFeedback( 4, feedback );
232 multiFeedback.setStepWeights( { 10, 40, 10, 40 } );
233 multiFeedback.setCurrentStep( 0 );
234
235 QVariantMap outputs;
236 if ( networkErrorSink )
237 outputs.insert( u"OUTPUT_INVALID_NETWORK"_s, networkErrorDest );
238 if ( nodeErrorSink )
239 outputs.insert( u"OUTPUT_INVALID_NODES"_s, nodeErrorDest );
240
241 // attribute validation
242 int directionFieldIdx = -1;
243 long long countInvalidFeatures = 0;
244 if ( !directionFieldName.isEmpty() )
245 {
246 directionFieldIdx = networkSource->fields().lookupField( directionFieldName );
247 if ( directionFieldIdx < 0 )
248 {
249 throw QgsProcessingException( QObject::tr( "Missing field %1 in input layer" ).arg( directionFieldName ) );
250 }
251
252 multiFeedback.pushInfo( QObject::tr( "Validating direction attributes…" ) );
253 const long long count = networkSource->featureCount();
254 long long current = 0;
255 const double step = count > 0 ? 100.0 / static_cast< double >( count ) : 1;
256
257 QgsFeatureIterator fit = networkSource->getFeatures();
258 QgsFeature feature;
259 while ( fit.nextFeature( feature ) )
260 {
261 if ( multiFeedback.isCanceled() )
262 break;
263
264 const QVariant val = feature.attribute( directionFieldIdx );
265 if ( !QgsVariantUtils::isNull( val ) )
266 {
267 const QString directionValueString = val.toString();
268 if ( directionValueString != forwardValue && directionValueString != backwardValue && directionValueString != bothValue )
269 {
270 if ( networkErrorSink )
271 {
272 QgsFeature outputFeature = feature;
273 QgsAttributes outputFeatureAttrs = outputFeature.attributes();
274 outputFeatureAttrs.append( QObject::tr( "Invalid direction value: '%1'" ).arg( directionValueString ) );
275 outputFeature.setAttributes( outputFeatureAttrs );
276 if ( !networkErrorSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
277 {
278 throw QgsProcessingException( writeFeatureError( networkErrorSink.get(), parameters, u"OUTPUT_INVALID_NETWORK"_s ) );
279 }
280 else
281 {
282 feedback->featureAddedToSink( u"OUTPUT_INVALID_NETWORK"_s );
283 }
284 }
285 countInvalidFeatures++;
286 }
287 }
288
289 current++;
290 multiFeedback.setProgress( static_cast< double >( current ) * step );
291 }
292
293 if ( networkErrorSink )
294 {
295 networkErrorSink->finalize();
296 feedback->featureSinkFinalized( u"OUTPUT_INVALID_NETWORK"_s );
297 }
298 }
299
300 outputs.insert( u"COUNT_INVALID_NETWORK_FEATURES"_s, countInvalidFeatures );
301 if ( countInvalidFeatures > 0 )
302 {
303 multiFeedback.reportError( QObject::tr( "Found %1 invalid network features" ).arg( countInvalidFeatures ) );
304 }
305
306 if ( !checkNodeToNodeDistance && !checkNodeToSegmentDistance )
307 {
308 // nothing more to do
309 return outputs;
310 }
311
312 multiFeedback.pushInfo( QObject::tr( "Building graph for topology validation…" ) );
313 multiFeedback.setCurrentStep( 1 );
314
315 QgsVectorLayerDirector director( networkSource.get(), directionFieldIdx, forwardValue, backwardValue, bothValue, QgsVectorLayerDirector::DirectionBoth );
316 QgsGraphBuilder builder( networkSource->sourceCrs(), true, tolerance, context.ellipsoid() );
317
318 QVector<QgsPointXY> snappedPoints;
319 director.makeGraph( &builder, {}, snappedPoints, &multiFeedback );
320
321 std::unique_ptr<QgsGraph> graph( builder.takeGraph() );
322
323 if ( multiFeedback.isCanceled() )
324 return outputs;
325
326 multiFeedback.pushInfo( QObject::tr( "Indexing graph nodes and edges…" ) );
327 multiFeedback.setCurrentStep( 2 );
328
329 // better index choice for point node index -- we satisfy the requirements
330 // of point geometries only, finalized once before reading
331 QgsSpatialIndexKDBush nodeIndex;
332 // standard QgsSpatialIndex for edges -- we can't use the faster KDBush index for these, as that is point only
334
335 const int vertexCount = graph->vertexCount();
336
337 const long long totalGraphElements = ( checkNodeToNodeDistance ? vertexCount : 0 ) + ( checkNodeToSegmentDistance ? graph->edgeCount() : 0 );
338 const double indexStep = totalGraphElements > 0 ? 100.0 / static_cast< double >( totalGraphElements ) : 1;
339 long long elementsProcessed = 0;
340
341 if ( checkNodeToNodeDistance )
342 {
343 for ( int i = 0; i < vertexCount; ++i )
344 {
345 if ( multiFeedback.isCanceled() )
346 break;
347 nodeIndex.addFeature( i, graph->vertex( i ).point() );
348 elementsProcessed++;
349 multiFeedback.setProgress( static_cast< double >( elementsProcessed ) * indexStep );
350 }
351 nodeIndex.finalize();
352 }
353
354 if ( checkNodeToSegmentDistance )
355 {
356 for ( int i = 0; i < graph->edgeCount(); ++i )
357 {
358 if ( multiFeedback.isCanceled() )
359 break;
360
361 const QgsGraphEdge &edge = graph->edge( i );
362 const QgsPointXY p1 = graph->vertex( edge.fromVertex() ).point();
363 const QgsPointXY p2 = graph->vertex( edge.toVertex() ).point();
364
365 edgeIndex.addFeature( i, QgsRectangle( p1, p2 ) );
366 elementsProcessed++;
367 multiFeedback.setProgress( static_cast< double >( elementsProcessed ) * indexStep );
368 }
369 }
370
371 // perform topology checks
372 multiFeedback.pushInfo( QObject::tr( "Validating graph topology…" ) );
373 multiFeedback.setCurrentStep( 2 );
374
375 const double topoStep = vertexCount > 0 ? 100.0 / vertexCount : 1;
376
377 struct NodeError
378 {
379 long long id = 0;
380 QgsPointXY pt;
381 double distance = std::numeric_limits<double>::max();
382 };
383
384 QSet< QPair< long long, long long > > alreadyReportedNodes;
385 long long countInvalidNodes = 0;
386
387 for ( long long i = 0; i < vertexCount; ++i )
388 {
389 if ( multiFeedback.isCanceled() )
390 break;
391
392 const QgsGraphVertex &v = graph->vertex( i );
393 const QgsPointXY &pt = v.point();
394
395 // whether we need to perform validation on this node
396 bool evaluateNode = true;
397
398 if ( checkEndpointsOnly )
399 {
400 // count unique neighbors to handle bidirectional segments (A->B and B->A) counting as one connection
401 QSet<int> adjacentNodeIndices;
402 for ( int edgeId : v.outgoingEdges() )
403 {
404 adjacentNodeIndices.insert( graph->edge( edgeId ).toVertex() );
405 }
406 for ( int edgeId : v.incomingEdges() )
407 {
408 adjacentNodeIndices.insert( graph->edge( edgeId ).fromVertex() );
409 }
410 if ( adjacentNodeIndices.count() != 1 )
411 {
412 evaluateNode = false;
413 }
414 }
415
416 if ( evaluateNode && checkNodeToNodeDistance )
417 {
418 const std::vector< QgsVectorLayerDirector::VertexSourceInfo > &fidsFirstNode = director.sourcesForVertex( i );
419
420 const QList<QgsSpatialIndexKDBushData> candidates = nodeIndex.intersects( QgsRectangle::fromCenterAndSize( pt, toleranceNodeToNode * 2, toleranceNodeToNode * 2 ) );
421
422 // only keep the closest violation
423 NodeError closestError;
424 for ( const QgsSpatialIndexKDBushData &data : candidates )
425 {
426 // skip self
427 if ( data.id == i )
428 continue;
429
430 // ignore nodes which are directly connected to each other
431 bool skip = false;
432 for ( const int edge : v.incomingEdges() )
433 {
434 if ( graph->edge( edge ).fromVertex() == i )
435 {
436 skip = true;
437 break;
438 }
439 }
440 if ( skip )
441 continue;
442 for ( const int edge : v.outgoingEdges() )
443 {
444 if ( graph->edge( edge ).toVertex() == data.id )
445 {
446 skip = true;
447 break;
448 }
449 }
450 if ( skip )
451 continue;
452
453 const std::vector<QgsVectorLayerDirector::VertexSourceInfo> &fidsSecondNode = director.sourcesForVertex( data.id );
454
455 bool shareCommonFeature = false;
456 for ( const QgsVectorLayerDirector::VertexSourceInfo &info1 : fidsFirstNode )
457 {
458 for ( const QgsVectorLayerDirector::VertexSourceInfo &info2 : fidsSecondNode )
459 {
460 if ( info1 == info2 )
461 {
462 shareCommonFeature = true;
463 break;
464 }
465 }
466 if ( shareCommonFeature )
467 break;
468 }
469
470 if ( shareCommonFeature )
471 {
472 // if there is a common feature joining these nodes, then don't consider them as invalid
473 continue;
474 }
475
476 const double distanceNodeToNode = pt.distance( data.point() );
477 if ( distanceNodeToNode < toleranceNodeToNode && distanceNodeToNode < closestError.distance )
478 {
479 closestError.distance = distanceNodeToNode;
480 closestError.id = data.id;
481 closestError.pt = data.point();
482 }
483 }
484
485 if ( !closestError.pt.isEmpty() )
486 {
487 const QPair< long long, long long > nodeId = qMakePair( std::min( closestError.id, i ), std::max( closestError.id, i ) );
488 if ( alreadyReportedNodes.contains( nodeId ) )
489 {
490 // already reported this -- eg when checking the other node in the pair
491 continue;
492 }
493 alreadyReportedNodes.insert( nodeId );
494
495 if ( nodeErrorSink )
496 {
497 QgsFeature nodeErrorFeature( nodeErrorFields );
498 nodeErrorFeature.setGeometry( std::make_unique< QgsLineString >( QVector<QgsPointXY>() << pt << closestError.pt ) );
499 nodeErrorFeature.setAttributes( QgsAttributes() << QObject::tr( "Node too close to adjacent node (%1 < %2)" ).arg( closestError.distance ).arg( toleranceNodeToNode ) );
500 if ( !nodeErrorSink->addFeature( nodeErrorFeature, QgsFeatureSink::FastInsert ) )
501 throw QgsProcessingException( writeFeatureError( nodeErrorSink.get(), parameters, u"OUTPUT_INVALID_NODES"_s ) );
502 else
503 feedback->featureAddedToSink( u"OUTPUT_INVALID_NODES"_s );
504 }
505 countInvalidNodes++;
506 }
507 }
508
509 if ( evaluateNode && checkNodeToSegmentDistance )
510 {
511 // only keep the closest violation
512 NodeError closestError;
513
514 const QList<QgsFeatureId> edgeIds = edgeIndex.intersects( QgsRectangle::fromCenterAndSize( pt, toleranceNodeToSegment * 2, toleranceNodeToSegment * 2 ) );
515 for ( QgsFeatureId edgeIdx : edgeIds )
516 {
517 const QgsGraphEdge &edge = graph->edge( static_cast< int >( edgeIdx ) );
518 // skip edges connected to this node
519 if ( edge.fromVertex() == i || edge.toVertex() == i )
520 continue;
521
522 const QgsPointXY p1 = graph->vertex( edge.fromVertex() ).point();
523 const QgsPointXY p2 = graph->vertex( edge.toVertex() ).point();
524
525 QgsPointXY closestPt;
526 const double distanceToSegment = std::sqrt( pt.sqrDistToSegment( p1.x(), p1.y(), p2.x(), p2.y(), closestPt ) );
527 if ( distanceToSegment >= toleranceNodeToSegment )
528 continue;
529
530 // we don't consider this a node-to-segment error if the closest point is actually one of the segment endpoints.
531 // in that case it's a node-to-NODE error.
532 if ( closestPt.compare( p1 ) || closestPt.compare( p2 ) )
533 {
534 continue;
535 }
536
537 if ( distanceToSegment > closestError.distance )
538 {
539 continue;
540 }
541 closestError.distance = distanceToSegment;
542 closestError.pt = closestPt;
543 }
544
545 if ( !closestError.pt.isEmpty() )
546 {
547 if ( nodeErrorSink )
548 {
549 QgsFeature nodeErrorFeature( nodeErrorFields );
550 nodeErrorFeature.setGeometry( std::make_unique< QgsLineString >( QVector<QgsPointXY>() << pt << closestError.pt ) );
551 nodeErrorFeature.setAttributes( QgsAttributes() << QObject::tr( "Node too close to non-noded segment (%1 < %2)" ).arg( closestError.distance ).arg( toleranceNodeToSegment ) );
552 if ( !nodeErrorSink->addFeature( nodeErrorFeature, QgsFeatureSink::FastInsert ) )
553 throw QgsProcessingException( writeFeatureError( nodeErrorSink.get(), parameters, u"OUTPUT_INVALID_NODES"_s ) );
554 else
555 feedback->featureAddedToSink( u"OUTPUT_INVALID_NODES"_s );
556 }
557 countInvalidNodes++;
558 }
559 }
560
561 multiFeedback.setProgress( static_cast< double >( i ) * topoStep );
562 }
563
564 if ( nodeErrorSink )
565 {
566 nodeErrorSink->finalize();
567 feedback->featureSinkFinalized( u"OUTPUT_INVALID_NODES"_s );
568 }
569
570 feedback->setProgress( 100 );
571 if ( countInvalidNodes > 0 )
572 {
573 multiFeedback.reportError( QObject::tr( "Found %1 invalid network nodes" ).arg( countInvalidNodes ) );
574 }
575
576 outputs.insert( u"COUNT_INVALID_NODES"_s, countInvalidNodes );
577
578 return outputs;
579}
580
@ VectorLine
Vector line layers.
Definition qgis.h:3716
@ LineString
LineString.
Definition qgis.h:297
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3947
@ Optional
Parameter is optional.
Definition qgis.h:3949
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:46
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.