mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-13 00:07:27 -05:00
[feature][selfsnap] add snapping to currently digitized feature for capture tool
This commit is contained in:
parent
d48c17816f
commit
66ebe34004
@ -700,6 +700,7 @@
|
|||||||
<file>themes/default/mIconSnappingMiddle.svg</file>
|
<file>themes/default/mIconSnappingMiddle.svg</file>
|
||||||
<file>themes/default/mIconSnappingOnScale.svg</file>
|
<file>themes/default/mIconSnappingOnScale.svg</file>
|
||||||
<file>themes/default/mIconSnappingVertex.svg</file>
|
<file>themes/default/mIconSnappingVertex.svg</file>
|
||||||
|
<file>themes/default/mIconSnappingSelf.svg</file>
|
||||||
<file>themes/default/mIconSnappingSegment.svg</file>
|
<file>themes/default/mIconSnappingSegment.svg</file>
|
||||||
<file>themes/default/mIconTopologicalEditing.svg</file>
|
<file>themes/default/mIconTopologicalEditing.svg</file>
|
||||||
<file>themes/default/mIconSnappingIntersection.svg</file>
|
<file>themes/default/mIconSnappingIntersection.svg</file>
|
||||||
|
|||||||
141
images/themes/default/mIconSnappingSelf.svg
Normal file
141
images/themes/default/mIconSnappingSelf.svg
Normal 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 |
@ -53,6 +53,8 @@ The QgsCadUtils class provides routines for CAD editing.
|
|||||||
|
|
||||||
QgsPointXY finalMapPoint;
|
QgsPointXY finalMapPoint;
|
||||||
|
|
||||||
|
QgsPointLocator::Match snapMatch;
|
||||||
|
|
||||||
QgsPointLocator::Match edgeMatch;
|
QgsPointLocator::Match edgeMatch;
|
||||||
|
|
||||||
double softLockCommonAngle;
|
double softLockCommonAngle;
|
||||||
|
|||||||
@ -343,6 +343,20 @@ Returns if the snapping on intersection is enabled
|
|||||||
void setIntersectionSnapping( bool enabled );
|
void setIntersectionSnapping( bool enabled );
|
||||||
%Docstring
|
%Docstring
|
||||||
Sets if the snapping on intersection is enabled
|
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
|
%End
|
||||||
|
|
||||||
SIP_PYDICT individualLayerSettings() const;
|
SIP_PYDICT individualLayerSettings() const;
|
||||||
|
|||||||
@ -166,6 +166,42 @@ Set if invisible features must be snapped or not.
|
|||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
%End
|
%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:
|
public slots:
|
||||||
|
|
||||||
void setConfig( const QgsSnappingConfig &snappingConfig );
|
void setConfig( const QgsSnappingConfig &snappingConfig );
|
||||||
|
|||||||
@ -287,6 +287,14 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
|
|||||||
tracingMenu->addAction( widgetAction );
|
tracingMenu->addAction( widgetAction );
|
||||||
mEnableTracingAction->setMenu( tracingMenu );
|
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
|
// layout
|
||||||
if ( mDisplayMode == ToolBar )
|
if ( mDisplayMode == ToolBar )
|
||||||
{
|
{
|
||||||
@ -316,6 +324,7 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
|
|||||||
mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
|
mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
|
||||||
tb->addAction( mIntersectionSnappingAction );
|
tb->addAction( mIntersectionSnappingAction );
|
||||||
tb->addAction( mEnableTracingAction );
|
tb->addAction( mEnableTracingAction );
|
||||||
|
tb->addAction( mSelfSnappingAction );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -350,6 +359,12 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
|
|||||||
interButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
|
interButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
|
||||||
layout->addWidget( interButton );
|
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->setContentsMargins( 0, 0, 0, 0 );
|
||||||
layout->setAlignment( Qt::AlignRight );
|
layout->setAlignment( Qt::AlignRight );
|
||||||
layout->setSpacing( mDisplayMode == Widget ? 3 : 0 );
|
layout->setSpacing( mDisplayMode == Widget ? 3 : 0 );
|
||||||
@ -488,6 +503,11 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
|
|||||||
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
|
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( config.selfSnapping() != mSelfSnappingAction->isChecked() )
|
||||||
|
{
|
||||||
|
mSelfSnappingAction->setChecked( config.selfSnapping() );
|
||||||
|
}
|
||||||
|
|
||||||
toggleSnappingWidgets( config.enabled() );
|
toggleSnappingWidgets( config.enabled() );
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -552,6 +572,7 @@ void QgsSnappingWidget::toggleSnappingWidgets( bool enabled )
|
|||||||
mAdvancedConfigWidget->setEnabled( enabled );
|
mAdvancedConfigWidget->setEnabled( enabled );
|
||||||
}
|
}
|
||||||
mIntersectionSnappingAction->setEnabled( enabled );
|
mIntersectionSnappingAction->setEnabled( enabled );
|
||||||
|
mSelfSnappingAction->setEnabled( enabled );
|
||||||
mEnableTracingAction->setEnabled( enabled );
|
mEnableTracingAction->setEnabled( enabled );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,6 +614,12 @@ void QgsSnappingWidget::enableIntersectionSnapping( bool enabled )
|
|||||||
mProject->setSnappingConfig( mConfig );
|
mProject->setSnappingConfig( mConfig );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsSnappingWidget::enableSelfSnapping( bool enabled )
|
||||||
|
{
|
||||||
|
mConfig.setSelfSnapping( enabled );
|
||||||
|
mProject->setSnappingConfig( mConfig );
|
||||||
|
}
|
||||||
|
|
||||||
void QgsSnappingWidget::onSnappingTreeLayersChanged()
|
void QgsSnappingWidget::onSnappingTreeLayersChanged()
|
||||||
{
|
{
|
||||||
mLayerTreeView->expandAll();
|
mLayerTreeView->expandAll();
|
||||||
|
|||||||
@ -114,6 +114,8 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
|
|||||||
|
|
||||||
void enableIntersectionSnapping( bool enabled );
|
void enableIntersectionSnapping( bool enabled );
|
||||||
|
|
||||||
|
void enableSelfSnapping( bool enabled );
|
||||||
|
|
||||||
void modeButtonTriggered( QAction *action );
|
void modeButtonTriggered( QAction *action );
|
||||||
void avoidIntersectionsModeButtonTriggered( QAction *action );
|
void avoidIntersectionsModeButtonTriggered( QAction *action );
|
||||||
void typeButtonTriggered( QAction *action );
|
void typeButtonTriggered( QAction *action );
|
||||||
@ -172,6 +174,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
|
|||||||
QAction *mIntersectionSnappingAction = nullptr;
|
QAction *mIntersectionSnappingAction = nullptr;
|
||||||
QAction *mEnableTracingAction = nullptr;
|
QAction *mEnableTracingAction = nullptr;
|
||||||
QgsDoubleSpinBox *mTracingOffsetSpinBox = nullptr;
|
QgsDoubleSpinBox *mTracingOffsetSpinBox = nullptr;
|
||||||
|
QAction *mSelfSnappingAction = nullptr;
|
||||||
QTreeView *mLayerTreeView = nullptr;
|
QTreeView *mLayerTreeView = nullptr;
|
||||||
QWidget *mAdvancedConfigWidget = nullptr;
|
QWidget *mAdvancedConfigWidget = nullptr;
|
||||||
QgsFloatingWidget *mAdvancedConfigContainer = nullptr;
|
QgsFloatingWidget *mAdvancedConfigContainer = nullptr;
|
||||||
|
|||||||
@ -179,6 +179,7 @@ bool QgsSnappingConfig::operator==( const QgsSnappingConfig &other ) const
|
|||||||
&& mTolerance == other.mTolerance
|
&& mTolerance == other.mTolerance
|
||||||
&& mUnits == other.mUnits
|
&& mUnits == other.mUnits
|
||||||
&& mIntersectionSnapping == other.mIntersectionSnapping
|
&& mIntersectionSnapping == other.mIntersectionSnapping
|
||||||
|
&& mSelfSnapping == other.mSelfSnapping
|
||||||
&& mIndividualLayerSettings == other.mIndividualLayerSettings
|
&& mIndividualLayerSettings == other.mIndividualLayerSettings
|
||||||
&& mScaleDependencyMode == other.mScaleDependencyMode
|
&& mScaleDependencyMode == other.mScaleDependencyMode
|
||||||
&& mMinimumScale == other.mMinimumScale
|
&& mMinimumScale == other.mMinimumScale
|
||||||
@ -218,6 +219,7 @@ void QgsSnappingConfig::reset()
|
|||||||
mUnits = units;
|
mUnits = units;
|
||||||
}
|
}
|
||||||
mIntersectionSnapping = false;
|
mIntersectionSnapping = false;
|
||||||
|
mSelfSnapping = false;
|
||||||
|
|
||||||
// set advanced config
|
// set advanced config
|
||||||
if ( mProject )
|
if ( mProject )
|
||||||
@ -343,6 +345,16 @@ void QgsSnappingConfig::setIntersectionSnapping( bool enabled )
|
|||||||
mIntersectionSnapping = enabled;
|
mIntersectionSnapping = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QgsSnappingConfig::selfSnapping() const
|
||||||
|
{
|
||||||
|
return mSelfSnapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSnappingConfig::setSelfSnapping( bool enabled )
|
||||||
|
{
|
||||||
|
mSelfSnapping = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> QgsSnappingConfig::individualLayerSettings() const
|
QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> QgsSnappingConfig::individualLayerSettings() const
|
||||||
{
|
{
|
||||||
return mIndividualLayerSettings;
|
return mIndividualLayerSettings;
|
||||||
@ -459,6 +471,9 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
|
|||||||
if ( snapSettingsElem.hasAttribute( QStringLiteral( "intersection-snapping" ) ) )
|
if ( snapSettingsElem.hasAttribute( QStringLiteral( "intersection-snapping" ) ) )
|
||||||
mIntersectionSnapping = snapSettingsElem.attribute( QStringLiteral( "intersection-snapping" ) ) == QLatin1String( "1" );
|
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
|
// do not clear the settings as they must be automatically synchronized with current layers
|
||||||
QDomNodeList nodes = snapSettingsElem.elementsByTagName( QStringLiteral( "individual-layer-settings" ) );
|
QDomNodeList nodes = snapSettingsElem.elementsByTagName( QStringLiteral( "individual-layer-settings" ) );
|
||||||
if ( nodes.count() )
|
if ( nodes.count() )
|
||||||
@ -504,6 +519,7 @@ void QgsSnappingConfig::writeProject( QDomDocument &doc )
|
|||||||
snapSettingsElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
|
snapSettingsElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
|
||||||
snapSettingsElem.setAttribute( QStringLiteral( "unit" ), static_cast<int>( mUnits ) );
|
snapSettingsElem.setAttribute( QStringLiteral( "unit" ), static_cast<int>( mUnits ) );
|
||||||
snapSettingsElem.setAttribute( QStringLiteral( "intersection-snapping" ), QString::number( mIntersectionSnapping ) );
|
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( "scaleDependencyMode" ), QString::number( mScaleDependencyMode ) );
|
||||||
snapSettingsElem.setAttribute( QStringLiteral( "minScale" ), mMinimumScale );
|
snapSettingsElem.setAttribute( QStringLiteral( "minScale" ), mMinimumScale );
|
||||||
snapSettingsElem.setAttribute( QStringLiteral( "maxScale" ), mMaximumScale );
|
snapSettingsElem.setAttribute( QStringLiteral( "maxScale" ), mMaximumScale );
|
||||||
|
|||||||
@ -335,6 +335,20 @@ class CORE_EXPORT QgsSnappingConfig
|
|||||||
//! Sets if the snapping on intersection is enabled
|
//! Sets if the snapping on intersection is enabled
|
||||||
void setIntersectionSnapping( bool 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
|
//! Returns individual snapping settings for all layers
|
||||||
#ifndef SIP_RUN
|
#ifndef SIP_RUN
|
||||||
QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> individualLayerSettings() const;
|
QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> individualLayerSettings() const;
|
||||||
@ -458,6 +472,7 @@ class CORE_EXPORT QgsSnappingConfig
|
|||||||
double mMaximumScale = 0.0;
|
double mMaximumScale = 0.0;
|
||||||
QgsTolerance::UnitType mUnits = QgsTolerance::ProjectUnits;
|
QgsTolerance::UnitType mUnits = QgsTolerance::ProjectUnits;
|
||||||
bool mIntersectionSnapping = false;
|
bool mIntersectionSnapping = false;
|
||||||
|
bool mSelfSnapping = false;
|
||||||
|
|
||||||
QHash<QgsVectorLayer *, IndividualLayerSettings> mIndividualLayerSettings;
|
QHash<QgsVectorLayer *, IndividualLayerSettings> mIndividualLayerSettings;
|
||||||
|
|
||||||
|
|||||||
@ -275,6 +275,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
|||||||
return QgsPointLocator::Match();
|
return QgsPointLocator::Match();
|
||||||
|
|
||||||
QgsPointLocator::Match bestMatch;
|
QgsPointLocator::Match bestMatch;
|
||||||
|
QgsPointLocator::MatchList edges; // for snap on intersection
|
||||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
|
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
|
||||||
|
|
||||||
if ( mSnappingConfig.intersectionSnapping() )
|
if ( mSnappingConfig.intersectionSnapping() )
|
||||||
@ -282,8 +283,19 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
|||||||
QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
|
QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
|
||||||
if ( !locEdges )
|
if ( !locEdges )
|
||||||
return QgsPointLocator::Match();
|
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 );
|
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +334,8 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
|||||||
|
|
||||||
QgsPointLocator::Match bestMatch;
|
QgsPointLocator::Match bestMatch;
|
||||||
QgsPointLocator::MatchList edges; // for snap on intersection
|
QgsPointLocator::MatchList edges; // for snap on intersection
|
||||||
double maxSnapIntTolerance = 0;
|
double maxTolerance = 0;
|
||||||
|
QgsPointLocator::Type maxTypes;
|
||||||
|
|
||||||
for ( const LayerConfig &layerConfig : qgis::as_const( filteredConfigs ) )
|
for ( const LayerConfig &layerConfig : qgis::as_const( filteredConfigs ) )
|
||||||
{
|
{
|
||||||
@ -334,13 +347,24 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
|||||||
if ( mSnappingConfig.intersectionSnapping() )
|
if ( mSnappingConfig.intersectionSnapping() )
|
||||||
{
|
{
|
||||||
edges << loc->edgesInRect( pointMap, tolerance );
|
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() )
|
if ( mSnappingConfig.intersectionSnapping() )
|
||||||
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxSnapIntTolerance );
|
edges << loc->edgesInRect( pointMap, maxTolerance );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mSnappingConfig.intersectionSnapping() )
|
||||||
|
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
|
||||||
|
|
||||||
return bestMatch;
|
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() )
|
if ( mSnappingConfig.intersectionSnapping() )
|
||||||
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
|
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
|
||||||
|
|
||||||
|
|||||||
@ -185,6 +185,48 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
|||||||
*/
|
*/
|
||||||
void setEnableSnappingForInvisibleFeature( bool enable );
|
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:
|
public slots:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,6 +299,8 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
|||||||
LocatorsMap mTemporaryLocators;
|
LocatorsMap mTemporaryLocators;
|
||||||
//! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those)
|
//! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those)
|
||||||
QSet<QString> mHybridNonindexableLayers;
|
QSet<QString> mHybridNonindexableLayers;
|
||||||
|
//! list of additionnal snapping layers
|
||||||
|
QSet<QgsVectorLayer *> mExtraSnapLayers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a record for each layer seen:
|
* a record for each layer seen:
|
||||||
|
|||||||
@ -51,6 +51,15 @@ QgsMapToolCapture::QgsMapToolCapture( QgsMapCanvas *canvas, QgsAdvancedDigitizin
|
|||||||
connect( canvas, &QgsMapCanvas::currentLayerChanged,
|
connect( canvas, &QgsMapCanvas::currentLayerChanged,
|
||||||
this, &QgsMapToolCapture::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() );
|
currentLayerChanged( canvas->currentLayer() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +72,8 @@ QgsMapToolCapture::~QgsMapToolCapture()
|
|||||||
mValidator->deleteLater();
|
mValidator->deleteLater();
|
||||||
mValidator = nullptr;
|
mValidator = nullptr;
|
||||||
}
|
}
|
||||||
|
mExtraSnapLayer->deleteLater();
|
||||||
|
mExtraSnapLayer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QgsMapToolCapture::Capabilities QgsMapToolCapture::capabilities() const
|
QgsMapToolCapture::Capabilities QgsMapToolCapture::capabilities() const
|
||||||
@ -75,6 +86,7 @@ void QgsMapToolCapture::activate()
|
|||||||
if ( mTempRubberBand )
|
if ( mTempRubberBand )
|
||||||
mTempRubberBand->show();
|
mTempRubberBand->show();
|
||||||
|
|
||||||
|
mCanvas->snappingUtils()->addExtraSnapLayer( mExtraSnapLayer );
|
||||||
QgsMapToolAdvancedDigitizing::activate();
|
QgsMapToolAdvancedDigitizing::activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +97,7 @@ void QgsMapToolCapture::deactivate()
|
|||||||
|
|
||||||
mSnapIndicator->setMatch( QgsPointLocator::Match() );
|
mSnapIndicator->setMatch( QgsPointLocator::Match() );
|
||||||
|
|
||||||
|
mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
|
||||||
QgsMapToolAdvancedDigitizing::deactivate();
|
QgsMapToolAdvancedDigitizing::deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,6 +526,7 @@ int QgsMapToolCapture::addVertex( const QgsPointXY &point, const QgsPointLocator
|
|||||||
// ordinary digitizing
|
// ordinary digitizing
|
||||||
mRubberBand->addPoint( point );
|
mRubberBand->addPoint( point );
|
||||||
mCaptureCurve.addVertex( layerPoint );
|
mCaptureCurve.addVertex( layerPoint );
|
||||||
|
updateExtraSnapLayer();
|
||||||
mSnappingMatches.append( match );
|
mSnappingMatches.append( match );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,6 +588,7 @@ int QgsMapToolCapture::addCurve( QgsCurve *c )
|
|||||||
c->transform( ct, QgsCoordinateTransform::ReverseTransform );
|
c->transform( ct, QgsCoordinateTransform::ReverseTransform );
|
||||||
}
|
}
|
||||||
mCaptureCurve.addCurve( c );
|
mCaptureCurve.addCurve( c );
|
||||||
|
updateExtraSnapLayer();
|
||||||
for ( int i = 0; i < c->length(); ++i )
|
for ( int i = 0; i < c->length(); ++i )
|
||||||
mSnappingMatches.append( QgsPointLocator::Match() );
|
mSnappingMatches.append( QgsPointLocator::Match() );
|
||||||
|
|
||||||
@ -583,6 +598,7 @@ int QgsMapToolCapture::addCurve( QgsCurve *c )
|
|||||||
void QgsMapToolCapture::clearCurve()
|
void QgsMapToolCapture::clearCurve()
|
||||||
{
|
{
|
||||||
mCaptureCurve.clear();
|
mCaptureCurve.clear();
|
||||||
|
updateExtraSnapLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
|
QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
|
||||||
@ -627,6 +643,7 @@ void QgsMapToolCapture::undo()
|
|||||||
vertexToRemove.vertex = size() - 1;
|
vertexToRemove.vertex = size() - 1;
|
||||||
mCaptureCurve.deleteVertex( vertexToRemove );
|
mCaptureCurve.deleteVertex( vertexToRemove );
|
||||||
mSnappingMatches.removeAt( vertexToRemove.vertex );
|
mSnappingMatches.removeAt( vertexToRemove.vertex );
|
||||||
|
updateExtraSnapLayer();
|
||||||
|
|
||||||
mCadDockWidget->removePreviousPoint();
|
mCadDockWidget->removePreviousPoint();
|
||||||
|
|
||||||
@ -676,6 +693,7 @@ void QgsMapToolCapture::stopCapturing()
|
|||||||
|
|
||||||
mCapturing = false;
|
mCapturing = false;
|
||||||
mCaptureCurve.clear();
|
mCaptureCurve.clear();
|
||||||
|
updateExtraSnapLayer();
|
||||||
mSnappingMatches.clear();
|
mSnappingMatches.clear();
|
||||||
if ( currentVectorLayer() )
|
if ( currentVectorLayer() )
|
||||||
currentVectorLayer()->triggerRepaint();
|
currentVectorLayer()->triggerRepaint();
|
||||||
@ -695,6 +713,7 @@ void QgsMapToolCapture::clean()
|
|||||||
void QgsMapToolCapture::closePolygon()
|
void QgsMapToolCapture::closePolygon()
|
||||||
{
|
{
|
||||||
mCaptureCurve.close();
|
mCaptureCurve.close();
|
||||||
|
updateExtraSnapLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsMapToolCapture::validateGeometry()
|
void QgsMapToolCapture::validateGeometry()
|
||||||
@ -795,6 +814,7 @@ void QgsMapToolCapture::setPoints( const QVector<QgsPointXY> &pointList )
|
|||||||
QgsLineString *line = new QgsLineString( pointList );
|
QgsLineString *line = new QgsLineString( pointList );
|
||||||
mCaptureCurve.clear();
|
mCaptureCurve.clear();
|
||||||
mCaptureCurve.addCurve( line );
|
mCaptureCurve.addCurve( line );
|
||||||
|
updateExtraSnapLayer();
|
||||||
mSnappingMatches.clear();
|
mSnappingMatches.clear();
|
||||||
for ( int i = 0; i < line->length(); ++i )
|
for ( int i = 0; i < line->length(); ++i )
|
||||||
mSnappingMatches.append( QgsPointLocator::Match() );
|
mSnappingMatches.append( QgsPointLocator::Match() );
|
||||||
@ -805,6 +825,7 @@ void QgsMapToolCapture::setPoints( const QgsPointSequence &pointList )
|
|||||||
QgsLineString *line = new QgsLineString( pointList );
|
QgsLineString *line = new QgsLineString( pointList );
|
||||||
mCaptureCurve.clear();
|
mCaptureCurve.clear();
|
||||||
mCaptureCurve.addCurve( line );
|
mCaptureCurve.addCurve( line );
|
||||||
|
updateExtraSnapLayer();
|
||||||
mSnappingMatches.clear();
|
mSnappingMatches.clear();
|
||||||
for ( int i = 0; i < line->length(); ++i )
|
for ( int i = 0; i < line->length(); ++i )
|
||||||
mSnappingMatches.append( QgsPointLocator::Match() );
|
mSnappingMatches.append( QgsPointLocator::Match() );
|
||||||
@ -868,3 +889,19 @@ QgsPoint QgsMapToolCapture::mapPoint( const QgsMapMouseEvent &e ) const
|
|||||||
|
|
||||||
return newPoint;
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
#include "qgscompoundcurve.h"
|
#include "qgscompoundcurve.h"
|
||||||
#include "qgsgeometry.h"
|
#include "qgsgeometry.h"
|
||||||
#include "qobjectuniqueptr.h"
|
#include "qobjectuniqueptr.h"
|
||||||
|
#include "qgssnappingutils.h"
|
||||||
|
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@ -132,6 +133,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
|
|||||||
private slots:
|
private slots:
|
||||||
void addError( const QgsGeometry::Error &error );
|
void addError( const QgsGeometry::Error &error );
|
||||||
void currentLayerChanged( QgsMapLayer *layer );
|
void currentLayerChanged( QgsMapLayer *layer );
|
||||||
|
//! Update the extra snap layer, this should be called whenever the capturecurve changes
|
||||||
|
void updateExtraSnapLayer();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
@ -312,6 +315,11 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
|
|||||||
QList< QgsGeometry::Error > mGeomErrors;
|
QList< QgsGeometry::Error > mGeomErrors;
|
||||||
QList< QgsVertexMarker * > mGeomErrorMarkers;
|
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;
|
bool mCaptureModeFromLayer = false;
|
||||||
|
|
||||||
std::unique_ptr<QgsSnapIndicator> mSnapIndicator;
|
std::unique_ptr<QgsSnapIndicator> mSnapIndicator;
|
||||||
|
|||||||
@ -71,6 +71,7 @@ class TestQgsMapToolAddFeatureLine : public QObject
|
|||||||
void testZMSnapping();
|
void testZMSnapping();
|
||||||
void testTopologicalEditingZ();
|
void testTopologicalEditingZ();
|
||||||
void testCloseLine();
|
void testCloseLine();
|
||||||
|
void testSelfSnapping();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgisApp *mQgisApp = nullptr;
|
QgisApp *mQgisApp = nullptr;
|
||||||
@ -85,6 +86,7 @@ class TestQgsMapToolAddFeatureLine : public QObject
|
|||||||
QgsVectorLayer *mLayerTopoZ = nullptr;
|
QgsVectorLayer *mLayerTopoZ = nullptr;
|
||||||
QgsVectorLayer *mLayerLine2D = nullptr;
|
QgsVectorLayer *mLayerLine2D = nullptr;
|
||||||
QgsVectorLayer *mLayerCloseLine = nullptr;
|
QgsVectorLayer *mLayerCloseLine = nullptr;
|
||||||
|
QgsVectorLayer *mLayerSelfSnapLine = nullptr;
|
||||||
QgsFeatureId mFidLineF1 = 0;
|
QgsFeatureId mFidLineF1 = 0;
|
||||||
QgsFeatureId mFidCurvedF1 = 0;
|
QgsFeatureId mFidCurvedF1 = 0;
|
||||||
};
|
};
|
||||||
@ -208,8 +210,15 @@ void TestQgsMapToolAddFeatureLine::initTestCase()
|
|||||||
|
|
||||||
mLayerLine2D->addFeature( lineString2DF );
|
mLayerLine2D->addFeature( lineString2DF );
|
||||||
QCOMPARE( mLayerLine2D->featureCount(), ( long )1 );
|
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 ) );
|
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
|
||||||
|
|
||||||
// create the tool
|
// create the tool
|
||||||
@ -578,5 +587,55 @@ void TestQgsMapToolAddFeatureLine::testCloseLine()
|
|||||||
|
|
||||||
mLayerCloseLine->undoStack()->undo();
|
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 )
|
QGSTEST_MAIN( TestQgsMapToolAddFeatureLine )
|
||||||
#include "testqgsmaptooladdfeatureline.moc"
|
#include "testqgsmaptooladdfeatureline.moc"
|
||||||
|
|||||||
@ -529,6 +529,61 @@ class TestQgsSnappingUtils : public QObject
|
|||||||
QVERIFY( m5.isValid() );
|
QVERIFY( m5.isValid() );
|
||||||
QVERIFY( m5.hasVertex() );
|
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 )
|
QGSTEST_MAIN( TestQgsSnappingUtils )
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user