mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-16 00:05:45 -04: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/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>
|
||||
|
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;
|
||||
|
||||
QgsPointLocator::Match snapMatch;
|
||||
|
||||
QgsPointLocator::Match edgeMatch;
|
||||
|
||||
double softLockCommonAngle;
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 );
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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 )
|
||||
|
Loading…
x
Reference in New Issue
Block a user