mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-12 00:06:44 -05:00
Merge pull request #57504 from GispoCoding/fix_copy_move_overlap
Apply avoid overlap with the copy move map tool
This commit is contained in:
commit
92f182568f
@ -2091,6 +2091,7 @@ QgisApp::QgisApp()
|
|||||||
mMapTools = std::make_unique< QgsAppMapTools >( mMapCanvas, mAdvancedDigitizingDockWidget );
|
mMapTools = std::make_unique< QgsAppMapTools >( mMapCanvas, mAdvancedDigitizingDockWidget );
|
||||||
mDigitizingTechniqueManager = new QgsMapToolsDigitizingTechniqueManager( this );
|
mDigitizingTechniqueManager = new QgsMapToolsDigitizingTechniqueManager( this );
|
||||||
|
|
||||||
|
mVectorLayerTools = new QgsGuiVectorLayerTools();
|
||||||
mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
|
mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
|
||||||
|
|
||||||
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &QgisApp::onActiveLayerChanged );
|
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &QgisApp::onActiveLayerChanged );
|
||||||
|
|||||||
@ -18,12 +18,14 @@
|
|||||||
|
|
||||||
#include "qgsguivectorlayertools.h"
|
#include "qgsguivectorlayertools.h"
|
||||||
|
|
||||||
|
#include "qgsavoidintersectionsoperation.h"
|
||||||
#include "qgisapp.h"
|
#include "qgisapp.h"
|
||||||
#include "qgsfeatureaction.h"
|
#include "qgsfeatureaction.h"
|
||||||
#include "qgsmessagebar.h"
|
#include "qgsmessagebar.h"
|
||||||
#include "qgsmessagebaritem.h"
|
#include "qgsmessagebaritem.h"
|
||||||
#include "qgsmessageviewer.h"
|
#include "qgsmessageviewer.h"
|
||||||
#include "qgsvectorlayer.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
|
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;
|
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
|
void QgsGuiVectorLayerTools::commitError( QgsVectorLayer *vlayer ) const
|
||||||
{
|
{
|
||||||
QgsMessageViewer *mv = new QgsMessageViewer();
|
QgsMessageViewer *mv = new QgsMessageViewer();
|
||||||
|
|||||||
@ -79,8 +79,26 @@ class QgsGuiVectorLayerTools : public QgsVectorLayerTools
|
|||||||
*/
|
*/
|
||||||
bool saveEdits( QgsVectorLayer *layer ) const override;
|
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:
|
private:
|
||||||
void commitError( QgsVectorLayer *vlayer ) const;
|
void commitError( QgsVectorLayer *vlayer ) const;
|
||||||
|
bool avoidIntersection( QgsVectorLayer *layer, QgsFeatureRequest &request, QString *errorMsg = nullptr ) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -277,7 +277,10 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
|
|||||||
{
|
{
|
||||||
emit messageEmitted( errorMsg, Qgis::MessageLevel::Critical );
|
emit messageEmitted( errorMsg, Qgis::MessageLevel::Critical );
|
||||||
deleteRubberband();
|
deleteRubberband();
|
||||||
|
vlayer->deleteFeatures( request.filterFids() );
|
||||||
|
vlayer->destroyEditCommand();
|
||||||
mSnapIndicator->setMatch( QgsPointLocator::Match() );
|
mSnapIndicator->setMatch( QgsPointLocator::Match() );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if ( !childrenInfoMsg.isEmpty() )
|
if ( !childrenInfoMsg.isEmpty() )
|
||||||
{
|
{
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
#include "qgssettings.h"
|
#include "qgssettings.h"
|
||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
#include "qgsmapmouseevent.h"
|
#include "qgsmapmouseevent.h"
|
||||||
|
#include "qgsguivectorlayertools.h"
|
||||||
#include "testqgsmaptoolutils.h"
|
#include "testqgsmaptoolutils.h"
|
||||||
|
|
||||||
|
|
||||||
@ -45,13 +46,16 @@ class TestQgsMapToolMoveFeature: public QObject
|
|||||||
void cleanupTestCase();// will be called after the last testfunction was executed.
|
void cleanupTestCase();// will be called after the last testfunction was executed.
|
||||||
|
|
||||||
void testMoveFeature();
|
void testMoveFeature();
|
||||||
|
void testCopyMoveFeature();
|
||||||
void testTopologicalMoveFeature();
|
void testTopologicalMoveFeature();
|
||||||
void testAvoidIntersectionAndTopoEdit();
|
void testAvoidIntersectionAndTopoEdit();
|
||||||
|
void testAvoidIntersectionsCopyMove();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgisApp *mQgisApp = nullptr;
|
QgisApp *mQgisApp = nullptr;
|
||||||
QgsMapCanvas *mCanvas = nullptr;
|
QgsMapCanvas *mCanvas = nullptr;
|
||||||
QgsMapToolMoveFeature *mCaptureTool = nullptr;
|
QgsMapToolMoveFeature *mCaptureTool = nullptr;
|
||||||
|
QgsMapToolMoveFeature *mCopyMoveTool = nullptr;
|
||||||
QgsVectorLayer *mLayerBase = nullptr;
|
QgsVectorLayer *mLayerBase = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,6 +77,7 @@ void TestQgsMapToolMoveFeature::initTestCase()
|
|||||||
|
|
||||||
mQgisApp = new QgisApp();
|
mQgisApp = new QgisApp();
|
||||||
|
|
||||||
|
|
||||||
mCanvas = new QgsMapCanvas();
|
mCanvas = new QgsMapCanvas();
|
||||||
|
|
||||||
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
|
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
|
||||||
@ -113,7 +118,8 @@ void TestQgsMapToolMoveFeature::initTestCase()
|
|||||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerBase );
|
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerBase );
|
||||||
mCanvas->setCurrentLayer( mLayerBase );
|
mCanvas->setCurrentLayer( mLayerBase );
|
||||||
|
|
||||||
// create the tool
|
// create the tools
|
||||||
|
mCopyMoveTool = new QgsMapToolMoveFeature( mCanvas, QgsMapToolMoveFeature::CopyMove );
|
||||||
mCaptureTool = new QgsMapToolMoveFeature( mCanvas, QgsMapToolMoveFeature::Move );
|
mCaptureTool = new QgsMapToolMoveFeature( mCanvas, QgsMapToolMoveFeature::Move );
|
||||||
mCanvas->setMapTool( mCaptureTool );
|
mCanvas->setMapTool( mCaptureTool );
|
||||||
|
|
||||||
@ -125,6 +131,7 @@ void TestQgsMapToolMoveFeature::initTestCase()
|
|||||||
void TestQgsMapToolMoveFeature::cleanupTestCase()
|
void TestQgsMapToolMoveFeature::cleanupTestCase()
|
||||||
{
|
{
|
||||||
delete mCaptureTool;
|
delete mCaptureTool;
|
||||||
|
delete mCopyMoveTool;
|
||||||
delete mCanvas;
|
delete mCanvas;
|
||||||
QgsApplication::exitQgis();
|
QgsApplication::exitQgis();
|
||||||
}
|
}
|
||||||
@ -144,6 +151,34 @@ void TestQgsMapToolMoveFeature::testMoveFeature()
|
|||||||
mLayerBase->undoStack()->undo();
|
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()
|
void TestQgsMapToolMoveFeature::testTopologicalMoveFeature()
|
||||||
{
|
{
|
||||||
const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
|
const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
|
||||||
@ -188,5 +223,42 @@ void TestQgsMapToolMoveFeature::testAvoidIntersectionAndTopoEdit()
|
|||||||
QgsProject::instance()->setAvoidIntersectionsMode( mode );
|
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 )
|
QGSTEST_MAIN( TestQgsMapToolMoveFeature )
|
||||||
#include "testqgsmaptoolmovefeature.moc"
|
#include "testqgsmaptoolmovefeature.moc"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user