Merge pull request #57504 from GispoCoding/fix_copy_move_overlap

Apply avoid overlap with the copy move map tool
This commit is contained in:
Julien Cabieces 2024-05-29 13:58:20 +02:00 committed by GitHub
commit 92f182568f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 142 additions and 1 deletions

View File

@ -2091,6 +2091,7 @@ QgisApp::QgisApp()
mMapTools = std::make_unique< QgsAppMapTools >( mMapCanvas, mAdvancedDigitizingDockWidget );
mDigitizingTechniqueManager = new QgsMapToolsDigitizingTechniqueManager( this );
mVectorLayerTools = new QgsGuiVectorLayerTools();
mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &QgisApp::onActiveLayerChanged );

View File

@ -18,12 +18,14 @@
#include "qgsguivectorlayertools.h"
#include "qgsavoidintersectionsoperation.h"
#include "qgisapp.h"
#include "qgsfeatureaction.h"
#include "qgsmessagebar.h"
#include "qgsmessagebaritem.h"
#include "qgsmessageviewer.h"
#include "qgsvectorlayer.h"
#include "qgsvectorlayerutils.h"
bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat, QWidget *parentWidget, bool showModal, bool hideParent ) const
{
@ -163,6 +165,51 @@ bool QgsGuiVectorLayerTools::stopEditing( QgsVectorLayer *layer, bool allowCance
return res;
}
bool QgsGuiVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureRequest &request, double dx, double dy, QString *errorMsg, const bool topologicalEditing, QgsVectorLayer *topologicalLayer, QString *childrenInfoMsg ) const
{
bool res = QgsVectorLayerTools::copyMoveFeatures( layer, request, dx, dy, errorMsg, topologicalEditing, topologicalLayer, childrenInfoMsg );
if ( res && layer->geometryType() == Qgis::GeometryType::Polygon )
{
res = avoidIntersection( layer, request, errorMsg );
}
return res;
}
bool QgsGuiVectorLayerTools::avoidIntersection( QgsVectorLayer *layer, QgsFeatureRequest &request, QString *errorMsg ) const
{
QgsAvoidIntersectionsOperation avoidIntersections;
QgsFeatureIterator fi = layer->getFeatures( request );
QgsFeature f;
const QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures {{ layer, request.filterFids() }};
while ( fi.nextFeature( f ) )
{
QgsGeometry geom = f.geometry();
const QgsFeatureId id = f.id();
const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( layer, id, geom, ignoreFeatures );
if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() )
{
if ( errorMsg )
{
*errorMsg = ( geom.isEmpty() ) ?
tr( "The feature cannot be moved because 1 or more resulting geometries would be empty" ) :
tr( "An error was reported during intersection removal" );
}
return false;
}
layer->changeGeometry( id, geom );
}
return true;
}
void QgsGuiVectorLayerTools::commitError( QgsVectorLayer *vlayer ) const
{
QgsMessageViewer *mv = new QgsMessageViewer();

View File

@ -79,8 +79,26 @@ class QgsGuiVectorLayerTools : public QgsVectorLayerTools
*/
bool saveEdits( QgsVectorLayer *layer ) const override;
/**
* Copy and move features with defined translation.
*
* \param layer The layer
* \param request The request for the features to be moved. It will be assigned to a new feature request with the newly copied features.
* \param dx The translation on x
* \param dy The translation on y
* \param errorMsg If given, it will contain the error message
* \param topologicalEditing If TRUE, the function will perform topological
* editing of the vertices of \a layer on \a layer and \a topologicalLayer
* \param topologicalLayer The layer where vertices from the moved features of \a layer will be added
* \param childrenInfoMsg If given, it will contain messages related to the creation of child features
* \returns TRUE if all features could be copied.
*
*/
bool copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureRequest &request SIP_INOUT, double dx = 0, double dy = 0, QString *errorMsg SIP_OUT = nullptr, const bool topologicalEditing = false, QgsVectorLayer *topologicalLayer = nullptr, QString *childrenInfoMsg = nullptr ) const override;
private:
void commitError( QgsVectorLayer *vlayer ) const;
bool avoidIntersection( QgsVectorLayer *layer, QgsFeatureRequest &request, QString *errorMsg = nullptr ) const;
};

View File

@ -277,7 +277,10 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
{
emit messageEmitted( errorMsg, Qgis::MessageLevel::Critical );
deleteRubberband();
vlayer->deleteFeatures( request.filterFids() );
vlayer->destroyEditCommand();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
return;
}
if ( !childrenInfoMsg.isEmpty() )
{

View File

@ -27,6 +27,7 @@
#include "qgssettings.h"
#include "qgsvectorlayer.h"
#include "qgsmapmouseevent.h"
#include "qgsguivectorlayertools.h"
#include "testqgsmaptoolutils.h"
@ -45,13 +46,16 @@ class TestQgsMapToolMoveFeature: public QObject
void cleanupTestCase();// will be called after the last testfunction was executed.
void testMoveFeature();
void testCopyMoveFeature();
void testTopologicalMoveFeature();
void testAvoidIntersectionAndTopoEdit();
void testAvoidIntersectionsCopyMove();
private:
QgisApp *mQgisApp = nullptr;
QgsMapCanvas *mCanvas = nullptr;
QgsMapToolMoveFeature *mCaptureTool = nullptr;
QgsMapToolMoveFeature *mCopyMoveTool = nullptr;
QgsVectorLayer *mLayerBase = nullptr;
};
@ -73,6 +77,7 @@ void TestQgsMapToolMoveFeature::initTestCase()
mQgisApp = new QgisApp();
mCanvas = new QgsMapCanvas();
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
@ -113,7 +118,8 @@ void TestQgsMapToolMoveFeature::initTestCase()
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerBase );
mCanvas->setCurrentLayer( mLayerBase );
// create the tool
// create the tools
mCopyMoveTool = new QgsMapToolMoveFeature( mCanvas, QgsMapToolMoveFeature::CopyMove );
mCaptureTool = new QgsMapToolMoveFeature( mCanvas, QgsMapToolMoveFeature::Move );
mCanvas->setMapTool( mCaptureTool );
@ -125,6 +131,7 @@ void TestQgsMapToolMoveFeature::initTestCase()
void TestQgsMapToolMoveFeature::cleanupTestCase()
{
delete mCaptureTool;
delete mCopyMoveTool;
delete mCanvas;
QgsApplication::exitQgis();
}
@ -144,6 +151,34 @@ void TestQgsMapToolMoveFeature::testMoveFeature()
mLayerBase->undoStack()->undo();
}
void TestQgsMapToolMoveFeature::testCopyMoveFeature()
{
mCanvas->setMapTool( mCopyMoveTool );
TestQgsMapToolAdvancedDigitizingUtils utils( mCopyMoveTool );
utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
const QString wkt1 = "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))";
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt(), wkt1 );
const QString wkt2 = "Polygon ((2 0, 2 5, 3 5, 3 0, 2 0))";
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt(), wkt2 );
// copied feature
const QString wkt3 = "Polygon ((1 0, 1 1, 2 1, 2 0, 1 0))";
QgsFeatureIterator fi1 = mLayerBase->getFeatures();
QgsFeature f1;
while ( fi1.nextFeature( f1 ) )
{
QCOMPARE( f1.geometry().asWkt( 2 ), wkt3 );
break;
}
mLayerBase->undoStack()->undo();
mCanvas->setMapTool( mCaptureTool );
}
void TestQgsMapToolMoveFeature::testTopologicalMoveFeature()
{
const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
@ -188,5 +223,42 @@ void TestQgsMapToolMoveFeature::testAvoidIntersectionAndTopoEdit()
QgsProject::instance()->setAvoidIntersectionsMode( mode );
}
void TestQgsMapToolMoveFeature::testAvoidIntersectionsCopyMove()
{
const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() );
QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer );
QgsProject::instance()->setTopologicalEditing( true );
mCanvas->setMapTool( mCopyMoveTool );
TestQgsMapToolAdvancedDigitizingUtils utils( mCopyMoveTool );
utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
utils.mouseClick( 2.5, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
const QString wkt1 = "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))";
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt(), wkt1 );
const QString wkt2 = "Polygon ((2 0, 2 1, 2 5, 3 5, 3 0, 2.5 0, 2 0))";
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt(), wkt2 );
// copied feature
const QString wkt3 = "Polygon ((1.5 1, 2 1, 2 0, 1.5 0, 1.5 1))";
QgsFeatureIterator fi1 = mLayerBase->getFeatures();
QgsFeature f1;
while ( fi1.nextFeature( f1 ) )
{
QCOMPARE( f1.geometry().asWkt( 2 ), wkt3 );
break;
}
mLayerBase->undoStack()->undo();
mCanvas->setMapTool( mCaptureTool );
QgsProject::instance()->setTopologicalEditing( topologicalEditing );
QgsProject::instance()->setAvoidIntersectionsMode( mode );
}
QGSTEST_MAIN( TestQgsMapToolMoveFeature )
#include "testqgsmaptoolmovefeature.moc"