Skip to content

Commit 928afdd

Browse files
committedDec 2, 2017
Fix geometry snapper sometimes creates unwanted overlapping segments
when snapping line layers Because the default behavior of the snapper is to insert extra vertices into the snapped geometry in order to make it 'follow' the reference geometries exactly, this can result in unwanted results for line layers where the resultant snapped layer has overlapping line segments. Since we can't always know what the desired result is that the user wants (maybe they do want overlapping lines), instead give them control over the result by exposing extra enum options which never insert extra vertices.
1 parent d0e927a commit 928afdd

File tree

5 files changed

+98
-5
lines changed

5 files changed

+98
-5
lines changed
 

‎python/analysis/vector/qgsgeometrysnapper.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class QgsGeometrySnapper : QObject
2828
{
2929
PreferNodes,
3030
PreferClosest,
31+
PreferNodesNoExtraVertices,
32+
PreferClosestNoExtraVertices,
3133
EndPointPreferNodes,
3234
EndPointPreferClosest,
3335
EndPointToEndPoint,

‎python/plugins/processing/algs/qgis/SnapGeometries.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ def initAlgorithm(self, config=None):
6161
self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double,
6262
minValue=0.00000001, maxValue=9999999999, defaultValue=10.0))
6363

64-
self.modes = [self.tr('Prefer aligning nodes'),
65-
self.tr('Prefer closest point'),
64+
self.modes = [self.tr('Prefer aligning nodes, insert extra vertices where required'),
65+
self.tr('Prefer closest point, insert extra vertices where required'),
66+
self.tr('Prefer aligning nodes, don\'t insert new vertices'),
67+
self.tr('Prefer closest point, don\'t insert new vertices'),
6668
self.tr('Move end points only, prefer aligning nodes'),
6769
self.tr('Move end points only, prefer closest point'),
6870
self.tr('Snap end points to end points only')]

‎src/analysis/vector/qgsgeometrysnapper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
550550
switch ( mode )
551551
{
552552
case PreferNodes:
553+
case PreferNodesNoExtraVertices:
553554
case EndPointPreferNodes:
554555
case EndPointToEndPoint:
555556
{
@@ -568,6 +569,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
568569
}
569570

570571
case PreferClosest:
572+
case PreferClosestNoExtraVertices:
571573
case EndPointPreferClosest:
572574
{
573575
QgsPoint nodeSnap, segmentSnap;
@@ -605,7 +607,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
605607
if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
606608
return QgsGeometry( subjGeom );
607609
//or for end point snapping
608-
if ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
610+
if ( mode == PreferClosestNoExtraVertices || mode == PreferNodesNoExtraVertices || mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
609611
return QgsGeometry( subjGeom );
610612

611613
// SnapIndex for subject feature

‎src/analysis/vector/qgsgeometrysnapper.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
4545
//! Snapping modes
4646
enum SnapMode
4747
{
48-
PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node
49-
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment
48+
PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node. New nodes will be inserted to make geometries follow each other exactly when inside allowable tolerance.
49+
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment. New nodes will be inserted to make geometries follow each other exactly when inside allowable tolerance.
50+
PreferNodesNoExtraVertices, //!< Prefer to snap to nodes, even when a segment may be closer than a node. No new nodes will be inserted.
51+
PreferClosestNoExtraVertices, //!< Snap to closest point, regardless of it is a node or a segment. No new nodes will be inserted.
5052
EndPointPreferNodes, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), prefer to snap to nodes
5153
EndPointPreferClosest, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), snap to closest point
5254
EndPointToEndPoint, //!< Only snap the start/end points of lines to other start/end points of lines

‎tests/src/analysis/testqgsgeometrysnapper.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class TestQgsGeometrySnapper : public QObject
4848
void endPointSnap();
4949
void endPointToEndPoint();
5050
void internalSnapper();
51+
void insertExtra();
5152
};
5253

5354
void TestQgsGeometrySnapper::initTestCase()
@@ -499,6 +500,90 @@ void TestQgsGeometrySnapper::internalSnapper()
499500
QCOMPARE( res.value( 4 ).asWkt(), QStringLiteral( "LineString (0 0, 5 5, 10 10, 15 15)" ) );
500501
}
501502

503+
void TestQgsGeometrySnapper::insertExtra()
504+
{
505+
// test extra node insertion behaviour
506+
QgsGeometry refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(0 0, 0.1 0, 0.2 0, 9.8 0, 9.9 0, 10 0, 10.1 0, 10.2 0, 20 0)" ) );
507+
QgsFeature f1( 1 );
508+
f1.setGeometry( refGeom );
509+
510+
// inserting extra nodes
511+
QgsInternalGeometrySnapper snapper( 2, QgsGeometrySnapper::PreferNodes );
512+
QgsGeometry result = snapper.snapFeature( f1 );
513+
QCOMPARE( result.asWkt(), f1.geometry().asWkt() );
514+
515+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0, 10 5)" ) );
516+
QgsFeature f2( 2 );
517+
f2.setGeometry( refGeom );
518+
result = snapper.snapFeature( f2 );
519+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 9.9 0, 10 0, 10.1 0, 10 5)" ) );
520+
521+
// reset snapper
522+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodes );
523+
result = snapper.snapFeature( f1 );
524+
525+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10 0)" ) );
526+
f2.setGeometry( refGeom );
527+
result = snapper.snapFeature( f2 );
528+
// should 'follow' line for a bit
529+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 9.8 0, 9.9 0, 10 0)" ) );
530+
531+
// using PreferNodesNoExtraVertices mode, no extra vertices should be inserted
532+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
533+
result = snapper.snapFeature( f1 );
534+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
535+
f2.setGeometry( refGeom );
536+
result = snapper.snapFeature( f2 );
537+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 10 5)" ) );
538+
539+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
540+
result = snapper.snapFeature( f1 );
541+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
542+
f2.setGeometry( refGeom );
543+
result = snapper.snapFeature( f2 );
544+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );
545+
546+
// using PreferClosestNoExtraVertices mode, no extra vertices should be inserted
547+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
548+
result = snapper.snapFeature( f1 );
549+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
550+
f2.setGeometry( refGeom );
551+
result = snapper.snapFeature( f2 );
552+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9 0, 10 5)" ) );
553+
554+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
555+
result = snapper.snapFeature( f1 );
556+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
557+
f2.setGeometry( refGeom );
558+
result = snapper.snapFeature( f2 );
559+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );
560+
561+
// using EndPointPreferNodes mode, no extra vertices should be inserted
562+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferNodes );
563+
result = snapper.snapFeature( f1 );
564+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
565+
f2.setGeometry( refGeom );
566+
result = snapper.snapFeature( f2 );
567+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );
568+
569+
// using EndPointPreferClosest mode, no extra vertices should be inserted
570+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferClosest );
571+
result = snapper.snapFeature( f1 );
572+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
573+
f2.setGeometry( refGeom );
574+
result = snapper.snapFeature( f2 );
575+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );
576+
577+
// using EndPointToEndPoint mode, no extra vertices should be inserted
578+
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointToEndPoint );
579+
result = snapper.snapFeature( f1 );
580+
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(-7 -2, 0.12 0)" ) );
581+
f2.setGeometry( refGeom );
582+
result = snapper.snapFeature( f2 );
583+
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (-7 -2, 0 0)" ) );
584+
585+
}
586+
502587

503588
QGSTEST_MAIN( TestQgsGeometrySnapper )
504589
#include "testqgsgeometrysnapper.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.