Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #41658 from lbartoletti/vertextool_fix_avoidInters…
…ections

[VertexTool] fixes avoid intersections
  • Loading branch information
3nids committed May 25, 2021
2 parents a13c655 + 1036732 commit c0e15c0
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 7 deletions.
83 changes: 76 additions & 7 deletions src/app/vertextool/qgsvertextool.cpp
Expand Up @@ -2176,20 +2176,35 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato


applyEditsToLayers( edits ); applyEditsToLayers( edits );


if ( QgsProject::instance()->topologicalEditing() && ( mapPointMatch->hasEdge() || mapPointMatch->hasMiddleSegment() ) && mapPointMatch->layer() ) if ( QgsProject::instance()->topologicalEditing() )
{ {
// topo editing: add vertex to existing segments when moving/adding a vertex to such segment. // topo editing: add vertex to existing segments when moving/adding a vertex to such segment.
// this requires that the snapping match is to a segment and the segment layer's CRS // this requires that the snapping match is to a segment and the segment layer's CRS
// is the same (otherwise we would need to reproject the point and it will not be coincident) // is the same (otherwise we would need to reproject the point and it will not be coincident)
const auto editKeys = edits.keys(); const auto editKeys = edits.keys();
for ( QgsVectorLayer *layer : editKeys ) for ( QgsVectorLayer *layer : editKeys )
{ {
if ( layer->crs() == mapPointMatch->layer()->crs() ) const auto editGeom = edits[layer].values();
for ( QgsGeometry g : editGeom )
{ {
if ( !layerPoint.is3D() ) QgsGeometry p = QgsGeometry::fromPointXY( QgsPointXY( layerPoint.x(), layerPoint.y() ) );
layerPoint.addZValue( defaultZValue() ); if ( ( mapPointMatch->hasEdge() || mapPointMatch->hasMiddleSegment() ) && mapPointMatch->layer() && ( layer->crs() == mapPointMatch->layer()->crs() ) )
layer->addTopologicalPoints( layerPoint ); {
mapPointMatch->layer()->addTopologicalPoints( layerPoint ); if ( g.convertToType( QgsWkbTypes::PointGeometry, true ).contains( p ) )
{
if ( !layerPoint.is3D() )
layerPoint.addZValue( defaultZValue() );
layer->addTopologicalPoints( layerPoint );
mapPointMatch->layer()->addTopologicalPoints( layerPoint );
}
}
if ( QgsProject::instance()->avoidIntersectionsMode() != QgsProject::AvoidIntersectionsMode::AllowIntersections )
{
for ( QgsAbstractGeometry::vertex_iterator it = g.vertices_begin() ; it != g.vertices_end() ; it++ )
{
layer->addTopologicalPoints( *it );
}
}
} }
} }
} }
Expand Down Expand Up @@ -2297,14 +2312,68 @@ void QgsVertexTool::applyEditsToLayers( QgsVertexTool::VertexEdits &edits )
QHash<QgsFeatureId, QgsGeometry>::iterator it2 = layerEdits.begin(); QHash<QgsFeatureId, QgsGeometry>::iterator it2 = layerEdits.begin();
for ( ; it2 != layerEdits.end(); ++it2 ) for ( ; it2 != layerEdits.end(); ++it2 )
{ {
layer->changeGeometry( it2.key(), it2.value() ); QgsGeometry featGeom = it2.value();
layer->changeGeometry( it2.key(), featGeom );
edits[layer][it2.key()] = featGeom;
}

if ( mVertexEditor )
mVertexEditor->updateEditor( mLockedFeature.get() );
}



for ( it = edits.begin() ; it != edits.end(); ++it )
{
QgsVectorLayer *layer = it.key();
QHash<QgsFeatureId, QgsGeometry> &layerEdits = it.value();
QHash<QgsFeatureId, QgsGeometry>::iterator it2 = layerEdits.begin();
for ( ; it2 != layerEdits.end(); ++it2 )
{
QList<QgsVectorLayer *> avoidIntersectionsLayers;
switch ( QgsProject::instance()->avoidIntersectionsMode() )
{
case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
avoidIntersectionsLayers.append( layer );
break;
case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
break;
case QgsProject::AvoidIntersectionsMode::AllowIntersections:
break;
}
QgsGeometry featGeom = it2.value();
layer->changeGeometry( it2.key(), featGeom );
if ( avoidIntersectionsLayers.size() > 0 )
{
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
QSet<QgsFeatureId> id;
id.insert( it2.key() );
ignoreFeatures.insert( layer, id );
int avoidIntersectionsReturn = featGeom.avoidIntersections( avoidIntersectionsLayers, ignoreFeatures );
switch ( avoidIntersectionsReturn )
{
case 2:
emit messageEmitted( tr( "The operation would change the geometry type." ), Qgis::Warning );
break;

case 3:
emit messageEmitted( tr( "At least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::Warning );
break;
default:
break;
}
}
layer->changeGeometry( it2.key(), featGeom );
edits[layer][it2.key()] = featGeom;
} }
layer->endEditCommand(); layer->endEditCommand();
layer->triggerRepaint(); layer->triggerRepaint();


if ( mVertexEditor ) if ( mVertexEditor )
mVertexEditor->updateEditor( mLockedFeature.get() ); mVertexEditor->updateEditor( mLockedFeature.get() );
} }

} }




Expand Down
99 changes: 99 additions & 0 deletions tests/src/app/testqgsvertextool.cpp
Expand Up @@ -17,6 +17,7 @@


#include "qgsadvanceddigitizingdockwidget.h" #include "qgsadvanceddigitizingdockwidget.h"
#include "qgsgeometry.h" #include "qgsgeometry.h"
#include "qgsgeos.h"
#include "qgsmapcanvas.h" #include "qgsmapcanvas.h"
#include "qgsmapcanvassnappingutils.h" #include "qgsmapcanvassnappingutils.h"
#include "qgsproject.h" #include "qgsproject.h"
Expand Down Expand Up @@ -70,6 +71,7 @@ class TestQgsVertexTool : public QObject
void testAddVertexAtEndpoint(); void testAddVertexAtEndpoint();
void testAddVertexDoubleClick(); void testAddVertexDoubleClick();
void testAddVertexDoubleClickWithShift(); void testAddVertexDoubleClickWithShift();
void testAvoidIntersections();
void testDeleteVertex(); void testDeleteVertex();
void testMoveMultipleVertices(); void testMoveMultipleVertices();
void testMoveMultipleVertices2(); void testMoveMultipleVertices2();
Expand Down Expand Up @@ -1010,6 +1012,103 @@ void TestQgsVertexTool::testAddVertexTopoFirstSegment()
QgsProject::instance()->setTopologicalEditing( false ); QgsProject::instance()->setTopologicalEditing( false );
} }


void TestQgsVertexTool::testAvoidIntersections()
{
// check that when adding a vertex to the first segment of a polygon's ring with topo editing
// enabled, the geometry does not get corrupted (#20774)

QgsProject::instance()->setTopologicalEditing( true );
QgsProject::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() );
QgsProject::instance()->setAvoidIntersectionsMode( QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer );

QgsPolygonXY polygon2;
QgsPolylineXY polygon2exterior;
polygon2exterior << QgsPointXY( 8, 2 ) << QgsPointXY( 9, 2 ) << QgsPointXY( 9, 3 ) << QgsPointXY( 8, 3 ) << QgsPointXY( 8, 2 );
polygon2 << polygon2exterior;
QgsFeature polygonF2;
polygonF2.setGeometry( QgsGeometry::fromPolygonXY( polygon2 ) );

mLayerPolygon->addFeature( polygonF2 );
QgsFeatureId mFidPolygonF2 = polygonF2.id();
QCOMPARE( mLayerPolygon->featureCount(), ( long )2 );

mouseClick( 7, 1, Qt::LeftButton );
mouseClick( 9, 2, Qt::LeftButton );

QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );

QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((4 4, 7 4, 8 3, 8 2, 9 2, 4 1, 4 4))" ) ); // avoid rounding errors
QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF2 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((8 2, 9 2, 9 3, 8 3, 8 2))" ) );

mLayerPolygon->undoStack()->undo();

QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );

// Move polygons and check that geometry are not avoided
// select polygons
mousePress( 3, 5, Qt::LeftButton );
mouseMove( 9.5, 0.5 );
mouseRelease( 9.5, 0.5, Qt::LeftButton );

// move polygons
mouseClick( 8, 2, Qt::LeftButton );
mouseClick( 5, 2, Qt::LeftButton );

QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((1 1, 4 1, 4 4, 1 4, 1 1))" ) );
QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF2 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((5 2, 6 2, 6 3, 5 3, 5 2))" ) );

mLayerPolygon->undoStack()->undo();
mLayerPolygon->undoStack()->undo(); // delete feature

QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );

// If topologicalEditing and avoidIntersections are activated we must take care that the topological points are well added.
QgsPolygonXY polygon_topo1;
QgsPolylineXY polygon_topo1exterior;
polygon_topo1exterior << QgsPointXY( 0, 10 ) << QgsPointXY( 0, 20 ) << QgsPointXY( 5, 15 ) << QgsPointXY( 0, 10 );
polygon_topo1 << polygon_topo1exterior;
QgsFeature polygonF_topo1;
polygonF_topo1.setGeometry( QgsGeometry::fromPolygonXY( polygon_topo1 ) );

mLayerPolygon->addFeature( polygonF_topo1 );
QgsFeatureId mFidPolygonF_topo1 = polygonF_topo1.id();

QgsPolygonXY polygon_topo2;
QgsPolylineXY polygon_topo2exterior;
polygon_topo2exterior << QgsPointXY( 10, 15 ) << QgsPointXY( 15, 10 ) << QgsPointXY( 15, 20 ) << QgsPointXY( 10, 15 );
polygon_topo2 << polygon_topo2exterior;
QgsFeature polygonF_topo2;
polygonF_topo2.setGeometry( QgsGeometry::fromPolygonXY( polygon_topo2 ) );

mLayerPolygon->addFeature( polygonF_topo2 );
QgsFeatureId mFidPolygonF_topo2 = polygonF_topo2.id();

QCOMPARE( mLayerPolygon->featureCount(), ( long )3 );

mouseClick( 5, 15, Qt::LeftButton );
mouseClick( 12.5, 15, Qt::LeftButton );

if ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 9 )
{
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo1 ).geometry().asWkt( 1 ), "Polygon ((0 10, 0 20, 10.7 15.7, 10 15, 10.7 14.3, 0 10))" );
}
else
{
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo1 ).geometry().asWkt( 1 ), "Polygon ((0 20, 10.7 15.7, 10 15, 10.7 14.3, 0 10, 0 20))" );
}
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo2 ).geometry().asWkt( 1 ), "Polygon ((10 15, 10.7 14.3, 15 10, 15 20, 10.7 15.7, 10 15))" );

mLayerPolygon->undoStack()->undo(); // undo topological points
mLayerPolygon->undoStack()->undo(); // undo move
mLayerPolygon->undoStack()->undo(); // delete feature polygonF_topo2
mLayerPolygon->undoStack()->undo(); // delete feature polygonF_topo1
QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );


QgsProject::instance()->setTopologicalEditing( false );
QgsProject::instance()->setAvoidIntersectionsMode( mode );
}
void TestQgsVertexTool::testActiveLayerPriority() void TestQgsVertexTool::testActiveLayerPriority()
{ {
// check that features from current layer get priority when picking points // check that features from current layer get priority when picking points
Expand Down

0 comments on commit c0e15c0

Please sign in to comment.