Skip to content

Commit c3d5b5e

Browse files
authored
fix(vertextool): topo edition when map CRS != layer CRS (qgis#58352)
1 parent ac976be commit c3d5b5e

File tree

2 files changed

+66
-46
lines changed

2 files changed

+66
-46
lines changed

src/app/vertextool/qgsvertextool.cpp

+23-45
Original file line numberDiff line numberDiff line change
@@ -164,55 +164,33 @@ class OneFeatureFilter : public QgsPointLocator::MatchFilter
164164
};
165165

166166

167-
//! a filter just to gather all matches at the same place
167+
/**
168+
* For polygons we need to filter out the last vertex, because it will have
169+
* the same coordinates than the first and we would have a duplicate match which
170+
* would make topology editing mode behave incorrectly
171+
*/
168172
class MatchCollectingFilter : public QgsPointLocator::MatchFilter
169173
{
170174
public:
171-
QList<QgsPointLocator::Match> matches;
172175
QgsVertexTool *vertextool = nullptr;
173176

174177
MatchCollectingFilter( QgsVertexTool *vertextool )
175178
: vertextool( vertextool ) {}
176179

177180
bool acceptMatch( const QgsPointLocator::Match &match ) override
178181
{
179-
if ( match.distance() > 0 )
180-
return false;
182+
if ( match.layer()->geometryType() != Qgis::GeometryType::Polygon )
183+
return true;
181184

182-
// there may be multiple points at the same location, but we get only one
183-
// result... the locator API needs a new method verticesInRect()
184-
QgsGeometry matchGeom = vertextool->cachedGeometry( match.layer(), match.featureId() );
185-
bool isPolygon = matchGeom.type() == Qgis::GeometryType::Polygon;
186-
QgsVertexId polygonRingVid;
187185
QgsVertexId vid;
188-
QgsPoint pt;
189-
while ( matchGeom.constGet()->nextVertex( vid, pt ) )
190-
{
191-
int vindex = matchGeom.vertexNrFromVertexId( vid );
192-
if ( pt.x() == match.point().x() && pt.y() == match.point().y() )
193-
{
194-
if ( isPolygon )
195-
{
196-
// for polygons we need to handle the case where the first vertex is matching because the
197-
// last point will have the same coordinates and we would have a duplicate match which
198-
// would make subsequent code behave incorrectly (topology editing mode would add a single
199-
// vertex twice)
200-
if ( vid.vertex == 0 )
201-
{
202-
polygonRingVid = vid;
203-
}
204-
else if ( vid.ringEqual( polygonRingVid ) && vid.vertex == matchGeom.constGet()->vertexCount( vid.part, vid.ring ) - 1 )
205-
{
206-
continue;
207-
}
208-
}
186+
QgsGeometry matchGeom = vertextool->cachedGeometry( match.layer(), match.featureId() );
187+
if ( !matchGeom.vertexIdFromVertexNr( match.vertexIndex(), vid ) )
188+
// should not happen because vertex index in match object was created with vertexNrFromVertexId
189+
// so the methods are reversible and we will have a vid
190+
return false;
209191

210-
QgsPointLocator::Match extra_match( match.type(), match.layer(), match.featureId(),
211-
0, match.point(), vindex );
212-
matches.append( extra_match );
213-
}
214-
}
215-
return true;
192+
// filter out the vertex if it is the last one (of its ring, in its part)
193+
return vid.vertex != matchGeom.constGet()->vertexCount( vid.part, vid.ring ) - 1;
216194
}
217195
};
218196

@@ -1013,7 +991,7 @@ QgsPointLocator::Match QgsVertexTool::snapToPolygonInterior( QgsMapMouseEvent *e
1013991
}
1014992

1015993

1016-
QList<QgsPointLocator::Match> QgsVertexTool::findEditableLayerMatches( const QgsPointXY &mapPoint, QgsVectorLayer *layer )
994+
QgsPointLocator::MatchList QgsVertexTool::findEditableLayerMatches( const QgsPointXY &mapPoint, QgsVectorLayer *layer )
1017995
{
1018996
QgsPointLocator::MatchList matchList;
1019997

@@ -1936,29 +1914,29 @@ void QgsVertexTool::buildDragBandsForVertices( const QSet<Vertex> &movingVertice
19361914
}
19371915
}
19381916

1939-
QList<QgsPointLocator::Match> QgsVertexTool::layerVerticesSnappedToPoint( QgsVectorLayer *layer, const QgsPointXY &mapPoint )
1917+
QgsPointLocator::MatchList QgsVertexTool::layerVerticesSnappedToPoint( QgsVectorLayer *layer, const QgsPointXY &mapPoint )
19401918
{
19411919
MatchCollectingFilter myfilter( this );
19421920
QgsPointLocator *loc = canvas()->snappingUtils()->locatorForLayer( layer );
1943-
loc->nearestVertex( mapPoint, 0, &myfilter, true );
1944-
return myfilter.matches;
1921+
double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
1922+
return loc->verticesInRect( mapPoint, tol, &myfilter, true );
19451923
}
19461924

1947-
QList<QgsPointLocator::Match> QgsVertexTool::layerSegmentsSnappedToSegment( QgsVectorLayer *layer, const QgsPointXY &mapPoint1, const QgsPointXY &mapPoint2 )
1925+
QgsPointLocator::MatchList QgsVertexTool::layerSegmentsSnappedToSegment( QgsVectorLayer *layer, const QgsPointXY &mapPoint1, const QgsPointXY &mapPoint2 )
19481926
{
1949-
QList<QgsPointLocator::Match> finalMatches;
1927+
QgsPointLocator::MatchList finalMatches;
19501928
// we want segment matches that have exactly the same vertices as the given segment (mapPoint1, mapPoint2)
19511929
// so rather than doing nearest edge search which could return any segment within a tolerance,
19521930
// we first find matches for one endpoint and then see if there is a matching other endpoint.
1953-
const QList<QgsPointLocator::Match> matches1 = layerVerticesSnappedToPoint( layer, mapPoint1 );
1931+
const QgsPointLocator::MatchList matches1 = layerVerticesSnappedToPoint( layer, mapPoint1 );
19541932
for ( const QgsPointLocator::Match &m : matches1 )
19551933
{
19561934
QgsGeometry g = cachedGeometry( layer, m.featureId() );
19571935
int v0, v1;
19581936
g.adjacentVertices( m.vertexIndex(), v0, v1 );
1959-
if ( v0 != -1 && QgsPointXY( g.vertexAt( v0 ) ) == mapPoint2 )
1937+
if ( v0 != -1 && toMapCoordinates( layer, QgsPointXY( g.vertexAt( v0 ) ) ) == mapPoint2 )
19601938
finalMatches << QgsPointLocator::Match( QgsPointLocator::Edge, layer, m.featureId(), 0, m.point(), v0 );
1961-
else if ( v1 != -1 && QgsPointXY( g.vertexAt( v1 ) ) == mapPoint2 )
1939+
else if ( v1 != -1 && toMapCoordinates( layer, QgsPointXY( g.vertexAt( v1 ) ) ) == mapPoint2 )
19621940
finalMatches << QgsPointLocator::Match( QgsPointLocator::Edge, layer, m.featureId(), 0, m.point(), m.vertexIndex() );
19631941
}
19641942
return finalMatches;

tests/src/app/testqgsvertextool.cpp

+43-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class TestQgsVertexTool : public QObject
9292
void testActiveLayerPriority();
9393
void testSelectedFeaturesPriority();
9494
void testVertexToolCompoundCurve();
95+
void testMoveVertexTopoOtherMapCrs();
9596

9697
private:
9798
QPoint mapToScreen( double mapX, double mapY )
@@ -1614,7 +1615,6 @@ void TestQgsVertexTool::testVertexToolCompoundCurve()
16141615
mouseMove( 17, 10 );
16151616
mouseClick( 17 + offsetInMapUnits, 10, Qt::LeftButton );
16161617
mouseClick( 7, 2, Qt::LeftButton );
1617-
mouseClick( 7, 1, Qt::RightButton );
16181618

16191619
// verifying that it's not possible to add a extra vertex to a CircularString
16201620
QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
@@ -1647,5 +1647,47 @@ void TestQgsVertexTool::testSelectVerticesByPolygon()
16471647
QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 5.5, 1.25 6, 1.75 6, 1.75 5.5, 1.25 5.5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
16481648
}
16491649

1650+
void TestQgsVertexTool::testMoveVertexTopoOtherMapCrs()
1651+
{
1652+
// test moving of vertices of two features at once
1653+
1654+
QgsProject::instance()->setTopologicalEditing( true );
1655+
QgsCoordinateReferenceSystem prevCrs = QgsProject::instance()->crs();
1656+
QgsCoordinateReferenceSystem tmpCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) );
1657+
1658+
// move linestring vertex to connect with polygon at point (7, 1)
1659+
mouseClick( 2, 1, Qt::LeftButton );
1660+
mouseClick( 7, 1, Qt::LeftButton );
1661+
1662+
// change CRS so that the map canvas and the layers CRSs are different
1663+
mCanvas->setDestinationCrs( tmpCrs );
1664+
mCanvas->snappingUtils()->locatorForLayer( mLayerLine )->init();
1665+
mCanvas->snappingUtils()->locatorForLayer( mLayerPolygon )->init();
1666+
1667+
// Start point is 7, 1 in layer coordinates, to snap to the linestring and polygon vertices.
1668+
// End point is 3, 3 in layer coordinates.
1669+
// Get the map coordinates for these points to click on it.
1670+
QgsPointXY mapPointStart = mCanvas->mapSettings().layerToMapCoordinates( mLayerPolygon, QgsPointXY( 7, 1 ) );
1671+
QgsPointXY mapPointEnd = mCanvas->mapSettings().layerToMapCoordinates( mLayerPolygon, QgsPointXY( 3, 3 ) );
1672+
mouseClick( mapPointStart.x(), mapPointStart.y(), Qt::LeftButton );
1673+
mouseClick( mapPointEnd.x(), mapPointEnd.y(), Qt::LeftButton );
1674+
1675+
// polygon and line features have changed, within the CRS conversion precision
1676+
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry().asWkt( 2 ), "LineString (3 3, 1 1, 1 3)" );
1677+
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry().asWkt( 2 ), "Polygon ((4 1, 3 3, 7 4, 4 4, 4 1))" );
1678+
1679+
QCOMPARE( mLayerLine->undoStack()->index(), 3 ); // one more move of vertex from earlier
1680+
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
1681+
mLayerLine->undoStack()->undo();
1682+
mLayerLine->undoStack()->undo();
1683+
mLayerPolygon->undoStack()->undo();
1684+
1685+
// back to the original state
1686+
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
1687+
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
1688+
mCanvas->setDestinationCrs( prevCrs );
1689+
QgsProject::instance()->setTopologicalEditing( false );
1690+
}
1691+
16501692
QGSTEST_MAIN( TestQgsVertexTool )
16511693
#include "testqgsvertextool.moc"

0 commit comments

Comments
 (0)