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.
This commit is contained in:
Nyall Dawson 2017-12-03 07:10:42 +10:00
parent d0e927a84f
commit 928afdd8c5
5 changed files with 98 additions and 5 deletions

View File

@ -28,6 +28,8 @@ class QgsGeometrySnapper : QObject
{
PreferNodes,
PreferClosest,
PreferNodesNoExtraVertices,
PreferClosestNoExtraVertices,
EndPointPreferNodes,
EndPointPreferClosest,
EndPointToEndPoint,

View File

@ -61,8 +61,10 @@ class SnapGeometriesToLayer(QgisAlgorithm):
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')]

View File

@ -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

View File

@ -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

View File

@ -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"