[feature][selfsnap] add snapping to currently digitized feature for capture tool

This commit is contained in:
olivierdalang 2020-05-05 10:30:26 +02:00
parent d48c17816f
commit 66ebe34004
15 changed files with 495 additions and 5 deletions

View File

@ -700,6 +700,7 @@
<file>themes/default/mIconSnappingMiddle.svg</file>
<file>themes/default/mIconSnappingOnScale.svg</file>
<file>themes/default/mIconSnappingVertex.svg</file>
<file>themes/default/mIconSnappingSelf.svg</file>
<file>themes/default/mIconSnappingSegment.svg</file>
<file>themes/default/mIconTopologicalEditing.svg</file>
<file>themes/default/mIconSnappingIntersection.svg</file>

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="mIconSnappingSelf.svg"
id="svg68"
version="1.1"
width="24"
viewBox="0 0 24 24"
height="24">
<metadata
id="metadata74">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs72" />
<sodipodi:namedview
inkscape:current-layer="svg68"
inkscape:window-maximized="0"
inkscape:window-y="120"
inkscape:window-x="351"
inkscape:cy="15.032678"
inkscape:cx="21.715662"
inkscape:zoom="14.849242"
showgrid="false"
id="namedview70"
inkscape:window-height="1163"
inkscape:window-width="1445"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
id="g66"
transform="translate(0.04085775,-7.9316355)"
stroke-linecap="round">
<path
sodipodi:nodetypes="cccc"
id="path60"
stroke-width="2"
stroke-linejoin="round"
stroke="#8cbe8c"
fill="none"
d="M 20.566062,23.218131 4.4877133,14.977485 9.0817013,27.060272 2,30" />
<ellipse
id="ellipse62"
stroke-width="2"
stroke="#ffffff"
ry="2.5"
rx="0.75"
fill="#322825"
cy="14"
cx="-14.75" />
<circle
id="circle64"
stroke-width="1.031"
stroke="#8c8c8c"
r="2.034729"
fill="#bebebe"
cy="26.858242"
cx="8.6102972" />
<circle
cx="4.4877133"
cy="15.179515"
fill="#bebebe"
r="2.034729"
stroke="#8c8c8c"
stroke-width="1.031"
id="circle64-5" />
</g>
<g
transform="matrix(1.2651778,0,0,1.2651778,-4.6265242,-0.70591315)"
id="g1921">
<path
sodipodi:nodetypes="cccc"
id="path1853"
stroke-width="0.35534"
stroke-linecap="round"
stroke="#505050"
overflow="visible"
fill="#505050"
d="m 19.461512,9.2175166 -2.259422,1.3635194 2.663548,1.925751 z" />
<path
id="path1855"
stroke-width="0.35534"
stroke-linecap="round"
stroke="#8b7617"
overflow="visible"
fill="#fce94f"
d="M 15.362026,1.9819405 12.900159,3.4032995 16.927342,10.37859 19.38921,8.9572295 15.362026,1.9819405" />
<path
id="path1861"
style="overflow:visible;opacity:0.5;fill:#fce94f;stroke:#8b7617;stroke-width:0.35534;stroke-linecap:round"
d="m 13.957675,3.3398245 3.79029,6.564978" />
<path
id="path1863"
stroke-width="0.236893"
stroke-linecap="square"
stroke="#969696"
overflow="visible"
fill="#969696"
d="M 19.369733,12.033342 17.379211,10.47792 18.198585,10.004853 Z" />
<path
id="path1865"
style="overflow:visible;opacity:0.5;fill:#fce94f;stroke:#8b7617;stroke-width:0.35534;stroke-linecap:round"
d="m 14.983453,2.7475915 3.790289,6.564978" />
<path
id="path1867"
stroke-width="0.236893"
stroke-linecap="round"
stroke="#969696"
overflow="visible"
fill="#e6e6e6"
d="m 15.192412,1.4694895 -2.651241,1.530695 0.516222,0.894122 2.65124,-1.530695 z" />
<path
id="path1869"
stroke-width="0.236893"
stroke-linecap="square"
stroke="#e6e6e6"
overflow="visible"
fill="#e6e6e6"
d="M 19.70856,11.92433 19.319283,9.3578195 18.499909,9.8308845 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -53,6 +53,8 @@ The QgsCadUtils class provides routines for CAD editing.
QgsPointXY finalMapPoint;
QgsPointLocator::Match snapMatch;
QgsPointLocator::Match edgeMatch;
double softLockCommonAngle;

View File

@ -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;

View File

@ -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<QgsVectorLayer *> 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 );

View File

@ -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();

View File

@ -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;

View File

@ -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<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> 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<int>( 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 );

View File

@ -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<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> 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<QgsVectorLayer *, IndividualLayerSettings> mIndividualLayerSettings;

View File

@ -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<QgsPointLocator::Type>( 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 );

View File

@ -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<QgsVectorLayer *> 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<QString> mHybridNonindexableLayers;
//! list of additionnal snapping layers
QSet<QgsVectorLayer *> mExtraSnapLayers;
/**
* a record for each layer seen:

View File

@ -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<QgsPointLocator::Match> 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<QgsPointXY> &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 );
}
}

View File

@ -22,6 +22,7 @@
#include "qgscompoundcurve.h"
#include "qgsgeometry.h"
#include "qobjectuniqueptr.h"
#include "qgssnappingutils.h"
#include <QPoint>
#include <QList>
@ -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<QgsSnapIndicator> mSnapIndicator;

View File

@ -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<QgsMapLayer *>() << 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<QgsMapLayer *>() << mLayerSelfSnapLine );
mLayerSelfSnapLine->startEditing();
// add layers to canvas
mCanvas->setLayers( QList<QgsMapLayer *>() << 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<QgsFeatureId> 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"

View File

@ -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 )