diff --git a/images/images.qrc b/images/images.qrc
index 219691db49a..2e26158bbe5 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -700,6 +700,7 @@
themes/default/mIconSnappingMiddle.svg
themes/default/mIconSnappingOnScale.svg
themes/default/mIconSnappingVertex.svg
+ themes/default/mIconSnappingSelf.svg
themes/default/mIconSnappingSegment.svg
themes/default/mIconTopologicalEditing.svg
themes/default/mIconSnappingIntersection.svg
diff --git a/images/themes/default/mIconSnappingSelf.svg b/images/themes/default/mIconSnappingSelf.svg
new file mode 100644
index 00000000000..0acabecae37
--- /dev/null
+++ b/images/themes/default/mIconSnappingSelf.svg
@@ -0,0 +1,141 @@
+
+
diff --git a/python/core/auto_generated/qgscadutils.sip.in b/python/core/auto_generated/qgscadutils.sip.in
index bdb47304da7..742621e103d 100644
--- a/python/core/auto_generated/qgscadutils.sip.in
+++ b/python/core/auto_generated/qgscadutils.sip.in
@@ -53,6 +53,8 @@ The QgsCadUtils class provides routines for CAD editing.
QgsPointXY finalMapPoint;
+ QgsPointLocator::Match snapMatch;
+
QgsPointLocator::Match edgeMatch;
double softLockCommonAngle;
diff --git a/python/core/auto_generated/qgssnappingconfig.sip.in b/python/core/auto_generated/qgssnappingconfig.sip.in
index 77c6645ea46..58dc70ad976 100644
--- a/python/core/auto_generated/qgssnappingconfig.sip.in
+++ b/python/core/auto_generated/qgssnappingconfig.sip.in
@@ -343,6 +343,20 @@ Returns if the snapping on intersection is enabled
void setIntersectionSnapping( bool enabled );
%Docstring
Sets if the snapping on intersection is enabled
+%End
+
+ bool selfSnapping() const;
+%Docstring
+Returns if self snapping (snapping to the currently digitised feature) is enabled
+
+.. versionadded:: 3.14
+%End
+
+ void setSelfSnapping( bool enabled );
+%Docstring
+Sets if self snapping (snapping to the currently digitised feature) is enabled
+
+.. versionadded:: 3.14
%End
SIP_PYDICT individualLayerSettings() const;
diff --git a/python/core/auto_generated/qgssnappingutils.sip.in b/python/core/auto_generated/qgssnappingutils.sip.in
index 8d50edf4fee..9c418399933 100644
--- a/python/core/auto_generated/qgssnappingutils.sip.in
+++ b/python/core/auto_generated/qgssnappingutils.sip.in
@@ -166,6 +166,42 @@ Set if invisible features must be snapped or not.
.. versionadded:: 3.2
%End
+ void addExtraSnapLayer( QgsVectorLayer *vl );
+%Docstring
+Supply an extra snapping layer (typically a memory layer).
+This is can be used by map tools to provide additionnal
+snappings points.
+
+.. seealso:: :py:func:`removeExtraSnapLayer`
+
+.. seealso:: :py:func:`getExtraSnapLayers`
+
+.. versionadded:: 3.14
+%End
+
+ void removeExtraSnapLayer( QgsVectorLayer *vl );
+%Docstring
+Removes an extra snapping layer
+
+.. seealso:: :py:func:`addExtraSnapLayer`
+
+.. seealso:: :py:func:`getExtraSnapLayers`
+
+.. versionadded:: 3.14
+%End
+
+ QSet getExtraSnapLayers();
+%Docstring
+Returns the list of extra snapping layers
+
+.. seealso:: :py:func:`addExtraSnapLayer`
+
+.. seealso:: :py:func:`removeExtraSnapLayer`
+
+.. versionadded:: 3.14
+%End
+
+
public slots:
void setConfig( const QgsSnappingConfig &snappingConfig );
diff --git a/src/app/qgssnappingwidget.cpp b/src/app/qgssnappingwidget.cpp
index 28aa4cdc197..abdcfaca69c 100644
--- a/src/app/qgssnappingwidget.cpp
+++ b/src/app/qgssnappingwidget.cpp
@@ -287,6 +287,14 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
tracingMenu->addAction( widgetAction );
mEnableTracingAction->setMenu( tracingMenu );
+ // self-snapping button
+ mSelfSnappingAction = new QAction( tr( "Self-snapping" ), this );
+ mSelfSnappingAction->setCheckable( true );
+ mSelfSnappingAction->setIcon( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSelf.svg" ) ) );
+ mSelfSnappingAction->setToolTip( tr( "Enable Self-snapping" ) );
+ mSelfSnappingAction->setObjectName( QStringLiteral( "SelfSnappingAction" ) );
+ connect( mSelfSnappingAction, &QAction::toggled, this, &QgsSnappingWidget::enableSelfSnapping );
+
// layout
if ( mDisplayMode == ToolBar )
{
@@ -316,6 +324,7 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
tb->addAction( mIntersectionSnappingAction );
tb->addAction( mEnableTracingAction );
+ tb->addAction( mSelfSnappingAction );
}
else
{
@@ -350,6 +359,12 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
interButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
layout->addWidget( interButton );
+ QToolButton *selfsnapButton = new QToolButton();
+ selfsnapButton->addAction( mSelfSnappingAction );
+ selfsnapButton->setDefaultAction( mSelfSnappingAction );
+ selfsnapButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+ layout->addWidget( selfsnapButton );
+
layout->setContentsMargins( 0, 0, 0, 0 );
layout->setAlignment( Qt::AlignRight );
layout->setSpacing( mDisplayMode == Widget ? 3 : 0 );
@@ -488,6 +503,11 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
}
+ if ( config.selfSnapping() != mSelfSnappingAction->isChecked() )
+ {
+ mSelfSnappingAction->setChecked( config.selfSnapping() );
+ }
+
toggleSnappingWidgets( config.enabled() );
}
@@ -552,6 +572,7 @@ void QgsSnappingWidget::toggleSnappingWidgets( bool enabled )
mAdvancedConfigWidget->setEnabled( enabled );
}
mIntersectionSnappingAction->setEnabled( enabled );
+ mSelfSnappingAction->setEnabled( enabled );
mEnableTracingAction->setEnabled( enabled );
}
@@ -593,6 +614,12 @@ void QgsSnappingWidget::enableIntersectionSnapping( bool enabled )
mProject->setSnappingConfig( mConfig );
}
+void QgsSnappingWidget::enableSelfSnapping( bool enabled )
+{
+ mConfig.setSelfSnapping( enabled );
+ mProject->setSnappingConfig( mConfig );
+}
+
void QgsSnappingWidget::onSnappingTreeLayersChanged()
{
mLayerTreeView->expandAll();
diff --git a/src/app/qgssnappingwidget.h b/src/app/qgssnappingwidget.h
index 42081e6edf6..2d56eda0840 100644
--- a/src/app/qgssnappingwidget.h
+++ b/src/app/qgssnappingwidget.h
@@ -114,6 +114,8 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
void enableIntersectionSnapping( bool enabled );
+ void enableSelfSnapping( bool enabled );
+
void modeButtonTriggered( QAction *action );
void avoidIntersectionsModeButtonTriggered( QAction *action );
void typeButtonTriggered( QAction *action );
@@ -172,6 +174,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
QAction *mIntersectionSnappingAction = nullptr;
QAction *mEnableTracingAction = nullptr;
QgsDoubleSpinBox *mTracingOffsetSpinBox = nullptr;
+ QAction *mSelfSnappingAction = nullptr;
QTreeView *mLayerTreeView = nullptr;
QWidget *mAdvancedConfigWidget = nullptr;
QgsFloatingWidget *mAdvancedConfigContainer = nullptr;
diff --git a/src/core/qgssnappingconfig.cpp b/src/core/qgssnappingconfig.cpp
index 2095212119d..86988973a0f 100644
--- a/src/core/qgssnappingconfig.cpp
+++ b/src/core/qgssnappingconfig.cpp
@@ -179,6 +179,7 @@ bool QgsSnappingConfig::operator==( const QgsSnappingConfig &other ) const
&& mTolerance == other.mTolerance
&& mUnits == other.mUnits
&& mIntersectionSnapping == other.mIntersectionSnapping
+ && mSelfSnapping == other.mSelfSnapping
&& mIndividualLayerSettings == other.mIndividualLayerSettings
&& mScaleDependencyMode == other.mScaleDependencyMode
&& mMinimumScale == other.mMinimumScale
@@ -218,6 +219,7 @@ void QgsSnappingConfig::reset()
mUnits = units;
}
mIntersectionSnapping = false;
+ mSelfSnapping = false;
// set advanced config
if ( mProject )
@@ -343,6 +345,16 @@ void QgsSnappingConfig::setIntersectionSnapping( bool enabled )
mIntersectionSnapping = enabled;
}
+bool QgsSnappingConfig::selfSnapping() const
+{
+ return mSelfSnapping;
+}
+
+void QgsSnappingConfig::setSelfSnapping( bool enabled )
+{
+ mSelfSnapping = enabled;
+}
+
QHash QgsSnappingConfig::individualLayerSettings() const
{
return mIndividualLayerSettings;
@@ -459,6 +471,9 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
if ( snapSettingsElem.hasAttribute( QStringLiteral( "intersection-snapping" ) ) )
mIntersectionSnapping = snapSettingsElem.attribute( QStringLiteral( "intersection-snapping" ) ) == QLatin1String( "1" );
+ if ( snapSettingsElem.hasAttribute( QStringLiteral( "self-snapping" ) ) )
+ mSelfSnapping = snapSettingsElem.attribute( QStringLiteral( "self-snapping" ) ) == QLatin1String( "1" );
+
// do not clear the settings as they must be automatically synchronized with current layers
QDomNodeList nodes = snapSettingsElem.elementsByTagName( QStringLiteral( "individual-layer-settings" ) );
if ( nodes.count() )
@@ -504,6 +519,7 @@ void QgsSnappingConfig::writeProject( QDomDocument &doc )
snapSettingsElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
snapSettingsElem.setAttribute( QStringLiteral( "unit" ), static_cast( mUnits ) );
snapSettingsElem.setAttribute( QStringLiteral( "intersection-snapping" ), QString::number( mIntersectionSnapping ) );
+ snapSettingsElem.setAttribute( QStringLiteral( "self-snapping" ), QString::number( mSelfSnapping ) );
snapSettingsElem.setAttribute( QStringLiteral( "scaleDependencyMode" ), QString::number( mScaleDependencyMode ) );
snapSettingsElem.setAttribute( QStringLiteral( "minScale" ), mMinimumScale );
snapSettingsElem.setAttribute( QStringLiteral( "maxScale" ), mMaximumScale );
diff --git a/src/core/qgssnappingconfig.h b/src/core/qgssnappingconfig.h
index 8768065c5a0..43fe7906a74 100644
--- a/src/core/qgssnappingconfig.h
+++ b/src/core/qgssnappingconfig.h
@@ -335,6 +335,20 @@ class CORE_EXPORT QgsSnappingConfig
//! Sets if the snapping on intersection is enabled
void setIntersectionSnapping( bool enabled );
+ /**
+ * Returns if self snapping (snapping to the currently digitised feature) is enabled
+ *
+ * \since QGIS 3.14
+ */
+ bool selfSnapping() const;
+
+ /**
+ * Sets if self snapping (snapping to the currently digitised feature) is enabled
+ *
+ * \since QGIS 3.14
+ */
+ void setSelfSnapping( bool enabled );
+
//! Returns individual snapping settings for all layers
#ifndef SIP_RUN
QHash individualLayerSettings() const;
@@ -458,6 +472,7 @@ class CORE_EXPORT QgsSnappingConfig
double mMaximumScale = 0.0;
QgsTolerance::UnitType mUnits = QgsTolerance::ProjectUnits;
bool mIntersectionSnapping = false;
+ bool mSelfSnapping = false;
QHash mIndividualLayerSettings;
diff --git a/src/core/qgssnappingutils.cpp b/src/core/qgssnappingutils.cpp
index 0dd8be67a3a..4d246cc1287 100644
--- a/src/core/qgssnappingutils.cpp
+++ b/src/core/qgssnappingutils.cpp
@@ -275,6 +275,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
return QgsPointLocator::Match();
QgsPointLocator::Match bestMatch;
+ QgsPointLocator::MatchList edges; // for snap on intersection
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
if ( mSnappingConfig.intersectionSnapping() )
@@ -282,8 +283,19 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
if ( !locEdges )
return QgsPointLocator::Match();
+ edges = locEdges->edgesInRect( pointMap, tolerance );
+ }
- QgsPointLocator::MatchList edges = locEdges->edgesInRect( pointMap, tolerance );
+ for ( QgsVectorLayer *vl : mExtraSnapLayers )
+ {
+ QgsPointLocator *loc = locatorForLayer( vl );
+ _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
+ if ( mSnappingConfig.intersectionSnapping() )
+ edges << loc->edgesInRect( pointMap, tolerance );
+ }
+
+ if ( mSnappingConfig.intersectionSnapping() )
+ {
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
}
@@ -322,7 +334,8 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
QgsPointLocator::Match bestMatch;
QgsPointLocator::MatchList edges; // for snap on intersection
- double maxSnapIntTolerance = 0;
+ double maxTolerance = 0;
+ QgsPointLocator::Type maxTypes;
for ( const LayerConfig &layerConfig : qgis::as_const( filteredConfigs ) )
{
@@ -334,13 +347,24 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
if ( mSnappingConfig.intersectionSnapping() )
{
edges << loc->edgesInRect( pointMap, tolerance );
- maxSnapIntTolerance = std::max( maxSnapIntTolerance, tolerance );
}
+ // We keep the maximum tolerance for intersection snapping and extra snapping
+ maxTolerance = std::max( maxTolerance, tolerance );
+ // To avoid yet an additionnal setting, on extra snappings, we use the combination of all enabled snap types
+ maxTypes = static_cast( maxTypes | layerConfig.type );
}
}
+ for ( QgsVectorLayer *vl : mExtraSnapLayers )
+ {
+ QgsPointLocator *loc = locatorForLayer( vl );
+ _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
+ if ( mSnappingConfig.intersectionSnapping() )
+ edges << loc->edgesInRect( pointMap, maxTolerance );
+ }
+
if ( mSnappingConfig.intersectionSnapping() )
- _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxSnapIntTolerance );
+ _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
return bestMatch;
}
@@ -373,6 +397,14 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
}
}
+ for ( QgsVectorLayer *vl : mExtraSnapLayers )
+ {
+ QgsPointLocator *loc = locatorForLayer( vl );
+ _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
+ if ( mSnappingConfig.intersectionSnapping() )
+ edges << loc->edgesInRect( pointMap, tolerance );
+ }
+
if ( mSnappingConfig.intersectionSnapping() )
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
diff --git a/src/core/qgssnappingutils.h b/src/core/qgssnappingutils.h
index 252f82f7f33..ee5379dca76 100644
--- a/src/core/qgssnappingutils.h
+++ b/src/core/qgssnappingutils.h
@@ -185,6 +185,48 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
*/
void setEnableSnappingForInvisibleFeature( bool enable );
+ /**
+ * Supply an extra snapping layer (typically a memory layer).
+ * This is can be used by map tools to provide additionnal
+ * snappings points.
+ *
+ * \see removeExtraSnapLayer()
+ * \see getExtraSnapLayers()
+ *
+ * \since QGIS 3.14
+ */
+ void addExtraSnapLayer( QgsVectorLayer *vl )
+ {
+ mExtraSnapLayers.insert( vl );
+ }
+
+ /**
+ * Removes an extra snapping layer
+ *
+ * \see addExtraSnapLayer()
+ * \see getExtraSnapLayers()
+ *
+ * \since QGIS 3.14
+ */
+ void removeExtraSnapLayer( QgsVectorLayer *vl )
+ {
+ mExtraSnapLayers.remove( vl );
+ }
+
+ /**
+ * Returns the list of extra snapping layers
+ *
+ * \see addExtraSnapLayer()
+ * \see removeExtraSnapLayer()
+ *
+ * \since QGIS 3.14
+ */
+ QSet getExtraSnapLayers()
+ {
+ return mExtraSnapLayers;
+ }
+
+
public slots:
/**
@@ -257,6 +299,8 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
LocatorsMap mTemporaryLocators;
//! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those)
QSet mHybridNonindexableLayers;
+ //! list of additionnal snapping layers
+ QSet mExtraSnapLayers;
/**
* a record for each layer seen:
diff --git a/src/gui/qgsmaptoolcapture.cpp b/src/gui/qgsmaptoolcapture.cpp
index fc37dc76762..281c1df2ad5 100644
--- a/src/gui/qgsmaptoolcapture.cpp
+++ b/src/gui/qgsmaptoolcapture.cpp
@@ -51,6 +51,15 @@ QgsMapToolCapture::QgsMapToolCapture( QgsMapCanvas *canvas, QgsAdvancedDigitizin
connect( canvas, &QgsMapCanvas::currentLayerChanged,
this, &QgsMapToolCapture::currentLayerChanged );
+ mExtraSnapLayer = new QgsVectorLayer( "LineString?crs=0", "extra snap", "memory" );
+ mExtraSnapLayer->startEditing();
+ QgsFeature f;
+ mExtraSnapLayer->addFeature( f );
+ mExtraSnapFeatureId = f.id();
+
+ connect( QgsProject::instance(), &QgsProject::snappingConfigChanged,
+ this, &QgsMapToolCapture::updateExtraSnapLayer );
+
currentLayerChanged( canvas->currentLayer() );
}
@@ -63,6 +72,8 @@ QgsMapToolCapture::~QgsMapToolCapture()
mValidator->deleteLater();
mValidator = nullptr;
}
+ mExtraSnapLayer->deleteLater();
+ mExtraSnapLayer = nullptr;
}
QgsMapToolCapture::Capabilities QgsMapToolCapture::capabilities() const
@@ -75,6 +86,7 @@ void QgsMapToolCapture::activate()
if ( mTempRubberBand )
mTempRubberBand->show();
+ mCanvas->snappingUtils()->addExtraSnapLayer( mExtraSnapLayer );
QgsMapToolAdvancedDigitizing::activate();
}
@@ -85,6 +97,7 @@ void QgsMapToolCapture::deactivate()
mSnapIndicator->setMatch( QgsPointLocator::Match() );
+ mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
QgsMapToolAdvancedDigitizing::deactivate();
}
@@ -513,6 +526,7 @@ int QgsMapToolCapture::addVertex( const QgsPointXY &point, const QgsPointLocator
// ordinary digitizing
mRubberBand->addPoint( point );
mCaptureCurve.addVertex( layerPoint );
+ updateExtraSnapLayer();
mSnappingMatches.append( match );
}
@@ -574,6 +588,7 @@ int QgsMapToolCapture::addCurve( QgsCurve *c )
c->transform( ct, QgsCoordinateTransform::ReverseTransform );
}
mCaptureCurve.addCurve( c );
+ updateExtraSnapLayer();
for ( int i = 0; i < c->length(); ++i )
mSnappingMatches.append( QgsPointLocator::Match() );
@@ -583,6 +598,7 @@ int QgsMapToolCapture::addCurve( QgsCurve *c )
void QgsMapToolCapture::clearCurve()
{
mCaptureCurve.clear();
+ updateExtraSnapLayer();
}
QList QgsMapToolCapture::snappingMatches() const
@@ -627,6 +643,7 @@ void QgsMapToolCapture::undo()
vertexToRemove.vertex = size() - 1;
mCaptureCurve.deleteVertex( vertexToRemove );
mSnappingMatches.removeAt( vertexToRemove.vertex );
+ updateExtraSnapLayer();
mCadDockWidget->removePreviousPoint();
@@ -676,6 +693,7 @@ void QgsMapToolCapture::stopCapturing()
mCapturing = false;
mCaptureCurve.clear();
+ updateExtraSnapLayer();
mSnappingMatches.clear();
if ( currentVectorLayer() )
currentVectorLayer()->triggerRepaint();
@@ -695,6 +713,7 @@ void QgsMapToolCapture::clean()
void QgsMapToolCapture::closePolygon()
{
mCaptureCurve.close();
+ updateExtraSnapLayer();
}
void QgsMapToolCapture::validateGeometry()
@@ -795,6 +814,7 @@ void QgsMapToolCapture::setPoints( const QVector &pointList )
QgsLineString *line = new QgsLineString( pointList );
mCaptureCurve.clear();
mCaptureCurve.addCurve( line );
+ updateExtraSnapLayer();
mSnappingMatches.clear();
for ( int i = 0; i < line->length(); ++i )
mSnappingMatches.append( QgsPointLocator::Match() );
@@ -805,6 +825,7 @@ void QgsMapToolCapture::setPoints( const QgsPointSequence &pointList )
QgsLineString *line = new QgsLineString( pointList );
mCaptureCurve.clear();
mCaptureCurve.addCurve( line );
+ updateExtraSnapLayer();
mSnappingMatches.clear();
for ( int i = 0; i < line->length(); ++i )
mSnappingMatches.append( QgsPointLocator::Match() );
@@ -868,3 +889,19 @@ QgsPoint QgsMapToolCapture::mapPoint( const QgsMapMouseEvent &e ) const
return newPoint;
}
+
+void QgsMapToolCapture::updateExtraSnapLayer()
+{
+ if ( canvas()->snappingUtils()->config().selfSnapping() && mCanvas->currentLayer() )
+ {
+ // the current layer may have changed
+ mExtraSnapLayer->setCrs( mCanvas->currentLayer()->crs() );
+ QgsGeometry geom = QgsGeometry( mCaptureCurve.clone() );
+ mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
+ }
+ else
+ {
+ QgsGeometry geom;
+ mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
+ }
+}
diff --git a/src/gui/qgsmaptoolcapture.h b/src/gui/qgsmaptoolcapture.h
index c815e0ce569..d2e696aaa1b 100644
--- a/src/gui/qgsmaptoolcapture.h
+++ b/src/gui/qgsmaptoolcapture.h
@@ -22,6 +22,7 @@
#include "qgscompoundcurve.h"
#include "qgsgeometry.h"
#include "qobjectuniqueptr.h"
+#include "qgssnappingutils.h"
#include
#include
@@ -132,6 +133,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
private slots:
void addError( const QgsGeometry::Error &error );
void currentLayerChanged( QgsMapLayer *layer );
+ //! Update the extra snap layer, this should be called whenever the capturecurve changes
+ void updateExtraSnapLayer();
protected:
@@ -312,6 +315,11 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
QList< QgsGeometry::Error > mGeomErrors;
QList< QgsVertexMarker * > mGeomErrorMarkers;
+ //! A layer containing the current capture curve to provide additionnal snapping
+ QgsVectorLayer *mExtraSnapLayer = nullptr;
+ //! The feature in that layer (for updating)
+ QgsFeatureId mExtraSnapFeatureId;
+
bool mCaptureModeFromLayer = false;
std::unique_ptr mSnapIndicator;
diff --git a/tests/src/app/testqgsmaptooladdfeatureline.cpp b/tests/src/app/testqgsmaptooladdfeatureline.cpp
index 9838e203f2a..b744fb1422b 100644
--- a/tests/src/app/testqgsmaptooladdfeatureline.cpp
+++ b/tests/src/app/testqgsmaptooladdfeatureline.cpp
@@ -71,6 +71,7 @@ class TestQgsMapToolAddFeatureLine : public QObject
void testZMSnapping();
void testTopologicalEditingZ();
void testCloseLine();
+ void testSelfSnapping();
private:
QgisApp *mQgisApp = nullptr;
@@ -85,6 +86,7 @@ class TestQgsMapToolAddFeatureLine : public QObject
QgsVectorLayer *mLayerTopoZ = nullptr;
QgsVectorLayer *mLayerLine2D = nullptr;
QgsVectorLayer *mLayerCloseLine = nullptr;
+ QgsVectorLayer *mLayerSelfSnapLine = nullptr;
QgsFeatureId mFidLineF1 = 0;
QgsFeatureId mFidCurvedF1 = 0;
};
@@ -208,8 +210,15 @@ void TestQgsMapToolAddFeatureLine::initTestCase()
mLayerLine2D->addFeature( lineString2DF );
QCOMPARE( mLayerLine2D->featureCount(), ( long )1 );
- mCanvas->setLayers( QList() << mLayerLine << mLayerLineCurved << mLayerLineZ << mLayerPointZM << mLayerTopoZ << mLayerLine2D );
+ // make testing layers
+ mLayerSelfSnapLine = new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:27700" ), QStringLiteral( "layer line" ), QStringLiteral( "memory" ) );
+ QVERIFY( mLayerSelfSnapLine->isValid() );
+ QgsProject::instance()->addMapLayers( QList() << mLayerSelfSnapLine );
+ mLayerSelfSnapLine->startEditing();
+
+ // add layers to canvas
+ mCanvas->setLayers( QList() << mLayerLine << mLayerLineCurved << mLayerLineZ << mLayerPointZM << mLayerTopoZ << mLayerLine2D << mLayerSelfSnapLine );
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
// create the tool
@@ -578,5 +587,55 @@ void TestQgsMapToolAddFeatureLine::testCloseLine()
mLayerCloseLine->undoStack()->undo();
}
+
+void TestQgsMapToolAddFeatureLine::testSelfSnapping()
+{
+ TestQgsMapToolAdvancedDigitizingUtils utils( mCaptureTool );
+
+ mCanvas->setCurrentLayer( mLayerSelfSnapLine );
+
+ QSet oldFids = utils.existingFeatureIds();
+
+ QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
+ cfg.setEnabled( true );
+ cfg.setMode( QgsSnappingConfig::AllLayers );
+ cfg.setTypeFlag( QgsSnappingConfig::VertexFlag );
+ cfg.setTolerance( 50 );
+ cfg.setUnits( QgsTolerance::Pixels );
+ mCanvas->snappingUtils()->setConfig( cfg );
+
+
+ QString targetWkt = "LineString (2 5, 3 5, 3 6, 2 5)";
+
+ // Without self snapping, endpoint won't snap to start point
+ cfg.setSelfSnapping( false );
+ mCanvas->snappingUtils()->setConfig( cfg );
+
+ utils.mouseClick( 2, 5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 3, 5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 3, 6, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 2, 5.1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 2, 5.1, Qt::RightButton );
+
+ QgsFeatureId newFid1 = utils.newFeatureId( oldFids );
+ QVERIFY( ! mLayerSelfSnapLine->getFeature( newFid1 ).geometry().equals( QgsGeometry::fromWkt( targetWkt ) ) );
+ mLayerSelfSnapLine->undoStack()->undo();
+
+ // With self snapping, endpoint will snap to start point
+ cfg.setSelfSnapping( true );
+ mCanvas->snappingUtils()->setConfig( cfg );
+
+ utils.mouseClick( 2, 5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 3, 5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 3, 6, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 2, 5.1, Qt::LeftButton, Qt::KeyboardModifiers(), true );
+ utils.mouseClick( 2, 5.1, Qt::RightButton );
+
+ QgsFeatureId newFid2 = utils.newFeatureId( oldFids );
+ QCOMPARE( mLayerSelfSnapLine->getFeature( newFid2 ).geometry(), QgsGeometry::fromWkt( targetWkt ) );
+ mLayerSelfSnapLine->undoStack()->undo();
+
+}
+
QGSTEST_MAIN( TestQgsMapToolAddFeatureLine )
#include "testqgsmaptooladdfeatureline.moc"
diff --git a/tests/src/core/testqgssnappingutils.cpp b/tests/src/core/testqgssnappingutils.cpp
index d806a0351ca..ed3b1d411cc 100644
--- a/tests/src/core/testqgssnappingutils.cpp
+++ b/tests/src/core/testqgssnappingutils.cpp
@@ -529,6 +529,61 @@ class TestQgsSnappingUtils : public QObject
QVERIFY( m5.isValid() );
QVERIFY( m5.hasVertex() );
}
+
+ void testExtraSnapLayers()
+ {
+ // START COPYPASTE
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( QSize( 100, 100 ) );
+ mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
+ QVERIFY( mapSettings.hasValidSettings() );
+
+ QgsSnappingUtils u;
+ QgsSnappingConfig snappingConfig = u.config();
+ u.setMapSettings( mapSettings );
+ snappingConfig.setEnabled( true );
+ snappingConfig.setTypeFlag( QgsSnappingConfig::VertexFlag );
+ snappingConfig.setMode( QgsSnappingConfig::AllLayers );
+ snappingConfig.setTolerance( 5 );
+ snappingConfig.setUnits( QgsTolerance::Pixels );
+ u.setConfig( snappingConfig );
+
+ // additional vector layer
+ QgsVectorLayer *extraVL = new QgsVectorLayer( QStringLiteral( "Point?field=fId:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
+ extraVL->startEditing();
+
+ // we start with one point: (5, 5) (at 50, 50 on screen)
+ QgsFeature f3( extraVL->fields() );
+ f3.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 0.50, 0.50 ) ) );
+ extraVL->addFeature( f3 );
+ QVERIFY( extraVL->featureCount() == 1 );
+
+ // Without the extra snapping layer, we have no snap
+ QgsPointLocator::Match m1 = u.snapToMap( QgsPointXY( 0.50, 0.50 ) );
+ QVERIFY( !m1.isValid() );
+
+ // We add the snapping layer, we have snap
+ u.addExtraSnapLayer( extraVL );
+ QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( 0.50, 0.50 ) );
+ QVERIFY( m2.isValid() );
+
+ // We add to the snapping layer, the snap changed
+ QgsFeature f4( extraVL->fields() );
+ f4.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 0.75, 0.75 ) ) );
+ extraVL->addFeature( f4 );
+ QVERIFY( extraVL->featureCount() == 2 );
+ QgsPointLocator::Match m3 = u.snapToMap( QgsPointXY( 0.50, 0.50 ) );
+ QgsPointLocator::Match m4 = u.snapToMap( QgsPointXY( 0.75, 0.75 ) );
+ QVERIFY( m3.isValid() );
+ QVERIFY( m4.isValid() );
+
+ // We remove the snapping layer, we have no snap
+ u.removeExtraSnapLayer( extraVL );
+ QgsPointLocator::Match m5 = u.snapToMap( QgsPointXY( 0.50, 0.50 ) );
+ QgsPointLocator::Match m6 = u.snapToMap( QgsPointXY( 0.75, 0.75 ) );
+ QVERIFY( !m5.isValid() );
+ QVERIFY( !m6.isValid() );
+ }
};
QGSTEST_MAIN( TestQgsSnappingUtils )