Skip to content
Permalink
Browse files
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.
  • Loading branch information
nyalldawson committed Dec 2, 2017
1 parent d0e927a commit 928afdd
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 5 deletions.
@@ -28,6 +28,8 @@ class QgsGeometrySnapper : QObject
{
PreferNodes,
PreferClosest,
PreferNodesNoExtraVertices,
PreferClosestNoExtraVertices,
EndPointPreferNodes,
EndPointPreferClosest,
EndPointToEndPoint,
@@ -61,8 +61,10 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double,
minValue=0.00000001, maxValue=9999999999, defaultValue=10.0))

self.modes = [self.tr('Prefer aligning nodes'),
self.tr('Prefer closest point'),
self.modes = [self.tr('Prefer aligning nodes, insert extra vertices where required'),
self.tr('Prefer closest point, insert extra vertices where required'),
self.tr('Prefer aligning nodes, don\'t insert new vertices'),
self.tr('Prefer closest point, don\'t insert new vertices'),
self.tr('Move end points only, prefer aligning nodes'),
self.tr('Move end points only, prefer closest point'),
self.tr('Snap end points to end points only')]
@@ -550,6 +550,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
switch ( mode )
{
case PreferNodes:
case PreferNodesNoExtraVertices:
case EndPointPreferNodes:
case EndPointToEndPoint:
{
@@ -568,6 +569,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
}

case PreferClosest:
case PreferClosestNoExtraVertices:
case EndPointPreferClosest:
{
QgsPoint nodeSnap, segmentSnap;
@@ -605,7 +607,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
return QgsGeometry( subjGeom );
//or for end point snapping
if ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
if ( mode == PreferClosestNoExtraVertices || mode == PreferNodesNoExtraVertices || mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
return QgsGeometry( subjGeom );

// SnapIndex for subject feature
@@ -45,8 +45,10 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
//! Snapping modes
enum SnapMode
{
PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment
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.
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.
PreferNodesNoExtraVertices, //!< Prefer to snap to nodes, even when a segment may be closer than a node. No new nodes will be inserted.
PreferClosestNoExtraVertices, //!< Snap to closest point, regardless of it is a node or a segment. No new nodes will be inserted.
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
EndPointPreferClosest, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), snap to closest point
EndPointToEndPoint, //!< Only snap the start/end points of lines to other start/end points of lines
@@ -48,6 +48,7 @@ class TestQgsGeometrySnapper : public QObject
void endPointSnap();
void endPointToEndPoint();
void internalSnapper();
void insertExtra();
};

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

void TestQgsGeometrySnapper::insertExtra()
{
// test extra node insertion behaviour
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)" ) );
QgsFeature f1( 1 );
f1.setGeometry( refGeom );

// inserting extra nodes
QgsInternalGeometrySnapper snapper( 2, QgsGeometrySnapper::PreferNodes );
QgsGeometry result = snapper.snapFeature( f1 );
QCOMPARE( result.asWkt(), f1.geometry().asWkt() );

refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0, 10 5)" ) );
QgsFeature f2( 2 );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 9.9 0, 10 0, 10.1 0, 10 5)" ) );

// reset snapper
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodes );
result = snapper.snapFeature( f1 );

refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
// should 'follow' line for a bit
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 9.8 0, 9.9 0, 10 0)" ) );

// using PreferNodesNoExtraVertices mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 10 5)" ) );

snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );

// using PreferClosestNoExtraVertices mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9 0, 10 5)" ) );

snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );

// using EndPointPreferNodes mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferNodes );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );

// using EndPointPreferClosest mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferClosest );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );

// using EndPointToEndPoint mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointToEndPoint );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(-7 -2, 0.12 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (-7 -2, 0 0)" ) );

}


QGSTEST_MAIN( TestQgsGeometrySnapper )
#include "testqgsgeometrysnapper.moc"

0 comments on commit 928afdd

Please sign in to comment.